From 3541ab44d81468b43399324cf2b718b465f68ef4 Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 6 Jun 2026 12:50:43 +0530 Subject: [PATCH] test(replay-ui): pin snapshot flatten/getAtPath path round-trip --- replay-ui/src/__tests__/snapshot-diff.test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 replay-ui/src/__tests__/snapshot-diff.test.ts diff --git a/replay-ui/src/__tests__/snapshot-diff.test.ts b/replay-ui/src/__tests__/snapshot-diff.test.ts new file mode 100644 index 0000000..12f8e70 --- /dev/null +++ b/replay-ui/src/__tests__/snapshot-diff.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from "bun:test"; +import { + canonicalize, + flatten, + getAtPath, + stableStringify, +} from "../lib/snapshot-diff"; + +// Bug class: a path off-by-one in the getAtPath regex parse resolves the wrong +// node, so every diff between two snapshots is mis-reported. flatten emits the +// paths the diff later feeds back through getAtPath, so every emitted path must +// resolve to the same value flatten recorded. +describe("flatten/getAtPath round-trip", () => { + const cases: { name: string; input: Record }[] = [ + { name: "nested objects", input: { a: { b: { c: 1 } } } }, + { + name: "array longer than inline limit indexes each element", + input: { items: [10, 20, 30, 40] }, + }, + { + name: "objects inside an expanded array", + input: { rows: [{ id: 1 }, { id: 2 }, { id: 3 }] }, + }, + { + name: "keys with dots are matched verbatim before regex split", + input: { "a.b": 7 }, + }, + { + name: "mixed nesting", + input: { ui: { tabs: ["x", "y", "z", "w"], open: true }, n: 0 }, + }, + ]; + + for (const { name, input } of cases) { + it(name, () => { + for (const row of flatten(input)) { + expect(stableStringify(getAtPath(input, row.path))).toBe( + stableStringify(row.value), + ); + } + }); + } + + it("indexes array elements by their real position, not off by one", () => { + const input = { items: ["a", "b", "c", "d"] }; + const rows = flatten(input); + expect(getAtPath(input, "items[0]")).toBe("a"); + expect(getAtPath(input, "items[3]")).toBe("d"); + expect(rows.map((r) => r.path)).toEqual([ + "items[0]", + "items[1]", + "items[2]", + "items[3]", + ]); + }); +}); + +describe("canonicalize", () => { + it("orders object keys so reordered snapshots compare equal", () => { + expect(stableStringify({ b: 1, a: 2 })).toBe(stableStringify({ a: 2, b: 1 })); + expect(canonicalize({ b: 1, a: 2 })).toEqual({ a: 2, b: 1 }); + }); + + it("preserves array order", () => { + expect(stableStringify([3, 1, 2])).not.toBe(stableStringify([1, 2, 3])); + }); +});