Some checks failed
CI / lint-and-test (push) Failing after 45s
- Added Node.js setup and webapp testing steps to the CI workflow for improved integration. - Updated HTML to link multiple CSS files for better modularity and organization of styles. - Removed deprecated `style.css` and introduced new CSS files for base styles, calendar, day detail, hints, markers, states, and duty list to enhance maintainability and readability. - Implemented new styles for improved presentation of duty information and user interactions. - Added unit tests for new API functions and contact link rendering to ensure functionality and reliability.
231 lines
6.6 KiB
JavaScript
231 lines
6.6 KiB
JavaScript
/**
|
|
* Unit tests for dateUtils: localDateString, dutyOverlapsLocalDay,
|
|
* dutyOverlapsLocalRange, getMonday, formatHHMM.
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
localDateString,
|
|
dutyOverlapsLocalDay,
|
|
dutyOverlapsLocalRange,
|
|
getMonday,
|
|
formatHHMM,
|
|
firstDayOfMonth,
|
|
lastDayOfMonth,
|
|
formatDateKey,
|
|
dateKeyToDDMM,
|
|
} from "./dateUtils.js";
|
|
|
|
describe("localDateString", () => {
|
|
it("formats date as YYYY-MM-DD", () => {
|
|
const d = new Date(2025, 0, 15);
|
|
expect(localDateString(d)).toBe("2025-01-15");
|
|
});
|
|
|
|
it("pads month and day with zero", () => {
|
|
expect(localDateString(new Date(2025, 0, 5))).toBe("2025-01-05");
|
|
expect(localDateString(new Date(2025, 8, 9))).toBe("2025-09-09");
|
|
});
|
|
|
|
it("handles December and year boundary", () => {
|
|
expect(localDateString(new Date(2024, 11, 31))).toBe("2024-12-31");
|
|
});
|
|
});
|
|
|
|
describe("dutyOverlapsLocalDay", () => {
|
|
it("returns true when duty spans the whole day", () => {
|
|
const d = {
|
|
start_at: "2025-02-25T00:00:00Z",
|
|
end_at: "2025-02-25T23:59:59Z",
|
|
};
|
|
expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(true);
|
|
});
|
|
|
|
it("returns true when duty overlaps part of the day", () => {
|
|
const d = {
|
|
start_at: "2025-02-25T09:00:00Z",
|
|
end_at: "2025-02-25T14:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(true);
|
|
});
|
|
|
|
it("returns true when duty continues from previous day", () => {
|
|
const d = {
|
|
start_at: "2025-02-24T22:00:00Z",
|
|
end_at: "2025-02-25T06:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(true);
|
|
});
|
|
|
|
it("returns false when duty ends before the day", () => {
|
|
const d = {
|
|
start_at: "2025-02-24T09:00:00Z",
|
|
end_at: "2025-02-24T18:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(false);
|
|
});
|
|
|
|
it("returns false when duty starts after the day", () => {
|
|
const d = {
|
|
start_at: "2025-02-26T09:00:00Z",
|
|
end_at: "2025-02-26T18:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalDay(d, "2025-02-25")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("dutyOverlapsLocalRange", () => {
|
|
it("returns true when duty overlaps the range", () => {
|
|
const d = {
|
|
start_at: "2025-02-24T12:00:00Z",
|
|
end_at: "2025-02-26T12:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(true);
|
|
});
|
|
|
|
it("returns true when duty is entirely inside the range", () => {
|
|
const d = {
|
|
start_at: "2025-02-26T09:00:00Z",
|
|
end_at: "2025-02-26T18:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(true);
|
|
});
|
|
|
|
it("returns false when duty ends before range start", () => {
|
|
const d = {
|
|
start_at: "2025-02-20T09:00:00Z",
|
|
end_at: "2025-02-22T18:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(false);
|
|
});
|
|
|
|
it("returns false when duty starts after range end", () => {
|
|
const d = {
|
|
start_at: "2025-03-01T09:00:00Z",
|
|
end_at: "2025-03-01T18:00:00Z",
|
|
};
|
|
expect(dutyOverlapsLocalRange(d, "2025-02-25", "2025-02-28")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getMonday", () => {
|
|
it("returns same day when date is Monday", () => {
|
|
const monday = new Date(2025, 0, 6); // 6 Jan 2025 is Monday
|
|
const result = getMonday(monday);
|
|
expect(result.getFullYear()).toBe(2025);
|
|
expect(result.getMonth()).toBe(0);
|
|
expect(result.getDate()).toBe(6);
|
|
expect(result.getDay()).toBe(1);
|
|
});
|
|
|
|
it("returns previous Monday for Wednesday", () => {
|
|
const wed = new Date(2025, 0, 8);
|
|
const result = getMonday(wed);
|
|
expect(result.getDay()).toBe(1);
|
|
expect(result.getDate()).toBe(6);
|
|
});
|
|
|
|
it("returns Monday of same week for Sunday", () => {
|
|
const sun = new Date(2025, 0, 12);
|
|
const result = getMonday(sun);
|
|
expect(result.getDay()).toBe(1);
|
|
expect(result.getDate()).toBe(6);
|
|
});
|
|
});
|
|
|
|
describe("formatHHMM", () => {
|
|
it("formats ISO string as HH:MM in local time", () => {
|
|
const s = "2025-02-25T14:30:00Z";
|
|
const result = formatHHMM(s);
|
|
expect(result).toMatch(/^\d{2}:\d{2}$/);
|
|
const d = new Date(s);
|
|
const expected = (d.getHours() < 10 ? "0" : "") + d.getHours() + ":" + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes();
|
|
expect(result).toBe(expected);
|
|
});
|
|
|
|
it("returns empty string for null", () => {
|
|
expect(formatHHMM(null)).toBe("");
|
|
});
|
|
|
|
it("returns empty string for undefined", () => {
|
|
expect(formatHHMM(undefined)).toBe("");
|
|
});
|
|
|
|
it("returns empty string for empty string", () => {
|
|
expect(formatHHMM("")).toBe("");
|
|
});
|
|
|
|
it("pads hours and minutes with zero", () => {
|
|
const s = "2025-02-25T09:05:00Z";
|
|
const result = formatHHMM(s);
|
|
expect(result).toMatch(/^\d{2}:\d{2}$/);
|
|
});
|
|
});
|
|
|
|
describe("firstDayOfMonth", () => {
|
|
it("returns first day of month", () => {
|
|
const d = new Date(2025, 5, 15);
|
|
const result = firstDayOfMonth(d);
|
|
expect(result.getFullYear()).toBe(2025);
|
|
expect(result.getMonth()).toBe(5);
|
|
expect(result.getDate()).toBe(1);
|
|
});
|
|
|
|
it("handles January", () => {
|
|
const d = new Date(2025, 0, 31);
|
|
const result = firstDayOfMonth(d);
|
|
expect(result.getDate()).toBe(1);
|
|
expect(result.getMonth()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("lastDayOfMonth", () => {
|
|
it("returns last day of month", () => {
|
|
const d = new Date(2025, 0, 15);
|
|
const result = lastDayOfMonth(d);
|
|
expect(result.getFullYear()).toBe(2025);
|
|
expect(result.getMonth()).toBe(0);
|
|
expect(result.getDate()).toBe(31);
|
|
});
|
|
|
|
it("returns 28 for non-leap February", () => {
|
|
const d = new Date(2023, 1, 1);
|
|
const result = lastDayOfMonth(d);
|
|
expect(result.getDate()).toBe(28);
|
|
expect(result.getMonth()).toBe(1);
|
|
});
|
|
|
|
it("returns 29 for leap February", () => {
|
|
const d = new Date(2024, 1, 1);
|
|
const result = lastDayOfMonth(d);
|
|
expect(result.getDate()).toBe(29);
|
|
});
|
|
});
|
|
|
|
describe("formatDateKey", () => {
|
|
it("formats ISO date string as DD.MM (local time)", () => {
|
|
const result = formatDateKey("2025-02-25T00:00:00Z");
|
|
expect(result).toMatch(/^\d{2}\.\d{2}$/);
|
|
const [day, month] = result.split(".");
|
|
expect(Number(day)).toBeGreaterThanOrEqual(1);
|
|
expect(Number(day)).toBeLessThanOrEqual(31);
|
|
expect(Number(month)).toBeGreaterThanOrEqual(1);
|
|
expect(Number(month)).toBeLessThanOrEqual(12);
|
|
});
|
|
|
|
it("returns DD.MM format with zero-padding", () => {
|
|
const result = formatDateKey("2025-01-05T12:00:00Z");
|
|
expect(result).toMatch(/^\d{2}\.\d{2}$/);
|
|
});
|
|
});
|
|
|
|
describe("dateKeyToDDMM", () => {
|
|
it("converts YYYY-MM-DD to DD.MM", () => {
|
|
expect(dateKeyToDDMM("2025-02-25")).toBe("25.02");
|
|
});
|
|
|
|
it("handles single-digit day and month", () => {
|
|
expect(dateKeyToDDMM("2025-01-09")).toBe("09.01");
|
|
});
|
|
});
|