mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 01:02:01 +03:00
add tests for datetime.ts
Covers detectDateOrder, resolveZone, addToDate and isCalendarUnit — none of it had direct unit coverage before, only indirectly through the engine's date-arithmetic tests.
This commit is contained in:
parent
a0cda64b95
commit
d71ad92422
1 changed files with 208 additions and 0 deletions
208
src/engine/datetime.test.ts
Normal file
208
src/engine/datetime.test.ts
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||||
|
import {
|
||||||
|
detectDateOrder,
|
||||||
|
resolveZone,
|
||||||
|
startOfToday,
|
||||||
|
addToDate,
|
||||||
|
isCalendarUnit,
|
||||||
|
} from "./datetime";
|
||||||
|
|
||||||
|
describe("datetime", () => {
|
||||||
|
describe("detectDateOrder", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect dmy", () => {
|
||||||
|
const mockFormatToParts = vi.fn().mockReturnValue([
|
||||||
|
{ type: "day", value: "31" },
|
||||||
|
{ type: "literal", value: "/" },
|
||||||
|
{ type: "month", value: "12" },
|
||||||
|
{ type: "literal", value: "/" },
|
||||||
|
{ type: "year", value: "2000" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mockDateTimeFormat = vi.spyOn(Intl, "DateTimeFormat").mockImplementation(() => ({
|
||||||
|
formatToParts: mockFormatToParts,
|
||||||
|
}) as any);
|
||||||
|
|
||||||
|
expect(detectDateOrder()).toBe("dmy");
|
||||||
|
mockDateTimeFormat.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect mdy", () => {
|
||||||
|
const mockFormatToParts = vi.fn().mockReturnValue([
|
||||||
|
{ type: "month", value: "12" },
|
||||||
|
{ type: "literal", value: "/" },
|
||||||
|
{ type: "day", value: "31" },
|
||||||
|
{ type: "literal", value: "/" },
|
||||||
|
{ type: "year", value: "2000" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mockDateTimeFormat = vi.spyOn(Intl, "DateTimeFormat").mockImplementation(() => ({
|
||||||
|
formatToParts: mockFormatToParts,
|
||||||
|
}) as any);
|
||||||
|
|
||||||
|
expect(detectDateOrder()).toBe("mdy");
|
||||||
|
mockDateTimeFormat.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should default to dmy if neither is found", () => {
|
||||||
|
const mockFormatToParts = vi.fn().mockReturnValue([
|
||||||
|
{ type: "year", value: "2000" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mockDateTimeFormat = vi.spyOn(Intl, "DateTimeFormat").mockImplementation(() => ({
|
||||||
|
formatToParts: mockFormatToParts,
|
||||||
|
}) as any);
|
||||||
|
|
||||||
|
expect(detectDateOrder()).toBe("dmy");
|
||||||
|
mockDateTimeFormat.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolveZone", () => {
|
||||||
|
it("should resolve 'local' to system timezone", () => {
|
||||||
|
const mockResolvedOptions = vi.fn().mockReturnValue({ timeZone: "America/Los_Angeles" });
|
||||||
|
const mockDateTimeFormat = vi.spyOn(Intl, "DateTimeFormat").mockImplementation(() => ({
|
||||||
|
resolvedOptions: mockResolvedOptions,
|
||||||
|
}) as any);
|
||||||
|
|
||||||
|
expect(resolveZone(["local"])).toBe("America/Los_Angeles");
|
||||||
|
mockDateTimeFormat.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve known abbreviations", () => {
|
||||||
|
expect(resolveZone(["utc"])).toBe("UTC");
|
||||||
|
expect(resolveZone(["est"])).toBe("America/New_York");
|
||||||
|
expect(resolveZone(["pst"])).toBe("America/Los_Angeles");
|
||||||
|
expect(resolveZone(["jst"])).toBe("Asia/Tokyo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve known cities", () => {
|
||||||
|
expect(resolveZone(["new", "york"])).toBe("America/New_York");
|
||||||
|
expect(resolveZone(["london"])).toBe("Europe/London");
|
||||||
|
expect(resolveZone(["tokyo"])).toBe("Asia/Tokyo");
|
||||||
|
expect(resolveZone(["buenos", "aires"])).toBe("America/Argentina/Buenos_Aires");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve russian cities", () => {
|
||||||
|
expect(resolveZone(["нью", "йорк"])).toBe("America/New_York");
|
||||||
|
expect(resolveZone(["москва"])).toBe("Europe/Moscow");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore punctuation and case", () => {
|
||||||
|
expect(resolveZone(["New-York"])).toBe("America/New_York");
|
||||||
|
expect(resolveZone(["LONdoN."])).toBe("Europe/London");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve raw IANA timezone", () => {
|
||||||
|
expect(resolveZone(["Europe", "Berlin"])).toBe("Europe/Berlin");
|
||||||
|
expect(resolveZone(["America", "Argentina", "Mendoza"])).toBe("America/Argentina/Mendoza");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null for unknown timezones", () => {
|
||||||
|
expect(resolveZone(["Unknown", "City"])).toBeNull();
|
||||||
|
expect(resolveZone(["Invalid/Timezone"])).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("startOfToday", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return midnight of the current day", () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(new Date("2024-05-15T14:30:00Z"));
|
||||||
|
|
||||||
|
const expected = new Date("2024-05-15T14:30:00Z");
|
||||||
|
expected.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
expect(startOfToday()).toBe(expected.getTime());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addToDate", () => {
|
||||||
|
it("should add days", () => {
|
||||||
|
const ms = new Date("2024-01-01T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 5, "day");
|
||||||
|
expect(new Date(res).toISOString()).toBe("2024-01-06T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add weeks", () => {
|
||||||
|
const ms = new Date("2024-01-01T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 2, "week");
|
||||||
|
expect(new Date(res).toISOString()).toBe("2024-01-15T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add months", () => {
|
||||||
|
const ms = new Date("2024-01-15T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 2, "month");
|
||||||
|
expect(new Date(res).toISOString()).toBe("2024-03-15T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clamp end of month correctly", () => {
|
||||||
|
const ms = new Date("2024-01-31T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 1, "month");
|
||||||
|
// 2024 is a leap year, so Feb 29
|
||||||
|
expect(new Date(res).toISOString()).toBe("2024-02-29T12:00:00.000Z");
|
||||||
|
|
||||||
|
const ms2 = new Date("2023-01-31T12:00:00Z").getTime();
|
||||||
|
const res2 = addToDate(ms2, 1, "month");
|
||||||
|
// 2023 is not a leap year, so Feb 28
|
||||||
|
expect(new Date(res2).toISOString()).toBe("2023-02-28T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add years", () => {
|
||||||
|
const ms = new Date("2024-01-15T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 3, "year");
|
||||||
|
expect(new Date(res).toISOString()).toBe("2027-01-15T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clamp end of month when adding years to leap day", () => {
|
||||||
|
const ms = new Date("2024-02-29T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 1, "year");
|
||||||
|
expect(new Date(res).toISOString()).toBe("2025-02-28T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle unit ids with prefixes", () => {
|
||||||
|
const ms = new Date("2024-01-01T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 5, "unit:day");
|
||||||
|
expect(new Date(res).toISOString()).toBe("2024-01-06T12:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return original ms if amount is not an integer", () => {
|
||||||
|
const ms = new Date("2024-01-01T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 1.5, "day");
|
||||||
|
expect(res).toBe(ms);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return original ms for unhandled units", () => {
|
||||||
|
const ms = new Date("2024-01-01T12:00:00Z").getTime();
|
||||||
|
const res = addToDate(ms, 1, "hour");
|
||||||
|
expect(res).toBe(ms);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isCalendarUnit", () => {
|
||||||
|
it("should return true for valid calendar units with integer amounts", () => {
|
||||||
|
expect(isCalendarUnit("day", 1)).toBe(true);
|
||||||
|
expect(isCalendarUnit("week", 5)).toBe(true);
|
||||||
|
expect(isCalendarUnit("month", -2)).toBe(true);
|
||||||
|
expect(isCalendarUnit("year", 10)).toBe(true);
|
||||||
|
expect(isCalendarUnit("unit:day", 1)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false for floating point amounts", () => {
|
||||||
|
expect(isCalendarUnit("day", 1.5)).toBe(false);
|
||||||
|
expect(isCalendarUnit("month", 0.5)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false for non-calendar units", () => {
|
||||||
|
expect(isCalendarUnit("hour", 1)).toBe(false);
|
||||||
|
expect(isCalendarUnit("minute", 1)).toBe(false);
|
||||||
|
expect(isCalendarUnit("unit:meter", 1)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue