import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { type EventFragment, getEventPig, getFutureOccurrences, getSingularEvents, organizeEventsByDate, organizeEventsInCalendar, sortSingularEvents, } from "./event.ts"; // "now" is tirsdag 2026-07-07 12:00 in Oslo beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-07-07T10:00:00Z")); }); afterEach(() => { vi.useRealTimers(); }); const makeEvent = (id: string, starts: string[]) => ({ id, occurrences: starts.map((start, i) => ({ id: `${id}-${i}`, start, end: null, })), }); describe("getSingularEvents", () => { it("flattens one copy per occurrence", () => { const a = makeEvent("a", ["2026-07-07T18:00:00Z", "2026-07-08T18:00:00Z"]); const b = makeEvent("b", ["2026-07-09T18:00:00Z"]); const singular = getSingularEvents([a, b]); expect(singular).toHaveLength(3); expect(singular.map((e) => e.occurrence.id)).toEqual(["a-0", "a-1", "b-0"]); expect(singular[0].id).toBe("a"); expect("occurrence" in a).toBe(false); }); }); describe("sortSingularEvents", () => { it("sorts by occurrence start", () => { const events = getSingularEvents([ makeEvent("late", ["2026-07-09T18:00:00Z"]), makeEvent("early", ["2026-07-07T18:00:00Z"]), makeEvent("mid", ["2026-07-08T18:00:00Z"]), ]); expect(sortSingularEvents(events).map((e) => e.id)).toEqual([ "early", "mid", "late", ]); }); }); describe("organizeEventsInCalendar", () => { it("nests by yearMonth/week/day and seeds empty days", () => { const events = getSingularEvents([ makeEvent("a", ["2026-07-07T18:00:00Z"]), ]); const calendar = organizeEventsInCalendar(events); expect(Object.keys(calendar)).toEqual(["2026-07"]); const weeks = Object.values(calendar["2026-07"]); expect(weeks).toHaveLength(1); const week = weeks[0]; // the whole week (man 6. – søn 12. juli) is pre-seeded expect(Object.keys(week)).toHaveLength(7); expect(week["2026-07-07"].map((e) => e.id)).toEqual(["a"]); expect(week["2026-07-06"]).toEqual([]); expect(week["2026-07-12"]).toEqual([]); }); it("splits a week across the year boundary under a shared week key", () => { const events = getSingularEvents([ makeEvent("romjul", ["2026-12-30T20:00:00Z"]), makeEvent("nyttaar", ["2027-01-02T20:00:00Z"]), ]); const calendar = organizeEventsInCalendar(events); // uke 53 (man 28.12. – søn 3.1.) spans both months; each yearMonth // bucket holds only its own days, under the same week number expect(Object.keys(calendar)).toEqual(["2026-12", "2027-01"]); expect(Object.keys(calendar["2026-12"])).toEqual(["53"]); expect(Object.keys(calendar["2027-01"])).toEqual(["53"]); expect(Object.keys(calendar["2026-12"]["53"])).toEqual([ "2026-12-28", "2026-12-29", "2026-12-30", "2026-12-31", ]); expect(Object.keys(calendar["2027-01"]["53"])).toEqual([ "2027-01-01", "2027-01-02", "2027-01-03", ]); expect(calendar["2026-12"]["53"]["2026-12-30"].map((e) => e.id)).toEqual([ "romjul", ]); expect(calendar["2027-01"]["53"]["2027-01-02"].map((e) => e.id)).toEqual([ "nyttaar", ]); }); it("seeds a full 7-day week through the DST fall-back", () => { // DST ends søndag 2026-10-25 (a 25-hour day in Oslo) const events = getSingularEvents([ makeEvent("a", ["2026-10-21T18:00:00Z"]), ]); const calendar = organizeEventsInCalendar(events); const weeks = Object.values(calendar["2026-10"]); expect(weeks).toHaveLength(1); expect(Object.keys(weeks[0])).toEqual([ "2026-10-19", "2026-10-20", "2026-10-21", "2026-10-22", "2026-10-23", "2026-10-24", "2026-10-25", ]); expect(weeks[0]["2026-10-21"].map((e) => e.id)).toEqual(["a"]); }); }); describe("organizeEventsByDate", () => { it("groups by Oslo date, sorted within the day", () => { const events = getSingularEvents([ makeEvent("kveld", ["2026-07-07T20:00:00Z"]), makeEvent("torsdag", ["2026-07-09T18:00:00Z"]), makeEvent("ettermiddag", ["2026-07-07T16:00:00Z"]), ]); const byDate = organizeEventsByDate(events); expect(Object.keys(byDate)).toEqual(["2026-07-07", "2026-07-09"]); expect(byDate["2026-07-07"].map((e) => e.id)).toEqual([ "ettermiddag", "kveld", ]); }); }); describe("getFutureOccurrences", () => { it("drops past days and sorts ascending", () => { const event = makeEvent("a", [ "2026-07-14T18:00:00Z", "2026-07-01T18:00:00Z", "2026-07-07T18:00:00Z", ]); expect(getFutureOccurrences(event).map((o) => o.start)).toEqual([ "2026-07-07T18:00:00Z", "2026-07-14T18:00:00Z", ]); }); }); describe("getEventPig", () => { const pigEvent = (pig: unknown, categoryPigs: string[] = []) => ({ pig, categories: categoryPigs.map((p) => ({ pig: p })), }) as unknown as EventFragment; it("returns an explicit valid pig", () => { expect(getEventPig(pigEvent("dance"))).toBe("dance"); }); it("returns null for empty, missing or unknown pig", () => { expect(getEventPig(pigEvent(""))).toBeNull(); expect(getEventPig(pigEvent(null))).toBeNull(); expect(getEventPig(pigEvent("notapig"))).toBeNull(); }); it("picks a valid category pig for automatic", () => { expect(getEventPig(pigEvent("automatic", ["music", "bogus"]))).toBe( "music" ); expect(["music", "drink"]).toContain( getEventPig(pigEvent("automatic", ["music", "drink"])) ); expect(getEventPig(pigEvent("automatic"))).toBeNull(); }); });