import parseMatrixConfig from "../../Gtd/parseMatrixConfig"; import collectMatrix from "../../Gtd/collectMatrix"; import matrixLabels from "../../Gtd/matrixLabels"; import { DataAdapter, RawFolder, RawNote } from "../../Gtd/types"; // --------------------------------------------------------------------------- // parseMatrixConfig // --------------------------------------------------------------------------- describe("parseMatrixConfig", () => { test("both modes use constructive labels while preserving distinct axes", () => { expect(matrixLabels("eisenhower")).toEqual({ columns: ["Urgent", "Not urgent"], rows: ["Important", "Not important"], quadrants: { topLeft: "Do Next", topRight: "Scheduled", bottomLeft: "On Deck", bottomRight: "Backlog", }, }); expect(matrixLabels("skeleton")).toEqual({ columns: ["Priority", "Current / backlog"], rows: ["Now", "Next / later"], quadrants: { topLeft: "Urgent", topRight: "In Progress", bottomLeft: "On Deck", bottomRight: "Backlog", }, }); }); test("applies SPEC defaults", () => { const c = parseMatrixConfig({}); expect(c).toMatchObject({ mode: "skeleton", title: null, scopeDepth: 0, notes: "all", todos: "gtd-only", sortType: "due-date", sort: "asc", urgentTag: "urgent", importantTag: "important", onDeckTag: "on-deck", inProgressTag: "in-progress", doneTag: "done", cardDetail: "hover", pageSize: 10, editable: false, }); expect(c.warnings).toHaveLength(0); }); test("editable is strict and requires distinct destination tags", () => { expect(parseMatrixConfig({ mode: "eisenhower", editable: " YES " }).editable).toBe(true); expect(parseMatrixConfig({ mode: "eisenhower", editable: "No" }).editable).toBe(false); for (const value of ["", "true", true, null, ["yes"], { value: "yes" }]) { const config = parseMatrixConfig({ mode: "eisenhower", editable: value }); expect(config.editable).toBe(false); expect(config.warnings).toContain(`Invalid editable "${String(value)}" (using "no")`); } expect(parseMatrixConfig({ editable: "yes" }).editable).toBe(true); const equalAxes = parseMatrixConfig({ mode: "eisenhower", editable: "yes", "urgent-tag": "Axis", "important-tag": "axis", }); expect(equalAxes.editable).toBe(false); expect(equalAxes.warnings).toContain( "editable Eisenhower axis tags must be distinct (editing disabled)" ); const equalWorkflow = parseMatrixConfig({ editable: "yes", "on-deck-tag": "URGENT" }); expect(equalWorkflow.editable).toBe(false); }); test("parses notes independently and rejects invalid or singular forms", () => { expect(parseMatrixConfig({ notes: " NONE " }).notes).toBe("none"); for (const value of ["", "gtd-only", null, ["all"], { mode: "all" }]) { const config = parseMatrixConfig({ notes: value }); expect(config.notes).toBe("all"); expect(config.warnings[0]).toContain("Invalid notes"); } const singular = parseMatrixConfig({ note: "none" }); expect(singular.notes).toBe("all"); expect(singular.warnings[0]).toContain("Unknown option"); }); test("parses done-tag and warns when it matches in-progress-tag", () => { expect(parseMatrixConfig({ "done-tag": " Finished " }).doneTag).toBe( "finished" ); expect(parseMatrixConfig({ "done-tag": "" }).doneTag).toBe("done"); const same = parseMatrixConfig({ "done-tag": "Active", "in-progress-tag": "active", }); expect(same.warnings).toEqual([ 'done-tag and in-progress-tag are both "active" — done takes priority', ]); }); test.each([ [1, 1], [10, 10], [25, 25], ["12", 12], ])("page-size accepts positive integers (%p)", (input, expected) => { const c = parseMatrixConfig({ "page-size": input }); expect(c.pageSize).toBe(expected); expect(c.warnings).toHaveLength(0); }); test.each([0, -1, 1.5, "many", "", NaN, Infinity, true])( "invalid page-size %p warns and falls back to 10", (input) => { const c = parseMatrixConfig({ "page-size": input }); expect(c.pageSize).toBe(10); expect(c.warnings).toEqual([`Invalid page-size "${input}" (using 10)`]); } ); test("mode parses and warns on invalid", () => { expect(parseMatrixConfig({ mode: "eisenhower" }).mode).toBe("eisenhower"); const bad = parseMatrixConfig({ mode: "wiebe" }); expect(bad.mode).toBe("skeleton"); expect(bad.warnings.some((w) => w.includes("mode"))).toBe(true); }); test("parses custom axis tags, lowercased", () => { const c = parseMatrixConfig({ "urgent-tag": "FIRE", "important-tag": "Matters", }); expect(c.urgentTag).toBe("fire"); expect(c.importantTag).toBe("matters"); }); test("warns when both axis tags are identical", () => { const c = parseMatrixConfig({ "urgent-tag": "now", "important-tag": "NOW", }); expect(c.warnings.some((w) => w.includes("will not separate"))).toBe(true); }); test("invalid values warn and fall back", () => { const c = parseMatrixConfig({ todos: "some", "sort-type": "priority", "card-detail": "popup", mystery: 1, }); expect(c.todos).toBe("gtd-only"); expect(c.sortType).toBe("due-date"); expect(c.cardDetail).toBe("hover"); expect(c.warnings.length).toBeGreaterThanOrEqual(4); }); }); // --------------------------------------------------------------------------- // collectMatrix // --------------------------------------------------------------------------- function makeNote(overrides: Partial): RawNote { return { id: "id", title: "Card", parent_id: "board", is_todo: 1, todo_due: 0, todo_completed: 0, updated_time: 0, body: "```gtd\n```", ...overrides, }; } function makeAdapter( folders: RawFolder[], notes: RawNote[], tagsByNote: Record = {} ): DataAdapter { return { getFolders: async () => folders, getNotesInFolder: async (folderId: string) => notes.filter((n) => n.parent_id === folderId), getNoteTagTitles: async (noteId: string) => tagsByNote[noteId] || [], }; } const folders: RawFolder[] = [{ id: "board", parent_id: "root" }]; describe("collectMatrix — quadrant bucketing", () => { test("all four Eisenhower quadrants route correctly; untagged lands in Backlog", async () => { const notes = [ makeNote({ id: "both", is_todo: 0, title: "Crisis", todo_completed: 123456, }), makeNote({ id: "imp", is_todo: 0, title: "Strategy" }), makeNote({ id: "urg", is_todo: 0, title: "Interruption" }), makeNote({ id: "none", is_todo: 0, title: "Timewaster" }), ]; const result = await collectMatrix( makeAdapter(folders, notes, { both: ["urgent", "important"], imp: ["important"], urg: ["urgent"], }), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower" }) ); expect(result.board.topLeft.map((c) => c.id)).toEqual(["both"]); expect(result.board.topRight.map((c) => c.id)).toEqual(["imp"]); expect(result.board.bottomLeft.map((c) => c.id)).toEqual(["urg"]); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["none"]); expect(result.cardCount).toBe(4); }); test("completed to-dos are excluded entirely", async () => { const notes = [ makeNote({ id: "open", title: "Open" }), makeNote({ id: "done", title: "Done", todo_completed: 123456 }), ]; const result = await collectMatrix( makeAdapter(folders, notes, { done: ["urgent", "important"] }), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower" }) ); const allIds = [ ...result.board.topLeft, ...result.board.topRight, ...result.board.bottomLeft, ...result.board.bottomRight, ].map((c) => c.id); expect(allIds).toEqual(["open"]); }); test("custom axis tags are honoured", async () => { const notes = [makeNote({ id: "x", title: "Hot" })]; const result = await collectMatrix( makeAdapter(folders, notes, { x: ["fire", "matters"] }), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower", "urgent-tag": "fire", "important-tag": "matters", }) ); expect(result.board.topLeft.map((c) => c.id)).toEqual(["x"]); }); test("includes opted-in notes while excluding plain and host notes", async () => { const notes = [ makeNote({ id: "matrix-note", is_todo: 0, title: "The matrix note", }), makeNote({ id: "plain-note", is_todo: 0, title: "A plain note", body: "No shortcode here", }), makeNote({ id: "gtd-note", is_todo: 0, title: "An opted-in note" }), makeNote({ id: "todo", title: "A todo" }), ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower" }) ); expect(result.cardCount).toBe(2); expect(result.board.bottomRight.map((c) => c.id)).toEqual([ "gtd-note", "todo", ]); expect(result.board.bottomRight[0]).toMatchObject({ isTodo: false, completed: false, isRecurring: false, }); }); test("gtd-only requires a gtd block", async () => { const notes = [ makeNote({ id: "in", body: "```gtd\n```" }), makeNote({ id: "out", body: "no block here" }), ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ mode: "eisenhower" }) // todos: gtd-only default ); expect(result.cardCount).toBe(1); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["in"]); }); test("todos: none excludes to-dos without disabling opted-in notes", async () => { const notes = [ makeNote({ id: "gtd-note", is_todo: 0 }), makeNote({ id: "gtd-todo" }), ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "none", mode: "eisenhower" }) ); expect(result.cardCount).toBe(1); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["gtd-note"]); }); test("requests tags only after an item is eligible", async () => { const notes = [ makeNote({ id: "plain-note", is_todo: 0, body: "No shortcode here", }), makeNote({ id: "plain-todo", body: "No shortcode here", }), makeNote({ id: "gtd-note", is_todo: 0 }), makeNote({ id: "gtd-todo" }), makeNote({ id: "done-todo", todo_completed: 123456 }), ]; const adapter = makeAdapter(folders, notes); const getNoteTagTitles = jest.fn( async (_noteId: string): Promise => [] ); adapter.getNoteTagTitles = getNoteTagTitles; await collectMatrix( adapter, "matrix-note", "board", parseMatrixConfig({ todos: "gtd-only", mode: "eisenhower" }) ); expect(getNoteTagTitles.mock.calls.map(([id]) => id)).toEqual([ "gtd-note", "gtd-todo", ]); }); test("due-date sort within a quadrant, dateless last", async () => { const notes = [ makeNote({ id: "later", todo_due: new Date(2026, 6, 20).getTime() }), makeNote({ id: "soon", todo_due: new Date(2026, 6, 1).getTime() }), makeNote({ id: "nodate" }), ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower" }) ); expect(result.board.bottomRight.map((c) => c.id)).toEqual([ "soon", "later", "nodate", ]); }); test("recurring tag flags cards", async () => { const notes = [makeNote({ id: "r" })]; const result = await collectMatrix( makeAdapter(folders, notes, { r: ["recurring", "urgent"] }), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower" }) ); expect(result.board.bottomLeft[0].isRecurring).toBe(true); }); }); describe("collectMatrix — mixed note/to-do contracts", () => { test.each(["skeleton", "eisenhower"] as const)( "supports project-only and task-only %s matrices", async (mode) => { const notes = [ makeNote({ id: "project", is_todo: 0 }), makeNote({ id: "plain-note", is_todo: 0, body: "No block" }), makeNote({ id: "task" }), ]; const cases: Array<[any, string[]]> = [ [{ notes: "all", todos: "none", mode }, ["project"]], [{ notes: "none", todos: "all", mode }, ["task"]], [{ notes: "none", todos: "none", mode }, []], ]; for (const [raw, expected] of cases) { const result = await collectMatrix( makeAdapter(folders, notes), "host", "board", parseMatrixConfig(raw) ); const ids = [ ...result.board.topLeft, ...result.board.topRight, ...result.board.bottomLeft, ...result.board.bottomRight, ].map((card) => card.id); expect(ids).toEqual(expected); expect(result.cardCount).toBe(expected.length); } } ); test("filtered notes do not request tags", async () => { const tagCalls: string[] = []; const adapter = makeAdapter(folders, [ makeNote({ id: "project", is_todo: 0 }), ]); adapter.getNoteTagTitles = async (noteId: string) => { tagCalls.push(noteId); return []; }; const result = await collectMatrix( adapter, "host", "board", parseMatrixConfig({ notes: "none", todos: "none" }) ); expect(tagCalls).toEqual([]); expect(result.cardCount).toBe(0); expect(result.scannedNotes).toBe(1); }); test.each(["skeleton", "eisenhower"] as const)( "excludes done notes before %s bucketing while ignoring done on open to-dos", async (mode) => { const notes = [ makeNote({ id: "done-note", is_todo: 0 }), makeNote({ id: "open-todo" }), ]; const result = await collectMatrix( makeAdapter(folders, notes, { "done-note": ["done", "in-progress", "urgent", "important"], "open-todo": ["done"], }), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode }) ); expect(result.cardCount).toBe(1); const ids = [ ...result.board.topLeft, ...result.board.topRight, ...result.board.bottomLeft, ...result.board.bottomRight, ].map((card) => card.id); expect(ids).toEqual(["open-todo"]); } ); test("honours a custom done tag for note exclusion", async () => { const result = await collectMatrix( makeAdapter(folders, [makeNote({ id: "note", is_todo: 0 })], { note: ["finished"], }), "matrix-note", "board", parseMatrixConfig({ "done-tag": "finished" }) ); expect(result.cardCount).toBe(0); }); test("handles empty, malformed, absent, and host note blocks", async () => { const notes = [ makeNote({ id: "empty-note", is_todo: 0 }), makeNote({ id: "broken-note", is_todo: 0, title: "Broken note", body: "```gtd\ntitle: [invalid\n```", }), makeNote({ id: "plain-note", is_todo: 0, body: "No shortcode here", }), makeNote({ id: "matrix-note", is_todo: 0 }), ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "none", mode: "eisenhower" }) ); expect(result.board.bottomRight.map((card) => card.id)).toEqual([ "empty-note", "broken-note", ]); expect(result.cardCount).toBe(2); expect(result.warnings).toHaveLength(1); expect(result.warnings[0]).toContain('"Broken note": gtd block problem'); }); test.each([ ["due-date", ["note-alpha", "todo-bravo", "note-zulu"]], ["title", ["note-alpha", "todo-bravo", "note-zulu"]], ["modified-date", ["note-zulu", "todo-bravo", "note-alpha"]], ] as const)("sorts mixed cards by %s", async (sortType, expectedIds) => { const notes = [ makeNote({ id: "todo-bravo", title: "Bravo", todo_due: new Date(2026, 6, 20).getTime(), updated_time: 20, }), makeNote({ id: "note-alpha", is_todo: 0, title: "Alpha", body: "```gtd\ndate: 2026-07-01\n```", updated_time: 30, }), makeNote({ id: "note-zulu", is_todo: 0, title: "Zulu", updated_time: 10, }), ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "all", mode: "eisenhower", "sort-type": sortType, "page-size": 1, }) ); expect(result.board.bottomRight.map((card) => card.id)).toEqual( expectedIds ); expect(result.cardCount).toBe(3); expect(result.board.bottomRight).toHaveLength(3); }); }); describe("collectMatrix — Skeleton mode", () => { const NOW = new Date(2026, 5, 15); // Jun 15 2026 function iso(y: number, m: number, d: number): number { return new Date(y, m - 1, d, 12).getTime(); } test("the four tag destinations: Urgent / In Progress / On Deck / Backlog", async () => { const notes = [ // active + due within window -> Do Next makeNote({ id: "donext", is_todo: 0, body: "```gtd\ndate: 2026-06-17\n```", }), // active + due later -> Scheduled makeNote({ id: "sched", is_todo: 0, body: "```gtd\ndate: 2026-06-25\n```", }), // not active + due soon -> On Deck makeNote({ id: "ondeck", is_todo: 0, body: "```gtd\ndate: 2026-06-16\n```", }), // not active, no date -> Backlog makeNote({ id: "backlog", is_todo: 0 }), ]; const result = await collectMatrix( makeAdapter(folders, notes, { donext: ["urgent"], sched: ["in-progress"], ondeck: ["on-deck"], }), "matrix-note", "board", parseMatrixConfig({ todos: "all" }), NOW ); expect(result.board.topLeft.map((c) => c.id)).toEqual(["donext"]); expect(result.board.topRight.map((c) => c.id)).toEqual(["sched"]); expect(result.board.bottomLeft.map((c) => c.id)).toEqual(["ondeck"]); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["backlog"]); }); test("urgent takes precedence over overlapping workflow tags", async () => { const notes = [makeNote({ id: "hot", is_todo: 0 })]; const result = await collectMatrix( makeAdapter(folders, notes, { hot: ["on-deck", "in-progress", "urgent"] }), "matrix-note", "board", parseMatrixConfig({ todos: "all" }), NOW ); expect(result.board.topLeft.map((c) => c.id)).toEqual(["hot"]); }); test("urgent tag alone routes to Urgent", async () => { const notes = [makeNote({ id: "flag", is_todo: 0 })]; const result = await collectMatrix( makeAdapter(folders, notes, { flag: ["urgent"] }), "matrix-note", "board", parseMatrixConfig({ todos: "all" }), NOW ); expect(result.board.topLeft.map((c) => c.id)).toEqual(["flag"]); }); test("native due dates do not affect Skeleton routing", async () => { const notes = [makeNote({ id: "late", todo_due: iso(2026, 6, 1) })]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "all" }), NOW ); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["late"]); }); test("in-progress work routes to In Progress", async () => { const notes = [makeNote({ id: "wip" })]; const result = await collectMatrix( makeAdapter(folders, notes, { wip: ["in-progress"] }), "matrix-note", "board", parseMatrixConfig({ todos: "all" }), NOW ); expect(result.board.topRight.map((c) => c.id)).toEqual(["wip"]); }); test("future dates do not affect Skeleton routing", async () => { const notes = [ makeNote({ id: "edge", todo_due: iso(2026, 6, 18) }), // +3 days makeNote({ id: "past-edge", todo_due: iso(2026, 6, 19) }), // +4 ]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({ todos: "all" }), NOW ); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["edge", "past-edge"]); }); test("gtd date override does not affect Skeleton routing", async () => { const body = "```gtd\ndate: 2026-06-16\n```"; const notes = [makeNote({ id: "g", body: body })]; const result = await collectMatrix( makeAdapter(folders, notes), "matrix-note", "board", parseMatrixConfig({}), NOW ); expect(result.board.bottomRight.map((c) => c.id)).toEqual(["g"]); }); }); describe("collectMatrix — notebook groups", () => { test("omits groups emptied by note filtering while retaining descendants", async () => { const tree: RawFolder[] = [ { id: "root", parent_id: "", title: "Root" }, { id: "child", parent_id: "root", title: "Child" }, ]; const notes = [ makeNote({ id: "broken-project", parent_id: "root", is_todo: 0, body: "```gtd\ntitle: [invalid\n```", }), makeNote({ id: "child-task", parent_id: "child" }), ]; const result = await collectMatrix( makeAdapter(tree, notes), "host", "root", parseMatrixConfig({ scope: "children", group: "notebook", notes: "none", todos: "all", }) ); expect(result.layout.kind).toBe("notebooks"); if (result.layout.kind !== "notebooks") throw new Error("grouped expected"); expect(result.layout.groups.map((group) => group.folderId)).toEqual([ "child", ]); expect(result.layout.groups[0].warnings).toEqual([]); expect(result.cardCount).toBe(1); expect(result.scannedNotes).toBe(2); }); test.each(["skeleton", "eisenhower"] as const)( "groups mixed cards exactly once in %s mode", async (mode) => { const tree: RawFolder[] = [ { id: "root-board", parent_id: "", title: "Projects" }, { id: "empty", parent_id: "root-board", title: "Empty" }, { id: "child", parent_id: "root-board", title: "Child" }, ]; const notes = [ makeNote({ id: "host", parent_id: "root-board", is_todo: 0 }), makeNote({ id: "root-note", parent_id: "root-board", is_todo: 0 }), makeNote({ id: "child-todo", parent_id: "child" }), makeNote({ id: "child-plain", parent_id: "child", is_todo: 0, title: "Alpha", }), makeNote({ id: "broken", parent_id: "child", is_todo: 0, title: "Broken", body: "```gtd\ntitle: [invalid\n```", }), makeNote({ id: "done", parent_id: "empty", todo_completed: 1 }), ]; const result = await collectMatrix( makeAdapter(tree, notes, { "root-note": ["important", "in-progress"], "child-todo": ["urgent"], }), "host", "root-board", parseMatrixConfig({ scope: "children", group: "notebook", todos: "all", mode, "sort-type": "title", }) ); expect(result.layout.kind).toBe("notebooks"); if (result.layout.kind !== "notebooks") throw new Error("grouped layout expected"); expect(result.layout.groups.map((group) => group.folderId)).toEqual([ "root-board", "child", ]); expect(result.layout.groups[0].cardCount).toBe(1); expect(result.layout.groups[1].cardCount).toBe(3); expect(result.layout.groups[1].warnings).toHaveLength(1); expect( result.layout.groups[1].board.bottomRight.map((card) => card.id) ).toEqual(["child-plain", "broken"]); const ids = result.layout.groups.flatMap((group) => [ ...group.board.topLeft, ...group.board.topRight, ...group.board.bottomLeft, ...group.board.bottomRight, ].map((card) => card.id) ); expect(ids.sort()).toEqual([ "broken", "child-plain", "child-todo", "root-note", ]); expect(result.board.bottomRight).toHaveLength(0); expect(result.scannedFolders).toBe(3); expect(result.scannedNotes).toBe(6); expect(result.cardCount).toBe(4); } ); test("retains the single layout when notebook grouping is off", async () => { const result = await collectMatrix( makeAdapter(folders, [makeNote({ id: "one" })]), "host", "board", parseMatrixConfig({ scope: "children", todos: "all" }) ); expect(result.layout).toMatchObject({ kind: "single", board: result.board, }); }); test.each(["skeleton", "eisenhower"] as const)( "keeps all four %s quadrants local to the owning notebook", async (mode) => { const tree: RawFolder[] = [ { id: "root-board", parent_id: "", title: "Projects" }, { id: "child", parent_id: "root-board", title: "Child" }, ]; const notes = [ makeNote({ id: "root-left", parent_id: "root-board", is_todo: 0 }), makeNote({ id: "tl", parent_id: "child", is_todo: 0 }), makeNote({ id: "tr", parent_id: "child", is_todo: 0 }), makeNote({ id: "bl", parent_id: "child", is_todo: 0 }), makeNote({ id: "br", parent_id: "child", is_todo: 0 }), ]; const tags = mode === "skeleton" ? { "root-left": ["on-deck"], tl: ["urgent"], tr: ["in-progress"], bl: ["on-deck"], } : { "root-left": ["urgent"], tl: ["urgent", "important"], tr: ["important"], bl: ["urgent"], }; const result = await collectMatrix( makeAdapter(tree, notes, tags), "host", "root-board", parseMatrixConfig({ scope: "children", group: "notebook", mode }) ); expect(result.layout.kind).toBe("notebooks"); if (result.layout.kind !== "notebooks") throw new Error("grouped layout expected"); const root = result.layout.groups[0].board; const child = result.layout.groups[1].board; expect(root.bottomLeft.map((card) => card.id)).toEqual(["root-left"]); expect(child.topLeft.map((card) => card.id)).toEqual(["tl"]); expect(child.topRight.map((card) => card.id)).toEqual(["tr"]); expect(child.bottomLeft.map((card) => card.id)).toEqual(["bl"]); expect(child.bottomRight.map((card) => card.id)).toEqual(["br"]); } ); test.each([ ["this-folder", "this-folder", 1], ["numeric depth", 1, 2], ["scope all", "all", 4], ] as const)( "retains one aggregate matrix for %s", async (_label, scope, count) => { const tree: RawFolder[] = [ { id: "board", parent_id: "", title: "Board" }, { id: "child", parent_id: "board", title: "Child" }, { id: "grand", parent_id: "child", title: "Grand" }, { id: "other", parent_id: "", title: "Other" }, ]; const notes = tree.map((folder) => makeNote({ id: "card-" + folder.id, parent_id: folder.id }) ); const result = await collectMatrix( makeAdapter(tree, notes), "host", "board", parseMatrixConfig({ scope, todos: "all", mode: "eisenhower" }) ); expect(result.layout.kind).toBe("single"); expect(result.cardCount).toBe(count); if (result.layout.kind !== "single") throw new Error("single layout expected"); expect(result.layout.board.bottomRight).toHaveLength(count); } ); });