import assert from "node:assert/strict"; import test from "node:test"; import { createCompletedTaskAutoClear, TODO_AUTO_CLEAR_DELAY_MS } from "../src/auto-clear.ts"; import { TaskState } from "../src/state.ts"; function clearState(state: TaskState, onClear: () => void): boolean { state.set("replace", [], false); onClear(); return true; } test("clears the task tree ten seconds after every task remains done", (context) => { context.mock.timers.enable({ apis: ["setTimeout"] }); const state = new TaskState(); state.add([{ name: "Done", status: "done" }]); let clearCount = 0; const autoClear = createCompletedTaskAutoClear(state, () => clearState(state, () => { clearCount += 1; })); autoClear.sync(); context.mock.timers.tick(TODO_AUTO_CLEAR_DELAY_MS - 1); assert.equal(state.stats().total, 1); context.mock.timers.tick(1); assert.deepEqual(state.snapshot(), { tasks: [], nextId: 1 }); assert.equal(clearCount, 1); }); test("cancels a pending clear when unfinished work appears", (context) => { context.mock.timers.enable({ apis: ["setTimeout"] }); const state = new TaskState(); state.add([{ name: "Done", status: "done" }]); let clearCount = 0; const autoClear = createCompletedTaskAutoClear(state, () => clearState(state, () => { clearCount += 1; })); autoClear.sync(); state.add([{ name: "New work" }]); autoClear.sync(); context.mock.timers.tick(TODO_AUTO_CLEAR_DELAY_MS); assert.equal(state.stats().total, 2); assert.equal(clearCount, 0); }); test("preserves the deadline for unrelated refreshes", (context) => { context.mock.timers.enable({ apis: ["setTimeout"] }); const state = new TaskState(); state.add([{ name: "Done", status: "done" }]); const autoClear = createCompletedTaskAutoClear(state, () => clearState(state, () => {})); autoClear.sync(); context.mock.timers.tick(9_000); state.update([{ id: 1, name: "Renamed" }]); autoClear.sync(); context.mock.timers.tick(1_000); assert.equal(state.stats().total, 0); }); test("restarts the delay for changed or restored completed trees", (context) => { context.mock.timers.enable({ apis: ["setTimeout"] }); const state = new TaskState(); state.add([{ name: "Done", status: "done" }]); const autoClear = createCompletedTaskAutoClear(state, () => clearState(state, () => {})); autoClear.sync(); context.mock.timers.tick(9_000); state.add([{ name: "Also done", status: "done" }]); autoClear.sync(); context.mock.timers.tick(1_000); assert.equal(state.stats().total, 2); context.mock.timers.tick(8_000); autoClear.sync(true); context.mock.timers.tick(2_000); assert.equal(state.stats().total, 2); context.mock.timers.tick(8_000); assert.equal(state.stats().total, 0); }); test("retries after a clear transaction fails and supports shutdown cancellation", (context) => { context.mock.timers.enable({ apis: ["setTimeout"] }); const state = new TaskState(); state.add([{ name: "Done", status: "done" }]); let attempts = 0; const autoClear = createCompletedTaskAutoClear(state, () => { attempts += 1; if (attempts === 1) return false; return clearState(state, () => {}); }); autoClear.sync(); context.mock.timers.tick(TODO_AUTO_CLEAR_DELAY_MS); assert.equal(state.stats().total, 1); context.mock.timers.tick(TODO_AUTO_CLEAR_DELAY_MS); assert.equal(state.stats().total, 0); state.add([{ name: "Done again", status: "done" }]); autoClear.sync(); autoClear.cancel(); context.mock.timers.tick(TODO_AUTO_CLEAR_DELAY_MS); assert.equal(state.stats().total, 1); });