fix(replay-ui): read data-* attributes by their markup names

The web runtime now publishes raw markup attribute names, so attrs["step"]
read nothing where the markup writes data-step. Three properties went
vacuous and exactlyOneStepIsSelected reported false against a UI that was
fine.

The test also fails if a dataOf key gains no matching attribute, or if an
attribute it derives is rendered nowhere.
This commit is contained in:
pj committed 2026-08-17 23:45:33 +05:30
1 parent 5651adb439
commit e432af7828
3 files changed
+69 -1

No files matched your search

@@ -0,0 +1,60 @@
import { describe, it, expect } from "bun:test";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { attributeOf } from "../../sanderling/data-attributes";
// Bug class: the web runtime keys attrs by the raw markup name, so a spec
// reading attrs["stepCount"] gets undefined and every property over it passes
// vacuously against a UI that is broken.
describe("attributeOf", () => {
it("names the attribute the markup writes for each logical key", () => {
expect(attributeOf("step")).toBe("data-step");
expect(attributeOf("stepCount")).toBe("data-step-count");
expect(attributeOf("active")).toBe("data-active");
expect(attributeOf("violations")).toBe("data-violations");
expect(attributeOf("tabId")).toBe("data-tab-id");
expect(attributeOf("violationCount")).toBe("data-violation-count");
});
});
const here = new URL(".", import.meta.url).pathname;
const specPath = join(here, "../../sanderling/spec.ts");
const sourceRoot = join(here, "..");
function sourceFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name);
if (entry.isDirectory()) return entry.name === "__tests__" ? [] : sourceFiles(path);
return entry.name.endsWith(".tsx") || entry.name.endsWith(".ts") ? [path] : [];
});
}
function keysReadByTheSpec(): string[] {
const spec = readFileSync(specPath, "utf8");
const keys = new Set<string>();
for (const match of spec.matchAll(/dataOf\([^,]+,\s*"([^"]+)"\)/g)) keys.add(match[1]);
return [...keys];
}
describe("the keys the spec reads", () => {
const keys = keysReadByTheSpec();
it("covers every dataOf call site", () => {
expect(keys.sort()).toEqual([
"active",
"step",
"stepCount",
"tabId",
"violationCount",
"violations",
]);
});
it("names attributes the UI renders", () => {
const markup = sourceFiles(sourceRoot)
.map((path) => readFileSync(path, "utf8"))
.join("\n");
const missing = keys.map(attributeOf).filter((attribute) => !markup.includes(`${attribute}=`));
expect(missing).toEqual([]);
});
});