joplin-plugin-gtd-calendar/src/tests/Gtd/groupEvents.test.ts
Victor Wiebe 57eae2b7f8 Phase 3: calendar grid rendering with hover cards and Unscheduled section
- src/Gtd/groupEvents.ts: render-ready day/week/month grouping mirroring
  upstream semantics (empty groups across the span, month-coloured tiles,
  primary labels on month/year boundaries, current-period flag). Divergence:
  the span always includes today so the highlight is visible.
- Grouping runs in the main process; payload now carries calendar.groups +
  calendar.unscheduled. Webview is a pure DOM builder.
- Webview rebuilds upstream's .event-calendar / .group / .hover-card
  structure so existing CSS applies: tile per period, first event's
  icon/2-chars (+N for multiples), bg/fg colours, hover card listing
  events with type glyphs and strikethrough for completed todos.
- Drilldown: single-event tiles open the note directly; hover-card rows
  and Unscheduled chips are individually clickable.
- Stable pastel from note-id hash replaces upstream's per-render random
  colour for events without bg-colour.
- Unscheduled section: chip list below the grid (the GTD bucket).
- 9 new grouping tests (42 total)
2026-06-12 18:38:04 -04:00

134 lines
4.3 KiB
TypeScript

import groupEvents from "../../Gtd/groupEvents";
import { GtdEvent } from "../../Gtd/types";
function makeEvent(overrides: Partial<GtdEvent>): GtdEvent {
return {
id: "id",
title: "Event",
date: null,
isTodo: false,
completed: false,
bgColour: null,
fgColour: null,
icon: null,
text: null,
updatedTime: 0,
...overrides,
};
}
const TODAY = new Date(2026, 5, 12); // Jun 12 2026
describe("groupEvents — day view", () => {
test("one group per calendar day across the span, empty groups included", () => {
const events = [
makeEvent({ id: "a", date: "2026-06-10" }),
makeEvent({ id: "b", date: "2026-06-14" }),
];
const result = groupEvents(events, "day", TODAY);
// Jun 10 .. Jun 14 (today Jun 12 is inside the span)
expect(result.groups).toHaveLength(5);
expect(result.groups[0].events.map((e) => e.id)).toEqual(["a"]);
expect(result.groups[1].events).toHaveLength(0);
expect(result.groups[4].events.map((e) => e.id)).toEqual(["b"]);
});
test("span always includes today", () => {
const events = [makeEvent({ id: "past", date: "2026-06-08" })];
const result = groupEvents(events, "day", TODAY);
// Jun 8 .. Jun 12
expect(result.groups).toHaveLength(5);
expect(result.groups[4].isCurrent).toBe(true);
expect(result.groups[4].dateISO).toBe("2026-06-12");
});
test("first-of-month label is the month name and primary", () => {
const events = [
makeEvent({ id: "a", date: "2026-06-30" }),
makeEvent({ id: "b", date: "2026-07-02" }),
];
const result = groupEvents(events, "day", new Date(2026, 6, 1));
const firstOfJuly = result.groups.find(
(g) => g.dateISO === "2026-07-01"
)!;
expect(firstOfJuly.labelPrimary).toBe(true);
expect(firstOfJuly.label.toLowerCase()).toContain("jul");
expect(firstOfJuly.monthIndex).toBe(6);
});
test("multiple events on one day share a group, order preserved", () => {
const events = [
makeEvent({ id: "x", date: "2026-06-12" }),
makeEvent({ id: "y", date: "2026-06-12" }),
];
const result = groupEvents(events, "day", TODAY);
expect(result.groups).toHaveLength(1);
expect(result.groups[0].events.map((e) => e.id)).toEqual(["x", "y"]);
});
});
describe("groupEvents — week and month views", () => {
test("week view groups by calendar week with current-week flag", () => {
const events = [
makeEvent({ id: "a", date: "2026-06-01" }),
makeEvent({ id: "b", date: "2026-06-19" }),
];
const result = groupEvents(events, "week", TODAY);
// Weeks of May 31, Jun 7, Jun 14 → 3 groups
expect(result.groups).toHaveLength(3);
expect(result.groups[1].isCurrent).toBe(true); // week containing Jun 12
expect(result.groups[0].events.map((e) => e.id)).toEqual(["a"]);
expect(result.groups[2].events.map((e) => e.id)).toEqual(["b"]);
});
test("month view groups by calendar month", () => {
const events = [
makeEvent({ id: "a", date: "2026-04-20" }),
makeEvent({ id: "b", date: "2026-08-03" }),
];
const result = groupEvents(events, "month", TODAY);
// Apr, May, Jun, Jul, Aug
expect(result.groups).toHaveLength(5);
expect(result.groups[2].isCurrent).toBe(true); // June
expect(result.groups[0].events.map((e) => e.id)).toEqual(["a"]);
expect(result.groups[4].events.map((e) => e.id)).toEqual(["b"]);
});
test("january month label is the year and primary", () => {
const events = [
makeEvent({ id: "a", date: "2025-12-15" }),
makeEvent({ id: "b", date: "2026-01-10" }),
];
const result = groupEvents(events, "month", new Date(2026, 0, 5));
const january = result.groups.find((g) => g.monthIndex === 0)!;
expect(january.label).toBe("2026");
expect(january.labelPrimary).toBe(true);
});
});
describe("groupEvents — unscheduled", () => {
test("dateless events pass through in order; no groups without scheduled events", () => {
const events = [
makeEvent({ id: "u1" }),
makeEvent({ id: "u2" }),
];
const result = groupEvents(events, "day", TODAY);
expect(result.groups).toHaveLength(0);
expect(result.unscheduled.map((e) => e.id)).toEqual(["u1", "u2"]);
});
test("mixed: scheduled grouped, dateless set aside", () => {
const events = [
makeEvent({ id: "s", date: "2026-06-12" }),
makeEvent({ id: "u" }),
];
const result = groupEvents(events, "day", TODAY);
expect(result.groups[0].events.map((e) => e.id)).toEqual(["s"]);
expect(result.unscheduled.map((e) => e.id)).toEqual(["u"]);
});
});