mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
* fix(studio): preserve composition playback continuity * feat(studio): drag compositions into the timeline * fix(studio): collapse expanded composition move aliases * fix(studio): make timeline cuts atomic * fix(studio): group inspector gesture history * test(studio): cover masked text selection * fix(studio): harden composition timeline reliability * fix(studio): satisfy CI source gates * fix(studio): harden composition mutation requests
106 lines
4.4 KiB
TypeScript
106 lines
4.4 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { patchElementInHtml } from "@hyperframes/studio-server/source-mutation";
|
|
import { describe, expect, it } from "vitest";
|
|
import { buildDomEditStylePatchOperation } from "./domEditing";
|
|
|
|
const fixtureDir = join(
|
|
dirname(fileURLToPath(import.meta.url)),
|
|
"../../../tests/e2e/fixtures/composition-reliability",
|
|
);
|
|
|
|
function fixture(path: string): string {
|
|
return readFileSync(join(fixtureDir, path), "utf8");
|
|
}
|
|
|
|
function parse(html: string): Document {
|
|
return new DOMParser().parseFromString(html, "text/html");
|
|
}
|
|
|
|
function inTemplate(document: Document, selector: string): Element | null {
|
|
for (const template of Array.from(document.querySelectorAll("template"))) {
|
|
const match = template.content.querySelector(selector);
|
|
if (match) return match;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
describe("composition reliability acceptance fixture", () => {
|
|
const indexSource = fixture("index.html");
|
|
const titleSource = fixture("compositions/title-card.html");
|
|
const nestedSource = fixture("compositions/nested-shell.html");
|
|
|
|
it("owns repeated root hosts, a nested host, transparent headline topology, and collisions", () => {
|
|
const index = parse(indexSource);
|
|
const repeated = Array.from(
|
|
index.querySelectorAll('[data-composition-src="compositions/title-card.html"]'),
|
|
);
|
|
expect(repeated).toHaveLength(2);
|
|
expect(repeated.map((host) => host.getAttribute("data-start"))).toEqual(["0", "4"]);
|
|
expect(
|
|
index.querySelector('[data-composition-src="compositions/nested-shell.html"]'),
|
|
).toBeTruthy();
|
|
|
|
const nested = parse(nestedSource);
|
|
const nestedHost = inTemplate(nested, '[data-composition-src="title-card.html"]');
|
|
expect(nestedHost).toBeTruthy();
|
|
const nestedDependency = nestedHost?.getAttribute("data-composition-src");
|
|
expect(
|
|
nestedDependency ? existsSync(resolve(fixtureDir, "compositions", nestedDependency)) : false,
|
|
).toBe(true);
|
|
|
|
const title = parse(titleSource);
|
|
const mask = inTemplate(title, ".hl-mask");
|
|
const headline = inTemplate(title, ".hl-mask > .hl-text");
|
|
expect(mask?.textContent).toContain("Reliable compositions");
|
|
expect(inTemplate(title, "style")?.textContent).toMatch(
|
|
/\.hl-mask\s*\{[^}]*overflow:\s*hidden;[^}]*background:\s*transparent;/,
|
|
);
|
|
expect(headline?.tagName).toBe("H1");
|
|
|
|
const collisionA = index.querySelector('[data-hf-id="collision-a"]')!;
|
|
const collisionB = index.querySelector('[data-hf-id="collision-b"]')!;
|
|
const layered = index.querySelector('[data-hf-id="layer-overlap"]')!;
|
|
expect(collisionA.getAttribute("data-track-index")).toBe(
|
|
collisionB.getAttribute("data-track-index"),
|
|
);
|
|
expect(
|
|
Number(collisionA.getAttribute("data-start")) +
|
|
Number(collisionA.getAttribute("data-duration")),
|
|
).toBe(Number(collisionB.getAttribute("data-start")));
|
|
expect(layered.getAttribute("data-start")).toBe(collisionB.getAttribute("data-start"));
|
|
expect(layered.getAttribute("data-track-index")).not.toBe(
|
|
collisionB.getAttribute("data-track-index"),
|
|
);
|
|
});
|
|
|
|
it("keeps timeline host edits in the root source and headline color in the template source", () => {
|
|
const moved = patchElementInHtml(indexSource, { hfId: "title-host-a" }, [
|
|
{ type: "attribute", property: "start", value: "5" },
|
|
{ type: "attribute", property: "duration", value: "2" },
|
|
]);
|
|
expect(moved.matched).toBe(true);
|
|
expect(moved.html).toContain('data-hf-id="title-host-a"');
|
|
expect(moved.html).toContain('data-start="5"');
|
|
expect(moved.html).toContain('data-duration="2"');
|
|
expect(moved.html).not.toContain('data-hf-id="title-text"');
|
|
expect(titleSource).not.toContain("#12b886");
|
|
|
|
const recolored = patchElementInHtml(titleSource, { hfId: "title-text" }, [
|
|
buildDomEditStylePatchOperation("color", "#12b886"),
|
|
]);
|
|
expect(recolored.matched).toBe(true);
|
|
const recoloredDocument = parse(recolored.html);
|
|
expect(
|
|
inTemplate(recoloredDocument, '[data-hf-id="title-text"]')?.getAttribute("style"),
|
|
).toContain("color: #12b886");
|
|
expect(
|
|
inTemplate(recoloredDocument, '[data-hf-id="title-mask"]')?.getAttribute("style"),
|
|
).toBeNull();
|
|
expect(recolored.html).not.toContain('data-hf-id="title-host-a"');
|
|
expect(indexSource).not.toContain("#12b886");
|
|
});
|
|
});
|