196 lines
5.6 KiB
TypeScript
196 lines
5.6 KiB
TypeScript
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();
|
||
});
|
||
});
|