@nathapp/nax 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * computeStoryContentHash — RRP-003
3
+ *
4
+ * AC-2: helper function computes a hash of title+description+ACs+tags
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+ import { computeStoryContentHash } from "../../../src/routing";
9
+ import type { UserStory } from "../../../src/prd/types";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Helpers
13
+ // ---------------------------------------------------------------------------
14
+
15
+ function makeStory(overrides?: Partial<UserStory>): UserStory {
16
+ return {
17
+ id: "US-001",
18
+ title: "Add login page",
19
+ description: "Users can log in with email and password",
20
+ acceptanceCriteria: ["Shows email field", "Shows password field", "Submits form"],
21
+ tags: ["auth", "ui"],
22
+ dependencies: [],
23
+ status: "pending",
24
+ passes: false,
25
+ escalations: [],
26
+ attempts: 0,
27
+ ...overrides,
28
+ };
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // AC-2: computeStoryContentHash exists and returns a string
33
+ // ---------------------------------------------------------------------------
34
+
35
+ describe("computeStoryContentHash", () => {
36
+ test("returns a non-empty string", () => {
37
+ const story = makeStory();
38
+ const hash = computeStoryContentHash(story);
39
+ expect(typeof hash).toBe("string");
40
+ expect(hash.length).toBeGreaterThan(0);
41
+ });
42
+
43
+ test("same story content produces the same hash (deterministic)", () => {
44
+ const story1 = makeStory();
45
+ const story2 = makeStory();
46
+ expect(computeStoryContentHash(story1)).toBe(computeStoryContentHash(story2));
47
+ });
48
+
49
+ test("different title produces different hash", () => {
50
+ const base = makeStory();
51
+ const changed = makeStory({ title: "Add registration page" });
52
+ expect(computeStoryContentHash(base)).not.toBe(computeStoryContentHash(changed));
53
+ });
54
+
55
+ test("different description produces different hash", () => {
56
+ const base = makeStory();
57
+ const changed = makeStory({ description: "Users can log in via OAuth" });
58
+ expect(computeStoryContentHash(base)).not.toBe(computeStoryContentHash(changed));
59
+ });
60
+
61
+ test("different acceptanceCriteria produces different hash", () => {
62
+ const base = makeStory();
63
+ const changed = makeStory({
64
+ acceptanceCriteria: ["Shows email field", "Shows password field"],
65
+ });
66
+ expect(computeStoryContentHash(base)).not.toBe(computeStoryContentHash(changed));
67
+ });
68
+
69
+ test("different tags produces different hash", () => {
70
+ const base = makeStory();
71
+ const changed = makeStory({ tags: ["auth", "api"] });
72
+ expect(computeStoryContentHash(base)).not.toBe(computeStoryContentHash(changed));
73
+ });
74
+
75
+ test("story ID, status, and attempts do NOT affect the hash (only content fields)", () => {
76
+ const base = makeStory({ id: "US-001", status: "pending", attempts: 0 });
77
+ const differentMeta = makeStory({ id: "US-099", status: "in-progress", attempts: 3 });
78
+ expect(computeStoryContentHash(base)).toBe(computeStoryContentHash(differentMeta));
79
+ });
80
+
81
+ test("empty acceptanceCriteria and tags produce a valid hash", () => {
82
+ const story = makeStory({ acceptanceCriteria: [], tags: [] });
83
+ const hash = computeStoryContentHash(story);
84
+ expect(typeof hash).toBe("string");
85
+ expect(hash.length).toBeGreaterThan(0);
86
+ });
87
+
88
+ test("adding an AC changes the hash", () => {
89
+ const before = makeStory({ acceptanceCriteria: ["AC1", "AC2"] });
90
+ const after = makeStory({ acceptanceCriteria: ["AC1", "AC2", "AC3 — new"] });
91
+ expect(computeStoryContentHash(before)).not.toBe(computeStoryContentHash(after));
92
+ });
93
+
94
+ test("adding a tag changes the hash", () => {
95
+ const before = makeStory({ tags: ["backend"] });
96
+ const after = makeStory({ tags: ["backend", "security"] });
97
+ expect(computeStoryContentHash(before)).not.toBe(computeStoryContentHash(after));
98
+ });
99
+ });