/** * @vitest-environment jsdom * * T2 — Stable id spec (spec for R1). * * These tests define what "stable hf- id" means BEFORE R1 implements it. * They are intentionally red until R1 lands. * * Currently failing (spec): tests 1, 2, 3 — parser assigns `element-N` not `hf-xxxx`. * Currently passing (baseline): tests 4, 5, 6, 7 — these already hold and must not regress. * * Scope: id assignment and stability only. Round-trip fidelity is T1 territory. */ import { describe, expect, it } from "vitest"; import { parseHtml } from "./htmlParser.js"; import { serialize } from "./test-utils.js"; describe("T2 — stable element ids (spec for R1)", () => { // --- Spec (red until R1) --- it("[spec] elements without an id get a hf- prefixed id at parse", () => { const html = `
Text
`; const { elements } = parseHtml(html); for (const el of elements) { expect(el.id).toMatch(/^hf-/); } }); it("[spec] generated hf- ids match /^hf-[a-z0-9]{4}$/", () => { const html = `
X
`; const { elements } = parseHtml(html); const noPreExistingId = elements.filter((e) => e.id !== "stage"); for (const el of noPreExistingId) { expect(el.id).toMatch(/^hf-[a-z0-9]{4}$/); } }); it("[spec] adding an element before existing ones does not change existing ids", () => { const base = `
A
B
`; const withPrepend = `
New
A
B
`; const baseAlpha = parseHtml(base).elements.find((e) => e.name === "AlphaEl"); const extendedAlpha = parseHtml(withPrepend).elements.find((e) => e.name === "AlphaEl"); expect(baseAlpha).toBeDefined(); expect(extendedAlpha).toBeDefined(); // With counter-based ids: base AlphaEl = element-1, extended AlphaEl = element-2 — FAILS. // With hf- stable ids: both = same hf-xxxx — PASSES (R1 target). expect(extendedAlpha?.id).toBe(baseAlpha?.id); }); // --- Baseline (already pass, must not regress) --- it("existing data-hf-id is pinned and becomes the clip id (never re-minted)", () => { const html = `
Hi
`; const { elements } = parseHtml(html); expect(elements.some((e) => e.id === "hf-anch")).toBe(true); }); it("ids are deterministic: same input produces same ids on re-parse", () => { const html = `
A
B
`; const first = parseHtml(html).elements.map((e) => e.id); const second = parseHtml(html).elements.map((e) => e.id); expect(first).toEqual(second); }); it("ids are unique within a document", () => { const html = `
A
B
C
`; const ids = parseHtml(html).elements.map((e) => e.id); expect(new Set(ids).size).toBe(ids.length); }); it("two elements with identical markup get distinct ids (no content-hash collision)", () => { // Ensures R1's id derivation includes position or a sibling counter, // not just content — two structurally identical elements must not collide. const html = `
Same
Same
`; const { elements } = parseHtml(html); const ids = elements.map((e) => e.id); expect(new Set(ids).size).toBe(ids.length); }); it("ids survive a serialize → re-parse round-trip", () => { const html = `
Content
`; const original = parseHtml(html); const reparsed = parseHtml(serialize(original)); const origIds = original.elements.map((e) => e.id).sort(); const roundIds = reparsed.elements.map((e) => e.id).sort(); expect(roundIds).toEqual(origIds); }); it.todo("sub-composition instances get scoped ids (compositionId/hf-x) — requires SDK session"); });