返回 DeepSeek-Reasonix
project-tree-runtime-projection.test.ts
根目录 / desktop / frontend / src / __tests__ / project-tree-runtime-projection.test.ts
1 import assert from "node:assert/strict";
2 import { createProjectTreeRuntimeProjection, projectTreeApplyRuntimeTopics } from "../lib/projectTreeRuntime";
3 import type { ProjectNode } from "../lib/types";
4
5 const catalog: ProjectNode[] = [
6 { key: "project-a", kind: "project", label: "A", root: "/a", children: [
7 { key: "known", kind: "topic", label: "Known", root: "/a", topicId: "known", children: [] },
8 ] },
9 { key: "project-b", kind: "project", label: "B", root: "/b", children: [] },
10 ];
11 const overlaid = projectTreeApplyRuntimeTopics(catalog, [
12 { scope: "project", workspaceRoot: "/a", node: {
13 key: "known", kind: "topic", label: "Known live", root: "/a", topicId: "known", running: true, status: "thinking", children: [
14 { key: "known-a", kind: "session", label: "Session A", topicId: "known", sessionPath: "/a/a.jsonl" },
15 { key: "known-b", kind: "session", label: "Session B", topicId: "known", sessionPath: "/a/b.jsonl" },
16 ],
17 } },
18 { scope: "project", workspaceRoot: "/b", node: {
19 key: "new", kind: "topic", label: "New live", root: "/b", topicId: "new", running: true, status: "streaming", children: [],
20 } },
21 ]);
22 const shape = (tree: ProjectNode[]) => tree.map((project) => project.children?.map((topic) => [
23 topic.topicId, topic.running, topic.runtimeOnly,
24 ]));
25 assert.deepEqual(shape(overlaid), [[['known', true, undefined]], [['new', true, true]]]);
26 assert.equal(overlaid[0]?.children?.[0]?.children?.length, 2);
27 assert.deepEqual(shape(projectTreeApplyRuntimeTopics(overlaid, [])), [[['known', undefined, undefined]], []]);
28 assert.equal(projectTreeApplyRuntimeTopics(overlaid, [
29 { scope: "project", workspaceRoot: "/a", node: {
30 key: "known", kind: "topic", label: "Known live", root: "/a", topicId: "known", running: true, children: [],
31 } },
32 ], new Set(["known"]))[0]?.children?.length, 0);
33 const settled = projectTreeApplyRuntimeTopics(overlaid, []);
34 assert.equal(settled[0]?.children?.[0]?.children?.length, 2,
35 "clearing runtime state preserves the topic's durable session children");
36 assert.equal(settled[0]?.children?.[0]?.children?.some((session) => session.open || session.running), false,
37 "clearing runtime state removes only transient session status");
38
39 const projects: ProjectNode[] = Array.from({ length: 100 }, (_, index) => ({
40 key: `p-${index}`, kind: "project", label: `P ${index}`, root: `/p/${index}`, children: [],
41 }));
42 const topics = projects.map((project, index) => ({
43 scope: "project", workspaceRoot: project.root, node: {
44 key: `t-${index}`, kind: "topic" as const, label: `T ${index}`, root: project.root,
45 topicId: `t-${index}`, running: true, status: "thinking" as const, children: [],
46 },
47 }));
48 const hundred = projectTreeApplyRuntimeTopics(projects, topics);
49 assert.equal(hundred.reduce((count, project) => count + (project.children?.filter((topic) => topic.running).length ?? 0), 0), 100);
50
51 const colliding = projectTreeApplyRuntimeTopics([
52 { key: "collision-a", kind: "project", label: "A", root: "/collision/a", children: [] },
53 { key: "collision-b", kind: "project", label: "B", root: "/collision/b", children: [] },
54 ], [
55 { scope: "project", workspaceRoot: "/collision/a", node: {
56 key: "shared-a", kind: "topic", label: "Shared A", root: "/collision/a", topicId: "shared",
57 sessionPath: "/collision/a/a.jsonl", running: true, children: [],
58 } },
59 { scope: "project", workspaceRoot: "/collision/b", node: {
60 key: "shared-b", kind: "topic", label: "Shared B", root: "/collision/b", topicId: "shared",
61 sessionPath: "/collision/b/b.jsonl", open: true, children: [],
62 } },
63 ]);
64 assert.deepEqual(
65 colliding.map((project) => project.children?.map((topic) => [topic.label, topic.sessionPath, topic.running, topic.open])),
66 [[['Shared A', '/collision/a/a.jsonl', true, undefined]], [['Shared B', '/collision/b/b.jsonl', undefined, true]]],
67 "runtime rows with the same topic id stay inside their project scope",
68 );
69
70 const stableProjection = createProjectTreeRuntimeProjection();
71 const stableCatalog: ProjectNode[] = [{
72 key: "stable-project", kind: "project", label: "Stable", root: "/stable", children: [
73 { key: "topic-a", kind: "topic", label: "A", root: "/stable", topicId: "a", createdAt: 300, lastActivityAt: 600, children: [] },
74 { key: "topic-b", kind: "topic", label: "B", root: "/stable", topicId: "b", createdAt: 200, lastActivityAt: 500, children: [] },
75 { key: "topic-c", kind: "topic", label: "C", root: "/stable", topicId: "c", createdAt: 100, lastActivityAt: 400, children: [] },
76 ],
77 }];
78 const runtimeB = [{
79 scope: "project", workspaceRoot: "/stable", node: {
80 key: "topic-b", kind: "topic" as const, label: "B", root: "/stable", topicId: "b",
81 open: true, running: false, children: [],
82 },
83 }];
84 const projected = stableProjection.apply(stableCatalog, runtimeB);
85 assert.strictEqual(projected[0]?.children?.[0], stableCatalog[0]?.children?.[0], "an unrelated row keeps its object identity");
86 assert.strictEqual(projected[0]?.children?.[2], stableCatalog[0]?.children?.[2], "every unrelated row keeps its object identity");
87 const repeated = stableProjection.apply(projected, structuredClone(runtimeB));
88 assert.strictEqual(repeated, projected, "an equivalent runtime snapshot is a tree-level no-op");
89
90 const transientCatalog: ProjectNode[] = [{
91 ...stableCatalog[0],
92 children: [stableCatalog[0]!.children![0]!, stableCatalog[0]!.children![2]!],
93 }];
94 const transient = stableProjection.apply(transientCatalog, structuredClone(runtimeB));
95 const restoredRuntimeB = transient[0]?.children?.find((topic) => topic.topicId === "b");
96 assert.equal(restoredRuntimeB?.runtimeOnly, true, "a catalog-lag row remains marked as runtime-only");
97 assert.equal(restoredRuntimeB?.createdAt, 200, "a catalog-lag row retains its resident creation time");
98 assert.equal(restoredRuntimeB?.lastActivityAt, 500, "a catalog-lag row retains its resident activity time");
99 assert.deepEqual(
100 [...(transient[0]?.children ?? [])]
101 .sort((a, b) => (b.lastActivityAt || b.createdAt || 0) - (a.lastActivityAt || a.createdAt || 0))
102 .map((topic) => topic.topicId),
103 ["a", "b", "c"],
104 "catalog lag cannot move the active row to a zero-time sort position",
105 );
106
107 const pruningProjection = createProjectTreeRuntimeProjection();
108 const pruningCatalog: ProjectNode[] = [{
109 key: "pruning-project", kind: "project", label: "Pruning", root: "/pruning", children: [
110 { key: "stale", kind: "topic", label: "Stale metadata", root: "/pruning", topicId: "stale", createdAt: 10, children: [] },
111 ],
112 }];
113 pruningProjection.apply(pruningCatalog, []);
114 const emptyPruningCatalog: ProjectNode[] = [{ ...pruningCatalog[0]!, children: [] }];
115 pruningProjection.apply(emptyPruningCatalog, []);
116 const afterPrune = pruningProjection.apply(emptyPruningCatalog, [{
117 scope: "project", workspaceRoot: "/pruning", node: {
118 key: "fresh", kind: "topic", label: "Fresh runtime", root: "/pruning", topicId: "stale", running: true, children: [],
119 },
120 }]);
121 assert.equal(afterPrune[0]?.children?.[0]?.label, "Fresh runtime", "deleted resident metadata is not resurrected");
122 assert.equal(afterPrune[0]?.children?.[0]?.createdAt, undefined, "deleted resident timestamps are released");
123 console.log(" PASS project tree runtime projection");
124
124 lines TYPESCRIPT