| 1 | // Run: npx tsx src/__tests__/project-tree-shell-race.test.ts |
| 2 | import assert from "node:assert/strict"; |
| 3 | import { readFileSync } from "node:fs"; |
| 4 | import { dirname, join } from "node:path"; |
| 5 | import { fileURLToPath } from "node:url"; |
| 6 | |
| 7 | const root = join(dirname(fileURLToPath(import.meta.url)), ".."); |
| 8 | const topic = readFileSync(join(root, "lib/projectTreeTopic.ts"), "utf8"); |
| 9 | const runtime = readFileSync(join(root, "lib/projectTreeRuntime.ts"), "utf8"); |
| 10 | const runtimeHook = readFileSync(join(root, "lib/useProjectTreeRuntimeProjection.ts"), "utf8"); |
| 11 | const bridge = readFileSync(join(root, "lib/bridge.ts"), "utf8"); |
| 12 | const panel = readFileSync(join(root, "components/ProjectTree.tsx"), "utf8"); |
| 13 | |
| 14 | assert.match(topic, /projectTreeShouldApplyShellSnapshot/, "shell race helper exported"); |
| 15 | assert.match(topic, /treeEmpty/, "empty-tree shells bypass catalog revision watermark"); |
| 16 | assert.match(panel, /projectTreeShouldApplyShellSnapshot/, "ProjectTree uses shell race helper"); |
| 17 | assert.match(panel, /treeRef\.current\.length === 0/, "v2 event re-fetches shell when tree is empty"); |
| 18 | assert.match(panel, /void refresh\(\)/, "empty-tree event path calls refresh"); |
| 19 | assert.match( |
| 20 | panel, |
| 21 | /projectTreeTopicPageIsFresh\(topicRevisionRef\.current, listKey, page\.revision\)/, |
| 22 | "topic pages compare revisions within their own project/group list", |
| 23 | ); |
| 24 | assert.doesNotMatch( |
| 25 | panel, |
| 26 | /projectTreeRevisionIsFresh\(latestRevisionRef\.current, page\.revision\)/, |
| 27 | "an unrelated project's revision cannot discard a valid slower topic page", |
| 28 | ); |
| 29 | assert.match( |
| 30 | panel, |
| 31 | /onProjectTreeChangedV2[\s\S]*projectTreeRevisionIsFresh\(latestRevisionRef\.current, event\.revision\)/, |
| 32 | "equal-revision catalog events use the shared freshness contract", |
| 33 | ); |
| 34 | assert.match(runtime, /onProjectTreeRuntimeChanged/, "runtime projection has a dedicated bridge subscription"); |
| 35 | assert.match(runtimeHook, /bindProjectTreeRuntime/, "ProjectTree binds the runtime projection after mount"); |
| 36 | assert.match(runtimeHook, /GetProjectTreeRuntimeSnapshot/, "runtime subscription reconciles with a post-subscribe snapshot"); |
| 37 | assert.match(bridge, /reason !== "runtime"/, "current frontend ignores tagged legacy runtime invalidations"); |
| 38 | assert.match(bridge, /reason !== "catalog-v2"/, "current frontend ignores catalog v2 compatibility invalidations"); |
| 39 | assert.doesNotMatch( |
| 40 | runtime, |
| 41 | /ListProjectTopics/, |
| 42 | "runtime events never reload catalog topic pages", |
| 43 | ); |
| 44 | assert.doesNotMatch( |
| 45 | panel, |
| 46 | /event\.revision\s*<=\s*latestRevisionRef\.current/, |
| 47 | "equal-revision tombstone overlays are not discarded", |
| 48 | ); |
| 49 | assert.doesNotMatch(panel, /setIndexingDone\(/, "topic refresh no longer waits for a global all-directories gate"); |
| 50 | assert.match(panel, /projectTreeEventAffectsFolder\(project, affected\)/, "directory revisions refresh only affected expanded projects"); |
| 51 | assert.match( |
| 52 | panel, |
| 53 | /projectTreeShellSignature/, |
| 54 | "debounced reload observes project arrivals through a shell signature", |
| 55 | ); |
| 56 | assert.doesNotMatch( |
| 57 | panel, |
| 58 | /\[\s*expanded,\s*loadProjectTopics,\s*query,\s*tree\s*\]/, |
| 59 | "debounced reload depends on the shell signature, not tree (topic loads would re-arm it forever)", |
| 60 | ); |
| 61 | assert.doesNotMatch(panel, /timeFilter/, "ProjectTree no longer owns a sidebar time filter"); |
| 62 | |
| 63 | console.log(" PASS project tree shell race contract"); |
| 64 |