- Detect recurrence via the 'recurring' tag maintained by the Repeating To-Dos plugin (v2 stores recurrence in userData, not the note body, so tag-presence is the correct signal — not body scanning) - DataAdapter gains getNoteTagTitles; lookup runs for to-dos only (notes can't recur), keeping extra API calls minimal - GtdEvent.isRecurring threaded through collector and payload - Webview shows ↻ to the right of any icon/glyph across all views: strip tiles, hover-card rows, month-grid chips, unscheduled chips - RECURRING_TAG constant leaves room for a future configurable tag name - README documents the pairing; SPEC roadmap marks Option A shipped - Version bumped to 0.2.0; 2 new tests (49 total)
195 lines
6.6 KiB
TypeScript
195 lines
6.6 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,
|
|
isRecurring: false,
|
|
...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"]);
|
|
});
|
|
});
|
|
|
|
describe("groupEvents — month-grid view", () => {
|
|
test("covers the whole current month padded to full weeks", () => {
|
|
const result = groupEvents([], "month-grid", TODAY);
|
|
const grid = result.grid!;
|
|
|
|
expect(grid.cells.length % 7).toBe(0);
|
|
expect(grid.weekdays).toHaveLength(7);
|
|
expect(grid.monthLabel).toContain("2026");
|
|
|
|
const inMonth = grid.cells.filter((c) => c.inMonth);
|
|
expect(inMonth).toHaveLength(30); // June 2026
|
|
expect(inMonth[0].dayNumber).toBe(1);
|
|
expect(inMonth[29].dayNumber).toBe(30);
|
|
});
|
|
|
|
test("flags today and buckets events into the right cells", () => {
|
|
const events = [
|
|
makeEvent({ id: "a", date: "2026-06-12" }),
|
|
makeEvent({ id: "b", date: "2026-06-12" }),
|
|
makeEvent({ id: "c", date: "2026-06-25" }),
|
|
];
|
|
const grid = groupEvents(events, "month-grid", TODAY).grid!;
|
|
|
|
const todayCell = grid.cells.find((c) => c.isToday)!;
|
|
expect(todayCell.dateISO).toBe("2026-06-12");
|
|
expect(todayCell.events.map((e) => e.id)).toEqual(["a", "b"]);
|
|
|
|
const cell25 = grid.cells.find((c) => c.dateISO === "2026-06-25")!;
|
|
expect(cell25.events.map((e) => e.id)).toEqual(["c"]);
|
|
});
|
|
|
|
test("events outside the displayed window are counted, not shown", () => {
|
|
const events = [
|
|
makeEvent({ id: "in", date: "2026-06-05" }),
|
|
makeEvent({ id: "out", date: "2026-09-01" }),
|
|
];
|
|
const grid = groupEvents(events, "month-grid", TODAY).grid!;
|
|
expect(grid.outsideCount).toBe(1);
|
|
const allShown = grid.cells.flatMap((c) => c.events.map((e) => e.id));
|
|
expect(allShown).toEqual(["in"]);
|
|
});
|
|
|
|
test("adjacent-month padding days can still hold events", () => {
|
|
// June 2026 starts on a Monday; the grid window starts Sunday May 31.
|
|
const events = [makeEvent({ id: "pad", date: "2026-05-31" })];
|
|
const grid = groupEvents(events, "month-grid", TODAY).grid!;
|
|
const padCell = grid.cells.find((c) => c.dateISO === "2026-05-31")!;
|
|
expect(padCell.inMonth).toBe(false);
|
|
expect(padCell.events.map((e) => e.id)).toEqual(["pad"]);
|
|
expect(grid.outsideCount).toBe(0);
|
|
});
|
|
|
|
test("unscheduled events still pass through in month-grid view", () => {
|
|
const events = [makeEvent({ id: "u" })];
|
|
const result = groupEvents(events, "month-grid", TODAY);
|
|
expect(result.unscheduled.map((e) => e.id)).toEqual(["u"]);
|
|
expect(result.groups).toHaveLength(0);
|
|
});
|
|
});
|