返回 DeepSeek-Reasonix
project-tree-independent-sessions.test.ts
根目录 / desktop / frontend / src / __tests__ / project-tree-independent-sessions.test.ts
1 import assert from "node:assert/strict";
2 import { test } from "node:test";
3 import { splitPinnedProjectTree } from "../lib/projectTreePresentation";
4 import { createProjectTreeRuntimeProjection } from "../lib/projectTreeRuntime";
5 import {
6 projectTreeReadActivityKey,
7 projectTreeSeedReadActivity,
8 projectTreeTopicHasUnreadActivity,
9 projectTreeTopicOpenRequest,
10 projectTreeWithoutTopics,
11 topicIsActive,
12 } from "../lib/projectTreeTopic";
13 import type { ProjectNode, ProjectRuntimeTopic } from "../lib/types";
14
15 const root = "/repo";
16 const topicId = "shared-topic";
17 const sessionA: ProjectNode = {
18 key: "session-a", kind: "topic", label: "Original", root, topicId,
19 session: { hostId: "local", sessionId: "a" }, sessionPath: "/sessions/a.jsonl",
20 resultSequence: 100, turnsState: "ready", children: [],
21 };
22 const sessionB: ProjectNode = {
23 key: "session-b", kind: "topic", label: "Fork", root, topicId,
24 session: { hostId: "local", sessionId: "b" }, sessionPath: "/sessions/b.jsonl",
25 resultSequence: 10, turnsState: "ready", children: [],
26 };
27 const catalog: ProjectNode[] = [{
28 key: "project", kind: "project", label: "Project", root,
29 children: [sessionA, sessionB],
30 }];
31 const runtimeA: ProjectRuntimeTopic = {
32 scope: "project", workspaceRoot: root,
33 node: { ...sessionA, label: "Runtime label", running: true, open: true, status: "waiting_confirmation" },
34 };
35
36 test("an empty runtime snapshot preserves two durable sessions sharing a topic", () => {
37 const projected = createProjectTreeRuntimeProjection().apply(catalog, []);
38 assert.deepEqual(projected[0]?.children?.map(row => row.session?.sessionId), ["a", "b"]);
39 assert.equal(projected[0]?.children?.[0]?.sessionPath, "/sessions/a.jsonl");
40 assert.equal(projected[0]?.children?.[1]?.sessionPath, "/sessions/b.jsonl");
41 });
42
43 test("one runtime affects only its exact session and never changes a sibling open target", () => {
44 const projection = createProjectTreeRuntimeProjection();
45 const overlaid = projection.apply(catalog, [runtimeA]);
46 const [a, b] = overlaid[0]?.children ?? [];
47 assert.equal(a?.status, "waiting_confirmation");
48 assert.equal(a?.running, true);
49 assert.equal(a?.sessionPath, "/sessions/a.jsonl");
50 assert.equal(b?.status, undefined);
51 assert.equal(b?.running, undefined);
52 assert.equal(b?.sessionPath, "/sessions/b.jsonl");
53 assert.equal(projectTreeTopicOpenRequest(a!)?.sessionPath, "session-id:a");
54 assert.equal(projectTreeTopicOpenRequest(b!)?.sessionPath, "session-id:b");
55 assert.deepEqual(projection.apply(overlaid, [runtimeA])[0]?.children?.map(row => row.session?.sessionId), ["a", "b"]);
56 assert.deepEqual(projection.apply(overlaid, [])[0]?.children?.map(row => row.session?.sessionId), ["a", "b"]);
57 });
58
59 test("two running sessions sharing a topic retain their own runtime status", () => {
60 const runtimeB: ProjectRuntimeTopic = {
61 scope: "project", workspaceRoot: root,
62 node: { ...sessionB, running: true, status: "thinking" },
63 };
64 const projected = createProjectTreeRuntimeProjection().apply(catalog, [runtimeA, runtimeB]);
65 assert.deepEqual(projected[0]?.children?.map(row => [row.session?.sessionId, row.status]), [
66 ["a", "waiting_confirmation"], ["b", "thinking"],
67 ]);
68 const afterAStops = createProjectTreeRuntimeProjection().apply(catalog, [runtimeB]);
69 assert.deepEqual(afterAStops[0]?.children?.map(row => [row.session?.sessionId, row.status]), [
70 ["a", undefined], ["b", "thinking"],
71 ]);
72 });
73
74 test("runtime reconciliation preserves dormant durable branch children", () => {
75 const branchA: ProjectNode = { ...sessionA, kind: "session" };
76 const branchB: ProjectNode = { ...sessionB, kind: "session" };
77 const parent: ProjectNode = { key: "legacy-parent", kind: "topic", label: "Legacy", root, topicId,
78 children: [branchA, branchB] };
79 const tree: ProjectNode[] = [{ ...catalog[0]!, children: [parent] }];
80 const projection = createProjectTreeRuntimeProjection();
81 const withRuntime = projection.apply(tree, [{ ...runtimeA, node: { ...parent,
82 children: [{ ...branchA, status: "waiting_confirmation", running: true }] } }]);
83 assert.deepEqual(withRuntime[0]?.children?.[0]?.children?.map(row => row.session?.sessionId), ["a", "b"]);
84 assert.equal(withRuntime[0]?.children?.[0]?.children?.[1]?.running, undefined);
85 const stopped = projection.apply(withRuntime, []);
86 assert.deepEqual(stopped[0]?.children?.[0]?.children?.map(row => row.session?.sessionId), ["a", "b"]);
87 });
88
89 test("pinning A neither hides B nor paints a duplicate of A", () => {
90 const pinnedTree: ProjectNode[] = [{ ...catalog[0]!, children: [{ ...sessionA, pinned: true }, sessionB] }];
91 const sections = splitPinnedProjectTree(pinnedTree, "updated");
92 assert.deepEqual(sections.pinned.map(row => row.session?.sessionId), ["a"]);
93 assert.deepEqual(sections.projects[0]?.children?.map(row => row.session?.sessionId), ["b"]);
94 });
95
96 test("the active A route cannot mark B active or suppress B's unread result", () => {
97 const bKey = projectTreeReadActivityKey(sessionB)!;
98 assert.equal(topicIsActive(sessionA, "project", root, topicId, "session-id:a"), true);
99 assert.equal(topicIsActive(sessionB, "project", root, topicId, "session-id:a"), false);
100 assert.equal(projectTreeTopicHasUnreadActivity(
101 { ...sessionB, resultSequence: 20 }, { [bKey]: 10 }, "project", root, topicId, "session-id:a",
102 ), true);
103 });
104
105 test("ordinary ready metadata cannot repair an imported baseline without authority", () => {
106 const bKey = projectTreeReadActivityKey(sessionB)!;
107 const seeded = projectTreeSeedReadActivity(catalog, { [bKey]: 100 });
108 assert.equal(seeded[bKey], 100);
109 assert.equal(projectTreeTopicHasUnreadActivity(
110 { ...sessionB, resultSequence: 20 }, seeded, "project", root, "unrelated", "session-id:a",
111 ), false);
112 });
113
114 test("a committed archive tombstone for B cannot hide A or revive B from a stale page", () => {
115 const filtered = projectTreeWithoutTopics(catalog, new Set(["ref\u0000local\u0000b"]));
116 assert.deepEqual(filtered[0]?.children?.map(row => row.session?.sessionId), ["a"]);
117 const staleIncoming: ProjectNode[] = [{ ...catalog[0]!, children: [sessionA, sessionB] }];
118 const fenced = projectTreeWithoutTopics(staleIncoming, new Set(["ref\u0000local\u0000b"]));
119 assert.deepEqual(fenced[0]?.children?.map(row => row.session?.sessionId), ["a"]);
120 });
121
121 lines TYPESCRIPT