joplin-plugin-gtd-calendar/src/tests/Gtd/excludeNotebook.test.ts

179 lines
6.3 KiB
TypeScript

import collectEvents from "../../Gtd/collectEvents";
import collectKanban from "../../Gtd/collectKanban";
import collectMatrix from "../../Gtd/collectMatrix";
import resolveEffectiveFolderScope from "../../Gtd/effectiveFolderScope";
import parseCalendarConfig from "../../Gtd/parseCalendarConfig";
import parseKanbanConfig from "../../Gtd/parseKanbanConfig";
import parseMatrixConfig from "../../Gtd/parseMatrixConfig";
import { DataAdapter, RawFolder, RawNote } from "../../Gtd/types";
const folders: RawFolder[] = [
{ id: "work", parent_id: "", title: "Work" },
{ id: "ibm", parent_id: "work", title: "IBM" },
{ id: "ibm-child", parent_id: "ibm", title: "Projects" },
{ id: "archive-work", parent_id: "work", title: "Archive" },
{ id: "personal", parent_id: "", title: "Personal" },
{ id: "archive-personal", parent_id: "personal", title: "Archive" },
];
function note(id: string, parentId: string): RawNote {
return {
id,
title: id,
parent_id: parentId,
is_todo: 1,
todo_due: 0,
todo_completed: 0,
updated_time: 0,
body: "```gtd\n```",
};
}
function adapter(calls: string[]): DataAdapter {
return {
getFolders: async () => folders,
getNotesInFolder: async (folderId) => {
calls.push(folderId);
return [note(`note-${folderId}`, folderId)];
},
getNoteTagTitles: async () => [],
};
}
describe("exclude-notebook config parsing", () => {
test.each([
["calendar", parseCalendarConfig],
["kanban", parseKanbanConfig],
["matrix", parseMatrixConfig],
] as const)("%s accepts omitted, scalar, and list forms", (_name, parse) => {
expect(parse({}).excludeNotebooks).toEqual([]);
expect(parse({ "exclude-notebook": " IBM " }).excludeNotebooks).toEqual([
"IBM",
]);
const config = parse({ "exclude-notebook": [" IBM ", "Archive", "IBM"] });
expect(config.excludeNotebooks).toEqual(["IBM", "Archive", "IBM"]);
expect(config.warnings).toEqual([]);
});
test.each([parseCalendarConfig, parseKanbanConfig, parseMatrixConfig])(
"retains valid list entries and warns in input order",
(parse) => {
const config = parse({
"exclude-notebook": ["IBM", " ", { title: "Archive" }, ["Personal"]],
});
expect(config.excludeNotebooks).toEqual(["IBM"]);
expect(config.warnings).toEqual([
"Invalid exclude-notebook entry 2 (empty value ignored)",
"Invalid exclude-notebook entry 3 (expected a notebook name, path, or id)",
"Invalid exclude-notebook entry 4 (expected a notebook name, path, or id)",
]);
}
);
});
describe("effective notebook scope", () => {
test("subtracts complete, overlapping trees from scope: all", () => {
const result = resolveEffectiveFolderScope(
folders,
"work",
0,
true,
["IBM", "Work/IBM/Projects", "IBM"]
);
expect(result.folderIds).toEqual([
"work",
"archive-work",
"personal",
"archive-personal",
]);
expect([...result.excludedFolderIds].sort()).toEqual(["ibm", "ibm-child"]);
expect(result.warnings).toEqual([]);
});
test("resolves entries independently and warns without fallback", () => {
const result = resolveEffectiveFolderScope(
folders,
"work",
Infinity,
false,
["Archive", "Nope", "Personal"]
);
expect(result.folderIds).toEqual(["work", "ibm", "archive-work", "ibm-child"]);
expect(result.warnings).toEqual([
'exclude-notebook: "Archive" is ambiguous (2 notebooks share that name — qualify it as a Parent/Child path) (nothing excluded)',
'exclude-notebook: no notebook named "Nope" (nothing excluded)',
]);
});
test("supports paths, root exclusion, empty scope, and cycle safety", () => {
expect(
resolveEffectiveFolderScope(folders, "work", Infinity, false, ["Work"]).folderIds
).toEqual([]);
const cyclic: RawFolder[] = [
{ id: "a", parent_id: "b", title: "A" },
{ id: "b", parent_id: "a", title: "B" },
];
expect(
resolveEffectiveFolderScope(cyclic, "a", Infinity, false, ["b"]).folderIds
).toEqual([]);
});
test("accepts a 32-character folder id", () => {
const id = "0123456789abcdef0123456789abcdef";
const idFolders = [
...folders,
{ id, parent_id: "personal", title: "Secret" },
];
const result = resolveEffectiveFolderScope(
idFolders,
"work",
0,
true,
[id.toUpperCase()]
);
expect(result.folderIds).not.toContain(id);
expect(result.warnings).toEqual([]);
});
});
describe("collector exclusion integration", () => {
test.each([
["calendar", async (data: DataAdapter) => collectEvents(data, "host", "work", parseCalendarConfig({ scope: "all", notes: "all", todos: "all", "exclude-notebook": "IBM" }))],
["kanban", async (data: DataAdapter) => collectKanban(data, "host", "work", parseKanbanConfig({ scope: "all", todos: "all", "exclude-notebook": "IBM" }))],
["matrix", async (data: DataAdapter) => collectMatrix(data, "host", "work", parseMatrixConfig({ scope: "all", todos: "all", mode: "eisenhower", "exclude-notebook": "IBM" }))],
] as const)("%s never fetches excluded folders", async (_name, collect) => {
const calls: string[] = [];
const result = await collect(adapter(calls));
expect(calls).toEqual(["work", "archive-work", "personal", "archive-personal"]);
expect(result.scannedFolders).toBe(4);
expect(result.scannedNotes).toBe(4);
});
test.each([
["kanban", async (data: DataAdapter) => collectKanban(data, "host", "work", parseKanbanConfig({ scope: "children", group: "notebook", todos: "all", "exclude-notebook": "IBM" }))],
["matrix", async (data: DataAdapter) => collectMatrix(data, "host", "work", parseMatrixConfig({ scope: "children", group: "notebook", todos: "all", mode: "eisenhower", "exclude-notebook": "IBM" }))],
] as const)("%s grouped layout omits the excluded tree", async (_name, collect) => {
const calls: string[] = [];
const result = await collect(adapter(calls));
expect(calls).toEqual(["work", "archive-work"]);
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([
"work",
"archive-work",
]);
});
test("an entirely excluded calendar scope uses an empty result", async () => {
const calls: string[] = [];
const result = await collectEvents(
adapter(calls),
"host",
"work",
parseCalendarConfig({ "exclude-notebook": "Work" })
);
expect(calls).toEqual([]);
expect(result).toMatchObject({ events: [], scannedFolders: 0, scannedNotes: 0 });
});
});