diff --git a/js/calendar/consts.ts b/js/calendar/consts.ts new file mode 100644 index 0000000000..299f4fd4d9 --- /dev/null +++ b/js/calendar/consts.ts @@ -0,0 +1,6 @@ +// 最小年份 +export const MIN_YEAR = 1970; + +export default { + MIN_YEAR, +}; diff --git a/js/calendar/types.ts b/js/calendar/types.ts new file mode 100644 index 0000000000..68653a8c26 --- /dev/null +++ b/js/calendar/types.ts @@ -0,0 +1,9 @@ +export type CalendarValue = string | Date; + +/** + * 日历的显示范围 + */ +export interface CalendarRange { + from: CalendarValue; + to: CalendarValue; +} diff --git a/js/calendar/utils.ts b/js/calendar/utils.ts new file mode 100644 index 0000000000..c81aa21cbc --- /dev/null +++ b/js/calendar/utils.ts @@ -0,0 +1,73 @@ +import dayjs from 'dayjs'; + +import type { CalendarRange, CalendarValue } from './types'; +import { MIN_YEAR } from './consts'; + +/** + * 根据当前时间创建一个默认日期 + * @returns 当前日期的dayjs对象 + */ +export const createDefaultCurDate = (): dayjs.Dayjs => dayjs(dayjs().format('YYYY-MM-DD')); + +/** + * 处理`range`参数输入值并生成日历范围 + * @param range 用于设置日历的年月份显示范围,[范围开始,范围结束] + * @returns 处理完成的日历范围 + */ +export const handleRange = ( + range?: Array +): { from: CalendarValue; to: CalendarValue } | null => { + // 检查范围边界 + const parseRangeBoundary = ( + value: CalendarRange['from'] | CalendarRange['to'] | null | undefined + ) => { + if (value === undefined || value === null) { + return null; + } + const parsed = dayjs(value); + if (!parsed.isValid()) { + return null; + } + return { + parsed, // dayjs 对象 + original: value as CalendarRange['from'] | CalendarRange['to'], // 原始值 + }; + }; + + if (!range || range.length < 2) { + return null; + } + const [v1, v2] = range; + const start = parseRangeBoundary(v1); + const end = parseRangeBoundary(v2); + + if (!start && !end) { + return null; + } + + // 未指定边界上/下限时使用默认值 + const fallback = ( + edge: 'from' | 'to' + ): { parsed: dayjs.Dayjs; original: string } => { + let fallbackParsed = dayjs(MIN_YEAR); + if (edge === 'to') { + fallbackParsed = createDefaultCurDate(); + } + return { + parsed: fallbackParsed, + original: fallbackParsed.format('YYYY-MM-DD'), + }; + }; + + let fromBoundary = start ?? fallback('from'); + let toBoundary = end ?? fallback('to'); + + if (fromBoundary.parsed.isAfter(toBoundary.parsed)) { + [fromBoundary, toBoundary] = [toBoundary, fromBoundary]; // 当前一项日期大于后一项时交换两值以确保边界逻辑正确 + } + + return { + from: fromBoundary.original, + to: toBoundary.original, + }; +}; diff --git a/test/unit/calendar/utils.test.js b/test/unit/calendar/utils.test.js new file mode 100644 index 0000000000..dcfc0d265b --- /dev/null +++ b/test/unit/calendar/utils.test.js @@ -0,0 +1,103 @@ +import { describe, it, expect } from "vitest"; +import dayjs from "dayjs"; +import { createDefaultCurDate, handleRange } from "../../../js/calendar/utils"; + +describe("utils", () => { + describe("createDefaultCurDate", () => { + it("返回当前日期的dayjs对象且日期格式为YYYY-MM-DD", () => { + const result = createDefaultCurDate(); + expect(result).toBeDefined(); + expect(result.isValid()).toBe(true); + const formatted = result.format("YYYY-MM-DD"); + const expected = dayjs().format("YYYY-MM-DD"); + expect(formatted).toBe(expected); + }); + }); + + describe("handleRange", () => { + // 场景1: 传入非列表值/传入列表值少于两项/传入列表值的两项均非日期格式 + it("传入 undefined 返回 null", () => { + const result = handleRange(undefined); + expect(result).toBeNull(); + }); + + it("传入 null 返回 null", () => { + const result = handleRange(null); + expect(result).toBeNull(); + }); + + it("传入非列表值返回 null", () => { + const result = handleRange("not-a-array"); + expect(result).toBeNull(); + }); + + it("传入长度小于2的列表返回 null", () => { + expect(handleRange([])).toBeNull(); + expect(handleRange(["2024-01-15"])).toBeNull(); + }); + + it("传入长度为2且两项均非日期格式的列表返回 null", () => { + expect(handleRange([null, undefined])).toBeNull(); + expect(handleRange(["abc", "not-a-date"])).toBeNull(); + }); + + // 场景2: 传入列表值的两项中有一项符合日期格式,另一项不符合 + it("传入第一项为非日期格式且第二项为日期的列表,第一项使用默认值处理", () => { + const result = handleRange(["abc", "2024-01-15"]); + expect(result).not.toBeNull(); + expect(result.from).toBe("1970-01-01"); + expect(result.to).toBe("2024-01-15"); + }); + + it("传入第一项为日期且第二项为非日期格式的列表,第二项使用默认值处理", () => { + const result = handleRange(["2024-01-15", "not-a-date"]); + const today = dayjs().format("YYYY-MM-DD"); + expect(result).not.toBeNull(); + expect(result.from).toBe("2024-01-15"); + expect(result.to).toBe(today); + }); + + // 场景3: 传入列表值的两项均符合日期格式 + it("传入两项均为日期列表且第一项日期早于第二项日期,原样返回", () => { + const result = handleRange(["2024-01-15", "2024-12-31"]); + expect(result).not.toBeNull(); + expect(result.from).toBe("2024-01-15"); + expect(result.to).toBe("2024-12-31"); + }); + + it("传入两项均为日期列表且第一项日期不早于第二项日期,交换两项后返回", () => { + const result = handleRange(["2024-12-31", "2024-01-15"]); + expect(result).not.toBeNull(); + expect(result.from).toBe("2024-01-15"); + expect(result.to).toBe("2024-12-31"); + }); + + it("处理传入两项为Date格式的列表", () => { + const date1 = new Date("2024-01-15"); + const date2 = new Date("2024-12-31"); + const result = handleRange([date1, date2]); + expect(result).not.toBeNull(); + expect(result.from).toBeInstanceOf(Date); + expect(result.to).toBeInstanceOf(Date); + expect(result.from.getTime()).toBe(new Date("2024-01-15").getTime()); + expect(result.to.getTime()).toBe(new Date("2024-12-31").getTime()); + }); + + it("处理传入一项字符串格式日期另一项为Date格式的列表", () => { + const date1 = new Date("2024-01-15"); + const result = handleRange([date1, "2024-12-31"]); + expect(result).not.toBeNull(); + // Date object is returned as-is, string is returned as string + expect(result.from).toBeInstanceOf(Date); + expect(result.to).toBe("2024-12-31"); + expect(result.from.getTime()).toBe(new Date("2024-01-15").getTime()); + }); + + it("传入两项均为日期且处于同一天的列表,原样返回", () => { + const result = handleRange(["2024-01-15", "2024-01-15"]); + expect(result).not.toBeNull(); + expect(result.from).toBe("2024-01-15"); + expect(result.to).toBe("2024-01-15"); + }); + }); +});