import { describe, expect, it } from "vitest"; import { addDaysIso, daysBetweenIso, fmtAge, fmtDate, initials, tenure, toIsoDate, yearsBetweenIso } from "@/lib/format"; describe("fmtDate", () => { it("formats an ISO date string in de-AT order", () => { expect(fmtDate("2026-03-05")).toBe("05.03.2026"); }); // A date-only column has no time and no zone. Routing it through a Date // would anchor it to UTC midnight and render the previous day wherever the // renderer sits west of UTC — including a server/browser hydration split. it("never shifts a date-only string across a day boundary", () => { expect(fmtDate("2026-01-01")).toBe("01.01.2026"); expect(fmtDate("2026-12-31")).toBe("31.12.2026"); }); it("renders a timestamp in Vienna time regardless of the runtime zone", () => { // 23:30 UTC on 5 March is already 6 March in Vienna (UTC+1 before the // DST switch); the same instant is still 5 March in UTC. expect(fmtDate("2026-03-05T23:30:00Z")).toBe("06.03.2026"); }); it("returns an em dash for null/undefined/empty input", () => { expect(fmtDate(null)).toBe("–"); expect(fmtDate(undefined)).toBe("–"); expect(fmtDate("")).toBe("–"); }); it("returns an em dash for an invalid date string", () => { expect(fmtDate("not-a-date")).toBe("–"); }); }); describe("initials", () => { it("uppercases the first letter of each name", () => { expect(initials("maria", "gruber")).toBe("MG"); }); it("trims surrounding whitespace before taking the first letter", () => { expect(initials(" Anna", "Huber ")).toBe("AH"); }); }); describe("fmtAge", () => { it("computes age correctly when the birthday has already passed this year", () => { const today = new Date(); const birthDate = new Date(today.getFullYear() - 30, 0, 1); // Jan 1, definitely passed expect(fmtAge(birthDate)).toBe(30); }); it("subtracts one year when the birthday has not yet occurred this year", () => { const today = new Date(); const future = new Date(today.getFullYear() - 30, 11, 31); // Dec 31, likely not yet passed if (today.getMonth() === 11 && today.getDate() === 31) return; // skip on the one day this'd be flaky expect(fmtAge(future)).toBe(29); }); }); describe("tenure", () => { it("formats whole years and months since entry", () => { const start = new Date(2020, 0, 15); const end = new Date(2023, 2, 15); // exactly 3 years, 2 months later expect(tenure(start, end)).toBe("3 Jahre, 2 Monate"); }); it("uses singular Jahr/Monat for exactly 1", () => { const start = new Date(2020, 0, 1); const end = new Date(2021, 1, 1); // 1 year, 1 month expect(tenure(start, end)).toBe("1 Jahr, 1 Monat"); }); it("falls back to 'unter 1 Monat' for less than a month", () => { const start = new Date(2024, 0, 1); const end = new Date(2024, 0, 15); expect(tenure(start, end)).toBe("unter 1 Monat"); }); it("defaults the end date to today when no end date is given", () => { const start = new Date(); expect(tenure(start)).toBe("unter 1 Monat"); }); }); describe("daysBetweenIso", () => { it("computes whole days between two dates", () => { expect(daysBetweenIso("2026-01-01", "2026-01-11")).toBe(10); }); it("returns a negative number when the second date precedes the first", () => { expect(daysBetweenIso("2026-01-11", "2026-01-01")).toBe(-10); }); // Both ends are anchored at UTC midnight, so the DST switch in between // cannot turn 30 calendar days into 29.96 and round down. it("is exact across a DST transition", () => { expect(daysBetweenIso("2026-03-15", "2026-04-15")).toBe(31); expect(daysBetweenIso("2026-10-15", "2026-11-15")).toBe(31); }); }); describe("addDaysIso", () => { it("rolls over month and year boundaries", () => { expect(addDaysIso("2026-12-31", 1)).toBe("2027-01-01"); expect(addDaysIso("2026-01-31", 1)).toBe("2026-02-01"); }); it("handles a leap day", () => { expect(addDaysIso("2028-02-28", 1)).toBe("2028-02-29"); }); }); describe("yearsBetweenIso", () => { it("counts only completed years", () => { expect(yearsBetweenIso("1990-05-15", "2026-05-15")).toBe(36); expect(yearsBetweenIso("1990-05-15", "2026-05-14")).toBe(35); }); }); describe("toIsoDate", () => { it("passes a date-only string through untouched", () => { expect(toIsoDate("2026-01-01")).toBe("2026-01-01"); }); it("resolves a timestamp to the Vienna calendar day", () => { expect(toIsoDate("2026-03-05T23:30:00Z")).toBe("2026-03-06"); }); });