import { validateAndMoveMatrixCard } from "../../Gtd/matrixMoveHandler"; import { DataAdapter, MutationAdapter, RawFolder, RawNote, RawTag } from "../../Gtd/types"; const folders: RawFolder[] = [ { id: "board", parent_id: "", title: "Board" }, { id: "child", parent_id: "board", title: "Child" }, { id: "outside", parent_id: "", title: "Outside" }, ]; function card(overrides: Partial = {}): RawNote { return { id: "card", title: "Card", parent_id: "board", is_todo: 0, todo_due: 0, todo_completed: 0, updated_time: 0, body: "```gtd\n```", ...overrides, }; } function setup(current: RawNote | null = card(), attached: RawTag[] = []) { const writes: string[] = []; const read: DataAdapter = { getFolders: async () => folders, getNotesInFolder: async () => [], getNoteTagTitles: async () => [], }; const mutation: MutationAdapter = { getNote: async () => current, getNoteTags: async () => attached.slice(), getAllTags: async () => [ { id: "urgent", title: "urgent" }, { id: "important", title: "important" }, ], createTag: async (title) => ({ id: title, title }), attachTag: async (tagId) => { writes.push(`attach:${tagId}`); }, detachTag: async (tagId) => { writes.push(`detach:${tagId}`); }, setTodoCompleted: async () => { throw new Error("completion write forbidden"); }, }; return { read, mutation, writes }; } function intent(overrides: Record = {}) { return { type: "moveMatrixCard", view: "matrix", hostNoteId: "host", cardId: "card", destination: "topLeft", rawConfig: "mode: eisenhower\neditable: yes\nnotes: all\ntodos: all", viewInstanceId: "view-1", ...overrides, }; } const host = { id: "host", parentId: "board" }; describe("validateAndMoveMatrixCard", () => { test("accepts a constrained editable Eisenhower move", async () => { const { read, mutation, writes } = setup(); await expect(validateAndMoveMatrixCard(intent(), host, read, mutation)).resolves.toEqual({ status: "success", viewInstanceId: "view-1", changed: true, }); expect(writes).toEqual(["attach:urgent", "attach:important"]); }); test.each([ {}, intent({ view: "kanban" }), intent({ destination: "done" }), intent({ cardId: "" }), intent({ rawConfig: { editable: "yes" } }), intent({ patch: { tags: ["urgent"] } }), ])("rejects malformed or expanded input without writes", async (request) => { const { read, mutation, writes } = setup(); expect((await validateAndMoveMatrixCard(request, host, read, mutation)).status).toBe("error"); expect(writes).toEqual([]); }); test.each([ ["", "error"], ["mode: eisenhower\neditable: no", "error"], ["mode: skeleton\neditable: yes", "error"], ["mode: eisenhower\neditable: yes\nurgent-tag: same\nimportant-tag: SAME", "error"], ["editable: [yes", "stale"], ] as const)("rejects invalid or non-editable config", async (rawConfig, status) => { const { read, mutation, writes } = setup(); expect((await validateAndMoveMatrixCard(intent({ rawConfig }), host, read, mutation)).status).toBe(status); expect(writes).toEqual([]); }); test("requires the selected host identity", async () => { for (const selected of [null, { id: "other", parentId: "board" }]) { const { read, mutation, writes } = setup(); expect((await validateAndMoveMatrixCard(intent(), selected, read, mutation)).status).toBe("stale"); expect(writes).toEqual([]); } }); test.each([ [card({ id: "host" }), [], "dashboard"], [card({ parent_id: "outside" }), [], "scope"], [card({ body: "plain" }), [], "gtd block"], [card({ is_todo: 1, todo_completed: 99 }), [], "complete"], [card(), [{ id: "done", title: "DONE" }], "complete"], ] as Array<[RawNote, RawTag[], string]>)("rejects stale item state", async (current, tags, message) => { const { read, mutation, writes } = setup(current, tags); const moveResult = await validateAndMoveMatrixCard(intent(), host, read, mutation); expect(moveResult).toMatchObject({ status: "stale" }); expect((moveResult as any).message).toContain(message); expect(writes).toEqual([]); }); test("resolves notebook and children scope from fresh folders", async () => { const { read, mutation, writes } = setup(card({ parent_id: "child" })); const moveResult = await validateAndMoveMatrixCard(intent({ rawConfig: "mode: eisenhower\neditable: yes\nnotebook: Board\nscope: children", }), host, read, mutation); expect(moveResult.status).toBe("success"); expect(writes).toEqual(["attach:urgent", "attach:important"]); }); test.each([ [card({ is_todo: 0 }), "mode: eisenhower\neditable: yes\nnotes: none\ntodos: all"], [card({ is_todo: 1, body: "plain" }), "mode: eisenhower\neditable: yes\nnotes: all\ntodos: gtd-only"], [card({ is_todo: 1 }), "mode: eisenhower\neditable: yes\nnotes: all\ntodos: none"], ])("rechecks filters and opt-in", async (current, rawConfig) => { const { read, mutation, writes } = setup(current); expect((await validateAndMoveMatrixCard(intent({ rawConfig }), host, read, mutation)).status).toBe("stale"); expect(writes).toEqual([]); }); test("returns a safe error and logs API diagnostics", async () => { const { read, mutation, writes } = setup(); const diagnostics: unknown[] = []; read.getFolders = async () => { throw new Error("private detail"); }; expect(await validateAndMoveMatrixCard(intent(), host, read, mutation, (e) => diagnostics.push(e))).toEqual({ status: "error", viewInstanceId: "view-1", message: "Could not apply the move. Refresh and try again.", }); expect(diagnostics).toHaveLength(1); expect(writes).toEqual([]); }); });