Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions js/calendar/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// 最小年份
export const MIN_YEAR = 1970;

export default {
MIN_YEAR,
};
9 changes: 9 additions & 0 deletions js/calendar/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type CalendarValue = string | Date;

/**
* 日历的显示范围
*/
export interface CalendarRange {
from: CalendarValue;
to: CalendarValue;
}
73 changes: 73 additions & 0 deletions js/calendar/utils.ts
Original file line number Diff line number Diff line change
@@ -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<CalendarValue>
): { 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,
};
};
103 changes: 103 additions & 0 deletions test/unit/calendar/utils.test.js
Original file line number Diff line number Diff line change
@@ -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");
});
});
});