173 lines
5.8 KiB
JavaScript
173 lines
5.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { performance } from "node:perf_hooks";
|
|
import { fileURLToPath } from "node:url";
|
|
import ts from "typescript";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const sourcePath = path.join(root, "src", "lib", "todoVisibility.ts");
|
|
const source = readFileSync(sourcePath, "utf8");
|
|
const transpiled = ts.transpileModule(source, {
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.ES2022,
|
|
target: ts.ScriptTarget.ES2022,
|
|
},
|
|
}).outputText;
|
|
|
|
const moduleUrl = `data:text/javascript;base64,${Buffer.from(transpiled).toString("base64")}`;
|
|
const {
|
|
dismissedTodoKeyForScope,
|
|
scopedTodoBatchKey,
|
|
scopedTodoDismissalKey,
|
|
shouldOpenTodoPanelByDefault,
|
|
shouldShowTodoPanel,
|
|
todoBatchKey,
|
|
todoDismissalKey,
|
|
todoPanelScope,
|
|
} = await import(moduleUrl);
|
|
|
|
const completedTodos = [
|
|
{ content: "Inspect the report", status: "completed" },
|
|
{ content: "Ship the fix", status: "completed" },
|
|
];
|
|
const activeTodos = [
|
|
{ content: "Inspect the report", status: "in_progress" },
|
|
{ content: "Ship the fix", status: "pending" },
|
|
];
|
|
|
|
assert.equal(
|
|
shouldShowTodoPanel("todo-final", null, completedTodos),
|
|
true,
|
|
"a completed todo list stays visible in collapsed form until the user dismisses it",
|
|
);
|
|
assert.equal(
|
|
shouldShowTodoPanel("todo-active", null, [{ content: "Run tests", status: "in_progress" }]),
|
|
true,
|
|
"an active todo_write remains visible",
|
|
);
|
|
assert.equal(
|
|
shouldShowTodoPanel("todo-final", "todo-final", completedTodos),
|
|
false,
|
|
"a user dismissal still hides that exact todo list",
|
|
);
|
|
const completedKey = todoDismissalKey(completedTodos);
|
|
const dismissedBySession = new Set([scopedTodoDismissalKey("session:a", completedKey)]);
|
|
assert.equal(
|
|
shouldShowTodoPanel(completedKey, dismissedTodoKeyForScope("session:a", dismissedBySession, completedKey), completedTodos),
|
|
false,
|
|
"a completed todo dismissal hides the list in the session where it was closed",
|
|
);
|
|
assert.equal(
|
|
shouldShowTodoPanel(completedKey, dismissedTodoKeyForScope("session:b", dismissedBySession, completedKey), completedTodos),
|
|
true,
|
|
"a completed todo dismissal in one session does not hide another session's list",
|
|
);
|
|
dismissedBySession.add(scopedTodoDismissalKey("session:b", completedKey));
|
|
assert.equal(
|
|
shouldShowTodoPanel(completedKey, dismissedTodoKeyForScope("session:a", dismissedBySession, completedKey), completedTodos),
|
|
false,
|
|
"closing another session's todo does not forget the first session dismissal",
|
|
);
|
|
assert.equal(
|
|
todoPanelScope({
|
|
activeTabId: "tab-a",
|
|
activeTab: {
|
|
id: "tab-a",
|
|
scope: "project",
|
|
workspaceRoot: "/repo",
|
|
topicId: "topic-a",
|
|
sessionPath: "/sessions/a.jsonl",
|
|
},
|
|
}),
|
|
"session:/sessions/a.jsonl",
|
|
"a restored history session uses its session path as the todo panel scope",
|
|
);
|
|
assert.equal(
|
|
todoPanelScope({
|
|
activeTabId: "tab-b",
|
|
activeTab: {
|
|
id: "tab-a",
|
|
scope: "project",
|
|
workspaceRoot: "/repo",
|
|
topicId: "topic-a",
|
|
sessionPath: "/sessions/a.jsonl",
|
|
},
|
|
eventChannel: "desktop-events",
|
|
}),
|
|
"tab:tab-b",
|
|
"a stale active-tab fallback must not scope dismissal to the previous session",
|
|
);
|
|
assert.equal(
|
|
todoPanelScope({
|
|
activeTabId: "tab-c",
|
|
activeTab: {
|
|
id: "tab-c",
|
|
scope: "project",
|
|
workspaceRoot: "/repo",
|
|
topicId: "topic-a",
|
|
},
|
|
}),
|
|
"tab:tab-c",
|
|
"an unsaved topic session uses its tab id so sibling sessions do not share todo state",
|
|
);
|
|
assert.equal(shouldShowTodoPanel(null, null, completedTodos), false, "no canonical todo item means no panel");
|
|
assert.equal(shouldShowTodoPanel("todo-empty", null, []), false, "empty todo lists do not render a panel");
|
|
|
|
const activeKey = todoDismissalKey(activeTodos);
|
|
assert.equal(
|
|
activeKey,
|
|
todoDismissalKey(activeTodos.map((todo) => ({ ...todo }))),
|
|
"the same task list keeps a stable dismissal key across restored event ids",
|
|
);
|
|
assert.equal(
|
|
shouldShowTodoPanel(activeKey, activeKey, activeTodos),
|
|
true,
|
|
"an incomplete restored todo list must reappear even after a stale local dismissal",
|
|
);
|
|
assert.notEqual(
|
|
activeKey,
|
|
todoDismissalKey([{ ...activeTodos[0], status: "completed" }, { ...activeTodos[1], status: "in_progress" }]),
|
|
"real progress produces a fresh dismissal key",
|
|
);
|
|
assert.equal(
|
|
todoBatchKey(activeTodos),
|
|
todoBatchKey([{ ...activeTodos[0], status: "completed" }, { ...activeTodos[1], status: "in_progress" }]),
|
|
"status progress stays in the same todo batch",
|
|
);
|
|
assert.equal(
|
|
scopedTodoBatchKey("session:a", todoBatchKey(activeTodos)),
|
|
scopedTodoBatchKey("session:a", todoBatchKey([{ ...activeTodos[0], status: "completed" }, { ...activeTodos[1], status: "in_progress" }])),
|
|
"status progress keeps the same scoped open-state key",
|
|
);
|
|
assert.notEqual(
|
|
scopedTodoBatchKey("session:a", todoBatchKey(activeTodos)),
|
|
scopedTodoBatchKey("session:b", todoBatchKey(activeTodos)),
|
|
"the same task list in a different session gets isolated open state",
|
|
);
|
|
assert.notEqual(
|
|
todoBatchKey(activeTodos),
|
|
todoBatchKey([{ content: "Run a different task", status: "in_progress" }]),
|
|
"new task content creates a new todo batch",
|
|
);
|
|
assert.equal(
|
|
shouldOpenTodoPanelByDefault(),
|
|
false,
|
|
"todo batches collapse by default regardless of completion",
|
|
);
|
|
|
|
const iterations = 200_000;
|
|
const started = performance.now();
|
|
for (let i = 0; i < iterations; i += 1) {
|
|
if (shouldShowTodoPanel("todo-perf", "todo-perf", completedTodos)) {
|
|
throw new Error("unexpected visible todo panel during performance loop");
|
|
}
|
|
}
|
|
const elapsed = performance.now() - started;
|
|
const perCallUs = (elapsed * 1000) / iterations;
|
|
|
|
assert.ok(elapsed < 500, `todo visibility check is too slow: ${elapsed.toFixed(2)} ms`);
|
|
console.log(
|
|
`todo visibility checks: ${iterations} calls in ${elapsed.toFixed(2)} ms (${perCallUs.toFixed(3)} us/call)`,
|
|
);
|