135 lines
5.5 KiB
TypeScript
135 lines
5.5 KiB
TypeScript
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> = {}): 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<string, unknown> = {}) {
|
|
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([]);
|
|
});
|
|
});
|