Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions packages/core/src/parsers/htmlParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ describe("parseHtml", () => {
const result = parseHtml(html);

expect(result.elements).toHaveLength(2);
expect(result.elements[0].id).toBe("text1");
expect(result.elements[0].id).toMatch(/^hf-[a-z0-9]{4}$/);
expect(result.elements[0].startTime).toBe(0);
expect(result.elements[0].duration).toBe(5);
expect(result.elements[0].name).toBe("Title");
expect(result.elements[0].type).toBe("text");

expect(result.elements[1].id).toBe("text2");
expect(result.elements[1].id).toMatch(/^hf-[a-z0-9]{4}$/);
expect(result.elements[1].startTime).toBe(2);
expect(result.elements[1].duration).toBe(5);
});
Expand All @@ -53,7 +53,7 @@ describe("parseHtml", () => {

expect(result.elements).toHaveLength(1);
expect(result.elements[0].type).toBe("composition");
expect(result.elements[0].id).toBe("comp1");
expect(result.elements[0].id).toMatch(/^hf-[a-z0-9]{4}$/);
if (result.elements[0].type === "composition") {
expect(result.elements[0].compositionId).toBe("abc123");
expect(result.elements[0].src).toBe("/compositions/abc123");
Expand All @@ -76,20 +76,20 @@ describe("parseHtml", () => {

expect(result.elements).toHaveLength(3);

const video = result.elements.find((e) => e.id === "vid1");
const video = result.elements.find((e) => e.type === "video");
expect(video).toBeDefined();
expect(video?.type).toBe("video");
expect(video?.id).toMatch(/^hf-[a-z0-9]{4}$/);
if (video?.type === "video") {
expect(video.src).toBe("video.mp4");
}

const audio = result.elements.find((e) => e.id === "aud1");
const audio = result.elements.find((e) => e.type === "audio");
expect(audio).toBeDefined();
expect(audio?.type).toBe("audio");
expect(audio?.id).toMatch(/^hf-[a-z0-9]{4}$/);

const img = result.elements.find((e) => e.id === "img1");
const img = result.elements.find((e) => e.type === "image");
expect(img).toBeDefined();
expect(img?.type).toBe("image");
expect(img?.id).toMatch(/^hf-[a-z0-9]{4}$/);
});

it("handles missing attributes gracefully", () => {
Expand Down Expand Up @@ -123,7 +123,7 @@ describe("parseHtml", () => {
const result = parseHtml(html);

expect(result.elements).toHaveLength(1);
expect(result.elements[0].id).toMatch(/^element-\d+$/);
expect(result.elements[0].id).toMatch(/^hf-[a-z0-9]{4}$/);
});

it("extracts GSAP script from script tags", () => {
Expand Down Expand Up @@ -398,9 +398,12 @@ describe("parseHtml", () => {
`;
const result = parseHtml(html);

expect(result.keyframes["text1"]).toBeDefined();
expect(result.keyframes["text1"]).toHaveLength(2);
expect(result.keyframes["text1"][0].id).toBe("kf1");
const elId = result.elements[0]?.id ?? "";
expect(elId).toMatch(/^hf-[a-z0-9]{4}$/);
const kfs = result.keyframes[elId];
expect(kfs).toBeDefined();
expect(kfs).toHaveLength(2);
expect(kfs[0].id).toBe("kf1");
});

it("parses stage zoom keyframes", () => {
Expand Down
47 changes: 42 additions & 5 deletions packages/core/src/studio-api/helpers/sourceMutation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,50 @@ describe("probeElementInSource", () => {
// Covers the same surface as T3 (Studio sourcePatcher) — Core sourceMutation supports
// all patch types (inline-style, attribute, text-content) via patchElementInHtml.
describe("T7 — data-hf-id targeting (spec for R1)", () => {
it.todo("updates inline style by data-hf-id when no HTML id attribute is present");
it("updates inline style by data-hf-id when no HTML id attribute is present", () => {
const source = `<h1 data-hf-id="hf-x7k2" style="color: red">Hello</h1>`;
const { html, matched } = patchElementInHtml(source, { hfId: "hf-x7k2" }, [
{ type: "inline-style", property: "color", value: "blue" },
]);
expect(matched).toBe(true);
expect(html).toMatch(/color:\s*blue/);
expect(html).toContain('data-hf-id="hf-x7k2"');
});

it.todo("updates text content by data-hf-id");
it("updates text content by data-hf-id", () => {
const source = `<p data-hf-id="hf-a1b2">Old text</p>`;
const { html, matched } = patchElementInHtml(source, { hfId: "hf-a1b2" }, [
{ type: "text-content", property: "", value: "New text" },
]);
expect(matched).toBe(true);
expect(html).toContain("New text");
});

it.todo("updates attribute by data-hf-id");
it("updates attribute by data-hf-id", () => {
const source = `<div data-hf-id="hf-c3d4" data-start="0"></div>`;
const { html, matched } = patchElementInHtml(source, { hfId: "hf-c3d4" }, [
{ type: "attribute", property: "start", value: "2.5" },
]);
expect(matched).toBe(true);
expect(html).toContain('data-start="2.5"');
});

it.todo("data-hf-id attribute survives the patch (can be targeted again)");
it("data-hf-id attribute survives the patch (can be targeted again)", () => {
const source = `<h1 data-hf-id="hf-x7k2" style="color: red">Hello</h1>`;
const { html } = patchElementInHtml(source, { hfId: "hf-x7k2" }, [
{ type: "inline-style", property: "color", value: "blue" },
]);
expect(html).toContain('data-hf-id="hf-x7k2"');
});

it.todo("hfId lookup falls through to selector when hfId is not found in the document");
it("hfId lookup falls through to selector when hfId is not found in the document", () => {
const source = `<h1 class="headline" style="color: red">Hello</h1>`;
const { html, matched } = patchElementInHtml(
source,
{ hfId: "hf-missing", selector: ".headline" },
[{ type: "inline-style", property: "color", value: "blue" }],
);
expect(matched).toBe(true);
expect(html).toMatch(/color:\s*blue/);
});
});
8 changes: 7 additions & 1 deletion packages/core/src/studio-api/helpers/sourceMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { parseHTML } from "linkedom";

export interface SourceMutationTarget {
id?: string | null;
hfId?: string;
selector?: string;
selectorIndex?: number;
}
Expand Down Expand Up @@ -32,6 +33,11 @@ function querySelectorAllWithTemplates(root: Document | Element, selector: strin
}

function findTargetElement(document: Document, target: SourceMutationTarget): Element | null {
if (target.hfId) {
const matches = querySelectorAllWithTemplates(document, `[data-hf-id="${target.hfId}"]`);
if (matches[0]) return matches[0];
}

if (target.id) {
const byId = document.getElementById(target.id);
if (byId) return byId;
Expand Down Expand Up @@ -207,7 +213,7 @@ export function patchElementInHtml(
}

export function probeElementInSource(source: string, target: SourceMutationTarget): boolean {
if (!target.id && !target.selector) return false;
if (!target.id && !target.hfId && !target.selector) return false;
const { document } = parseSourceDocument(source);
const el = findTargetElement(document, target);
return el != null && isHTMLElement(el);
Expand Down
Loading