| 1 | // Run: tsx src/__tests__/project-tree-catalog-completeness.test.ts |
| 2 | |
| 3 | import { mergeIncompleteProjectTopicPage, mergeProjectTopicPage } from "../lib/projectTreeTopic"; |
| 4 | import type { ProjectNode } from "../lib/types"; |
| 5 | |
| 6 | let passed = 0; |
| 7 | let failed = 0; |
| 8 | |
| 9 | function eq(actual: unknown, expected: unknown, label: string) { |
| 10 | if (JSON.stringify(actual) === JSON.stringify(expected)) { |
| 11 | process.stdout.write(` PASS ${label}\n`); |
| 12 | passed += 1; |
| 13 | } else { |
| 14 | process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}\n`); |
| 15 | failed += 1; |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | console.log("\nproject tree catalog completeness"); |
| 20 | |
| 21 | const completeResidentTopics: ProjectNode[] = [ |
| 22 | { key: "topic-a", kind: "topic", label: "A", topicId: "a", lastActivityAt: 600 }, |
| 23 | { key: "topic-b", kind: "topic", label: "B", topicId: "b", lastActivityAt: 500 }, |
| 24 | { key: "topic-c", kind: "topic", label: "C", topicId: "c", lastActivityAt: 400 }, |
| 25 | ]; |
| 26 | const incompleteTopics = mergeIncompleteProjectTopicPage(completeResidentTopics, [ |
| 27 | { key: "topic-b", kind: "topic", label: "B stale", topicId: "b", lastActivityAt: 100 }, |
| 28 | { key: "topic-d", kind: "topic", label: "D", topicId: "d", lastActivityAt: 700 }, |
| 29 | ]); |
| 30 | eq( |
| 31 | incompleteTopics.map((node) => `${node.topicId}:${node.label}:${node.lastActivityAt}`), |
| 32 | ["a:A:600", "b:B:500", "c:C:400", "d:D:700"], |
| 33 | "an incomplete catalog page preserves complete timestamps and order while appending discoveries", |
| 34 | ); |
| 35 | eq( |
| 36 | incompleteTopics[1] === completeResidentTopics[1], |
| 37 | true, |
| 38 | "an incomplete catalog page keeps resident row identity instead of poisoning runtime metadata", |
| 39 | ); |
| 40 | eq( |
| 41 | mergeProjectTopicPage( |
| 42 | incompleteTopics, |
| 43 | [ |
| 44 | { key: "topic-a", kind: "topic", label: "A", topicId: "a", lastActivityAt: 600 }, |
| 45 | { key: "topic-c", kind: "topic", label: "C", topicId: "c", lastActivityAt: 400 }, |
| 46 | { key: "topic-b", kind: "topic", label: "B canonical", topicId: "b", lastActivityAt: 100 }, |
| 47 | ], |
| 48 | false, |
| 49 | ).map((node) => `${node.topicId}:${node.lastActivityAt}`), |
| 50 | ["a:600", "c:400", "b:100"], |
| 51 | "a complete catalog page can still apply a legitimate activity decrease and canonical order", |
| 52 | ); |
| 53 | |
| 54 | console.log(`\n${passed} passed, ${failed} failed`); |
| 55 | if (failed > 0) process.exit(1); |
| 56 |