mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-11 19:09:20 +00:00
* fix(inspector): report nested hosts' authored window, one location per source line The runtime strips data-duration/data-end from nested composition hosts after boot and keeps them as data-hf-authored-*. Timeline inspection built its resolver without authored attrs, so a host with data-duration="4" reported its GSAP timeline length (3.72s) instead. Timeline text and events[].sources listed the same file:line once per mapped snippet (creation, caller, dependency), so a one-line script printed one location up to nine times. * refactor(inspector): carry the dedupe reason in the helper's name * fix(inspector): end continuation on a page that fits no snippet; fill budget with candidates * fix(inspector): timeline groups by component, names attr values, eases, film end * fix(studio-server): stamp hf ids into a file without reformatting it * fix(inspector): find an element's tag before its served hf id reaches the source * fix(inspector): live element map shows authored classes and text, not the playhead's * fix(inspector): collapse script-built text so the element map ignores the playhead Class names stay in source order, a runtime-stripped id is shown as id="…" rather than a selector, and a grouped row names which children it covers. * fix(inspector): keep paging, stamping, and the live map still on the reported edges A snippet larger than the page ended the walk, so later snippets could not be reached. Raw text ended at a longer tag name, which could stamp an id inside a script. An empty or mixed source element showed live text, and an unknown duration read as a film that ends at 0s. * fix(inspector): mint ids for an empty hf id and keep an unquoted class An empty data-hf-id was treated as already assigned, so stamping left it blank while the older stamper minted one. An unquoted class was read as no class, so the live map dropped it. * fix(inspector): narrow markup before reading a child tag skipMarkup returns a boolean, so the build still sees Markup where a tag is required. Narrow on the kind first so the inspector typechecks. * fix: quiet check false positives and name the ancestor of an id-less target Endpoint contact, disjoint fromTo pairs, and a negative z-index under a stacking-context parent were warnings with bad advice. Layout and contrast blamed a dot grid, a circular avatar's panel, and a visibility override. A faint texture veil is no longer the click target. * fix(lint): drop a history sentence and six gsap comments The comments check treats "load-bearing" as history, and gsap.ts added six comment lines. The names already carry what those lines said. * fix(parsers): ignore an empty id written inside another attribute The empty-id scan matched the first data-hf-id="" in the tag, including one inside a quoted title. It now skips quoted text, so the real attribute is minted. * fix(lint): keep real covers and a negative z-index under a card A gradient scale-down treated every fill like a radial dot, so a panel that starts transparent stopped counting as a cover. A stacking context on any ancestor cleared a negative z-index, and a faint wrapper made images inside it unpickable. * fix: keep an emoji from shifting a stamped id The quote mask replaced one emoji with one space, so the id landed before the closing quote. A radial cover that turns opaque after a clear stop stays fully painted. * fix(lint): score a letter-only dot as a dot, not a color name namesAColor saw #fff and rgba as words, so those dot grids counted as a full cover. Hex and rgb colors are stripped before that word check.
116 lines
4.8 KiB
TypeScript
116 lines
4.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { Sources, mergeSnippets, fingerprint } from "../src/sources";
|
|
import { PREVIEW_IGNORED_DIRS } from "../../core/src/projectFolders";
|
|
import {
|
|
bundleToSingleHtml,
|
|
type BundleScriptProvenance,
|
|
} from "../../core/src/compiler/htmlBundler";
|
|
|
|
describe("source records", () => {
|
|
it("ignores the generated folders Studio's preview signature ignores", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "inspect-source-"));
|
|
try {
|
|
writeFileSync(join(dir, "index.html"), "<div></div>");
|
|
const before = fingerprint(dir);
|
|
for (const folder of PREVIEW_IGNORED_DIRS) {
|
|
mkdirSync(join(dir, folder));
|
|
writeFileSync(join(dir, folder, "peaks.json"), "[1,2,3]");
|
|
}
|
|
expect(fingerprint(dir)).toBe(before);
|
|
writeFileSync(join(dir, "data.json"), "{}");
|
|
expect(fingerprint(dir)).not.toBe(before);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("keeps original multiline blocks and deduplicates targets without reformatting", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "inspect-source-"));
|
|
try {
|
|
const text = 'const target = "#title";\nconst config = {\n x: 100,\n duration: 1\n};\n';
|
|
writeFileSync(join(dir, "scene.js"), text);
|
|
const source = new Sources(dir);
|
|
const a = source.slice("scene.js", 24, text.length, "animation creation", 0)!;
|
|
const b = source.slice("scene.js", 24, text.length, "animation creation", 1)!;
|
|
const result = mergeSnippets([a, b], source);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].targets).toEqual([0, 1]);
|
|
expect(text.includes(result[0].code)).toBe(true);
|
|
const before = fingerprint(dir);
|
|
writeFileSync(join(dir, "scene.js"), text.replace("100", "150"));
|
|
expect(fingerprint(dir)).not.toBe(before);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
it("extracts raw-text elements with HTML-valid whitespace before the closing bracket", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "inspect-raw-text-"));
|
|
try {
|
|
writeFileSync(
|
|
join(dir, "index.html"),
|
|
"<script>const marker = 1;</script \n><style>.marker { color: red; }</style\t>",
|
|
);
|
|
const source = new Sources(dir);
|
|
expect(source.chunks.map((chunk) => chunk.text)).toContain("const marker = 1;");
|
|
expect(source.cssRules.some((rule) => rule.selector === ".marker")).toBe(true);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
it("opt-in compiler mapping keeps default HTML unchanged and concurrent maps isolated", async () => {
|
|
const dirs = [0, 1].map(() => mkdtempSync(join(tmpdir(), "inspect-map-")));
|
|
try {
|
|
dirs.forEach((dir, i) =>
|
|
writeFileSync(
|
|
join(dir, "index.html"),
|
|
`<!doctype html><html><head></head><body><div data-composition-id="root" data-start="0" data-duration="10" data-width="1920" data-height="1080"></div><script>const marker${i}=1; window.__timelines={};</script></body></html>`,
|
|
),
|
|
);
|
|
const outputs = await Promise.all(
|
|
dirs.map(async (dir) => {
|
|
const maps: BundleScriptProvenance[] = [];
|
|
const plain = await bundleToSingleHtml(dir);
|
|
const mapped = await bundleToSingleHtml(dir, { scriptProvenance: (p) => maps.push(p) });
|
|
return { plain, mapped, maps };
|
|
}),
|
|
);
|
|
outputs.forEach((output, i) => {
|
|
expect(output.mapped.replace(/\n\/\/# sourceURL=hf-inspect-[a-f0-9]+\.js/g, "")).toBe(
|
|
output.plain,
|
|
);
|
|
expect(output.plain).not.toContain("hf-inspect-");
|
|
expect(output.maps.some((map) => map.input.includes(`marker${i}`))).toBe(true);
|
|
expect(output.maps.some((map) => map.input.includes(`marker${1 - i}`))).toBe(false);
|
|
});
|
|
} finally {
|
|
dirs.forEach((dir) => rmSync(dir, { recursive: true, force: true }));
|
|
}
|
|
});
|
|
it("finds an element's tag before its served data-hf-id reaches the source", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "inspect-source-"));
|
|
try {
|
|
writeFileSync(
|
|
join(dir, "scene.html"),
|
|
'<div data-composition-id="s"><span id="t" class="w">A</span><i class="dot"></i><i class="dot"></i></div>',
|
|
);
|
|
const source = new Sources(dir);
|
|
const served = {
|
|
ref: "r",
|
|
tag: "span",
|
|
id: "t",
|
|
hfId: "hf-ab12",
|
|
classes: ["w"],
|
|
composition: "s",
|
|
};
|
|
expect(source.markup(served, 0)?.code).toContain('<span id="t" class="w">');
|
|
const twin = { ...served, tag: "i", id: "", classes: ["dot"] };
|
|
expect(source.markup(twin, 0)).toBeNull();
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|