Files
2026-09-27 21:59:13 +08:00

211 lines
7.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest'
import {
canonicalizeDateText,
listDateSegments,
parseDateInput,
resolveDateSegment,
stepDateSegment,
} from '@/components/ui/date/date-utils.js'
const DATE_FMT = 'YYYY-MM-DD'
const DATETIME_FMT = 'YYYY-MM-DD HH:mm:ss'
/** 段列表压缩成 { field: [start, end] } 便于断言 */
function segmentMap(text, format) {
return Object.fromEntries(listDateSegments(text, format).map((s) => [s.field, [s.start, s.end]]))
}
const at = (text) => new Date(text)
describe('date-utils/listDateSegments + resolveDateSegment', () => {
it('YYYY-MM-DD 下三段区间正确', () => {
expect(segmentMap('2026-09-26', DATE_FMT)).toEqual({
year: [0, 4],
month: [5, 7],
day: [8, 10],
})
})
it('分隔符换成 / 、. 或年月日汉字后段区间不变', () => {
expect(segmentMap('2026/09/26', DATE_FMT)).toEqual({
year: [0, 4],
month: [5, 7],
day: [8, 10],
})
expect(segmentMap('2026.09.26', DATE_FMT)).toEqual({
year: [0, 4],
month: [5, 7],
day: [8, 10],
})
expect(segmentMap('2026年09月26日', 'YYYY年MM月DD日')).toEqual({
year: [0, 4],
month: [5, 7],
day: [8, 10],
})
// 反向也成立:format 带汉字,手打短横线同样能定位到段
expect(segmentMap('2026-09-26', 'YYYY年MM月DD日')).toEqual({
year: [0, 4],
month: [5, 7],
day: [8, 10],
})
})
it('含时分秒时追加 hour / minute / second 段', () => {
expect(segmentMap('2026-09-26 14:30:00', DATETIME_FMT)).toEqual({
year: [0, 4],
month: [5, 7],
day: [8, 10],
hour: [11, 13],
minute: [14, 16],
second: [17, 19],
})
})
it('hh:mm A 下 meridiem 段覆盖整段 PM(大小写均可识别)', () => {
expect(segmentMap('02:30 PM', 'hh:mm A')).toEqual({
hour: [0, 2],
minute: [3, 5],
meridiem: [6, 8],
})
expect(segmentMap('02:30 pm', 'hh:mm A')).toEqual({
hour: [0, 2],
minute: [3, 5],
meridiem: [6, 8],
})
})
it('Do 的 day 段含序数后缀,MMM 的 month 段为整段字母', () => {
expect(segmentMap('26th', 'Do')).toEqual({ day: [0, 4] })
expect(segmentMap('Sep', 'MMM')).toEqual({ month: [0, 3] })
})
it('相邻段首尾相接(YYYYMMDD)时边界位归左段', () => {
expect(segmentMap('20260926', 'YYYYMMDD')).toEqual({
year: [0, 4],
month: [4, 6],
day: [6, 8],
})
expect(resolveDateSegment('20260926', 4, 'YYYYMMDD')?.field).toBe('year')
expect(resolveDateSegment('20260926', 5, 'YYYYMMDD')?.field).toBe('month')
})
it('光标停在分隔符上归左侧段,停在文本末尾归最后一段', () => {
expect(resolveDateSegment('2026-09-26', 4, DATE_FMT)?.field).toBe('year')
expect(resolveDateSegment('2026-09-26', 7, DATE_FMT)?.field).toBe('month')
expect(resolveDateSegment('2026-09-26', 10, DATE_FMT)?.field).toBe('day')
})
it('caret 越界时被 clamp 到文本长度,空文本与不匹配文本返回 null', () => {
expect(resolveDateSegment('2026-09-26', 999, DATE_FMT)?.field).toBe('day')
expect(resolveDateSegment('', 0, DATE_FMT)).toBeNull()
expect(resolveDateSegment('2026-09-26', 3, 'hh:mm A')).toBeNull()
expect(resolveDateSegment('2026-09-26', 3, DATETIME_FMT)).toBeNull() // 缺时分秒,整串失配
})
it('不可步进的 token(dddd)不产生段', () => {
expect(listDateSegments('星期一', 'dddd')).toEqual([])
})
})
describe('date-utils/stepDateSegment', () => {
it('day +1 跨月进位', () => {
expect(stepDateSegment(at('2026-09-30T00:00:00'), 'day', 1)).toEqual(
new Date('2026-10-01T00:00:00'),
)
})
it('month +1 月末夹取', () => {
expect(stepDateSegment(at('2026-01-31T00:00:00'), 'month', 1)).toEqual(
new Date('2026-02-28T00:00:00'),
)
})
it('year +1 闰日夹取', () => {
expect(stepDateSegment(at('2024-02-29T00:00:00'), 'year', 1)).toEqual(
new Date('2025-02-28T00:00:00'),
)
})
it('hour / minute / second 回绕且日期不变', () => {
expect(stepDateSegment(at('2026-09-26T23:10:00'), 'hour', 1)).toEqual(
new Date('2026-09-26T00:10:00'),
)
expect(stepDateSegment(at('2026-09-26T14:59:00'), 'minute', 1)).toEqual(
new Date('2026-09-26T14:00:00'),
)
expect(stepDateSegment(at('2026-09-26T14:30:59'), 'second', 1)).toEqual(
new Date('2026-09-26T14:30:00'),
)
})
it('hour -1 从 0 回绕到 23 且日期不变', () => {
expect(stepDateSegment(at('2026-09-26T00:10:00'), 'hour', -1)).toEqual(
new Date('2026-09-26T23:10:00'),
)
})
it('meridiem ±1 只翻转上下午,小时数字不变(12 小时制显示)', () => {
const am = at('2026-09-26T09:30:00')
const pm = stepDateSegment(am, 'meridiem', 1)
expect(pm).toEqual(new Date('2026-09-26T21:30:00'))
expect(stepDateSegment(pm, 'meridiem', -1)).toEqual(am)
})
it('非法 field 返回原日期,非法日期返回 null', () => {
const value = at('2026-09-26T09:30:00')
expect(stepDateSegment(value, 'unknown', 1)).toEqual(value)
expect(stepDateSegment(null, 'day', 1)).toBeNull()
})
})
describe('date-utils/parseDateInput', () => {
it('空串与纯空白视为 isEmpty 且合法(组件按清空值处理)', () => {
expect(parseDateInput('', DATE_FMT)).toEqual({ date: null, isEmpty: true, isValid: true })
expect(parseDateInput(' ', DATE_FMT)).toEqual({ date: null, isEmpty: true, isValid: true })
})
it('严格格式命中', () => {
expect(parseDateInput('2026-09-26', DATE_FMT).date).toEqual(new Date('2026-09-26T00:00:00'))
})
it('斜杠与点号分隔经归一化后命中', () => {
expect(parseDateInput('2026/09/26', DATE_FMT).date).toEqual(new Date('2026-09-26T00:00:00'))
expect(parseDateInput('2026.09.26', DATE_FMT).date).toEqual(new Date('2026-09-26T00:00:00'))
})
it('中文年月日经 canonicalize 后命中', () => {
expect(parseDateInput('2026年09月26日', DATE_FMT).date).toEqual(new Date('2026-09-26T00:00:00'))
})
it('含时分秒的输入对 YYYY-MM-DD HH:mm 也能命中', () => {
const result = parseDateInput('2026-09-26 14:30:00', 'YYYY-MM-DD HH:mm')
expect(result.isValid).toBe(true)
expect(result.date).toEqual(new Date('2026-09-26T14:30:00'))
})
it('部分输入(只有年月)命中且日补 1', () => {
const result = parseDateInput('2026-09', DATE_FMT)
expect(result.isValid).toBe(true)
expect(result.date).toEqual(new Date('2026-09-01T00:00:00'))
})
it('纯文字与非法日期判定为不合法', () => {
expect(parseDateInput('abc', DATE_FMT)).toEqual({ date: null, isEmpty: false, isValid: false })
expect(parseDateInput('2026-13-45', DATE_FMT).isValid).toBe(false)
})
it('format 为数组时任一写法都能命中', () => {
const fmts = ['YYYY年MM月DD日', 'YYYY-MM-DD']
expect(parseDateInput('2026年09月26日', fmts).isValid).toBe(true)
expect(parseDateInput('2026-09-26', fmts).isValid).toBe(true)
})
})
describe('date-utils/canonicalizeDateText', () => {
it('统一分隔符并压缩空白', () => {
expect(canonicalizeDateText(' 2026年09月26日 ')).toBe('2026-09-26')
expect(canonicalizeDateText('2026.09.26')).toBe('2026-09-26')
expect(canonicalizeDateText('2026-09-26 14:30')).toBe('2026-09-26 14:30')
})
})