返回 DeepSeek-Reasonix
project-tree-rename-optimistic.test.ts
根目录 / desktop / frontend / src / __tests__ / project-tree-rename-optimistic.test.ts
1 // Run: tsx src/__tests__/project-tree-rename-optimistic.test.ts
2
3 import { projectTreeWithTopicTitle } from "../lib/projectTreeTopic";
4 import { readFileSync } from "node:fs";
5 import { fileURLToPath } from "node:url";
6 import { dirname, join } from "node:path";
7
8 let passed = 0;
9 let failed = 0;
10
11 function eq(a: unknown, b: unknown, label: string) {
12 if (JSON.stringify(a) === JSON.stringify(b)) {
13 process.stdout.write(` PASS ${label}\n`);
14 passed += 1;
15 } else {
16 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
17 failed += 1;
18 }
19 }
20
21 console.log("\nproject tree optimistic rename");
22
23 eq(
24 projectTreeWithTopicTitle(
25 [
26 {
27 key: "p",
28 kind: "project",
29 label: "P",
30 children: [
31 { key: "topic_rename", kind: "topic", label: "Old name", topicId: "topic_rename" },
32 { key: "topic_keep", kind: "topic", label: "Keep me", topicId: "topic_keep" },
33 ],
34 },
35 ],
36 "topic_rename",
37 "New name",
38 ).map((node) => (node.children ?? []).map((child) => child.label)),
39 [["New name", "Keep me"]],
40 "a committed rename repaints the topic row label before the catalog event arrives",
41 );
42
43 eq(
44 projectTreeWithTopicTitle(
45 [{ key: "p", kind: "project", label: "P", children: [{ key: "t", kind: "topic", label: "Same", topicId: "t" }] }],
46 "t",
47 "Same",
48 )[0].children?.[0].label,
49 "Same",
50 "an unchanged rename label leaves the tree identity stable",
51 );
52
53 eq(
54 projectTreeWithTopicTitle(
55 [{ key: "p", kind: "project", label: "P", children: [{ key: "t", kind: "topic", label: "T", topicId: "t" }] }],
56 "topic_missing",
57 "New name",
58 )[0].children?.[0].label,
59 "T",
60 "a rename for a topic outside the loaded tree changes nothing",
61 );
62
63 const projectTreeSource = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../components/ProjectTree.tsx"), "utf8");
64 eq(
65 projectTreeSource.includes("projectTreeWithTopicTitle(current, topicId, title)"),
66 true,
67 "commitRenameTopic paints the new label optimistically before the refresh round-trip",
68 );
69 eq(
70 projectTreeSource.includes("project-tree__skeleton"),
71 true,
72 "an expanded folder with a loading first topic page renders skeleton rows",
73 );
74 eq(
75 /projectTreeRevisionIsFresh\(latestRevisionRef\.current, event\.revision\)\) \{\s*void refresh\(\)/.test(projectTreeSource),
76 true,
77 "a stale project-tree revision falls back to a full snapshot refetch instead of dropping the event",
78 );
79 eq(
80 projectTreeSource.includes("renaming: aiRenamingTopics, rename: aiRenameSession } = useSessionTitleOperation"),
81 true,
82 "AI rename progress is tracked per target instead of globally blocking unrelated sessions",
83 );
84 eq(
85 readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../lib/useSessionTitleOperation.ts"), "utf8").includes("sessionTitleErrorKey(error)"),
86 true,
87 "session operation error codes are localized before they reach the toast",
88 );
89 eq(
90 projectTreeSource.includes("disabled: aiRenamingTopics.has(aiRenameTarget)"),
91 true,
92 "an AI rename only disables the targeted local session",
93 );
94
95 console.log(`\n${passed} passed, ${failed} failed`);
96 if (failed > 0) process.exit(1);
97
97 lines TYPESCRIPT