Collapse completed todo subtrees in widget

This commit is contained in:
lesetong
2026-08-24 10:23:43 +08:00
parent cd74f91e2a
commit 98f3904189
3 changed files with 122 additions and 11 deletions
+51
View File
@@ -9,6 +9,7 @@ import {
renderTaskLine,
renderToolResult,
renderTree,
renderWidgetTree,
snapshotStats,
} from "../src/ui.ts";
import { updateTaskWidget } from "../src/widget.ts";
@@ -67,6 +68,56 @@ test("renders lightweight connectors without a detached root continuation", () =
]);
});
test("collapses completed widget subtrees while keeping list trees expanded", () => {
const state = new TaskState(() => 0);
state.add([
{
name: "Release",
children: [
{
name: "Completed branch",
children: [
{ name: "Done one", status: "done" },
{ name: "Done two", status: "done" },
],
},
{ name: "Open branch", children: [{ name: "Open leaf" }] },
],
},
{
name: "Pending parent",
children: [{ name: "Finished child", status: "done" }],
},
]);
const widgetLines = renderWidgetTree(state.list(), plainTheme as never, 80).map(stripAnsi);
assert.deepEqual(widgetLines, [
" ○ 1. Release",
" ├─ ○ 2. Completed branch · 2/2 done",
" └─ ○ 5. Open branch",
" └─ ○ 6. Open leaf",
" ○ 7. Pending parent · 1/1 done",
]);
const fullTree = renderTree(state.list(), plainTheme as never, 80).map(stripAnsi);
assert.ok(fullTree.some((line) => line.includes("Done one")));
assert.ok(fullTree.some((line) => line.includes("Finished child")));
state.update([{ id: 3, status: "pending" }]);
const reopened = renderWidgetTree(state.list(), plainTheme as never, 80).map(stripAnsi);
assert.ok(reopened.some((line) => line.includes("Done one")));
assert.ok(reopened.some((line) => line.includes("Done two")));
});
test("hides a completed widget summary before squeezing the task name", () => {
const state = new TaskState(() => 0);
state.add([{ name: "A long parent task name", children: [{ name: "Done", status: "done" }] }]);
const line = renderWidgetTree(state.list(), plainTheme as never, 20)[0] ?? "";
assert.ok(visibleWidth(line) <= 20);
assert.doesNotMatch(stripAnsi(line), /done$/);
});
test("formats compact timers and adds hours only when needed", () => {
assert.equal(formatDuration(0), "00:00");
assert.equal(formatDuration(65_432), "01:05");