mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-07 15:27:14 +00:00
* fix(catalog): match preview length to authored compositions * fix(catalog): derive undeclared preview duration from motion
150 lines
5.2 KiB
TypeScript
150 lines
5.2 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { existsSync, readFileSync, readdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
import { resolve, join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { prepareProjectDir } from "./generate-catalog-previews.js";
|
|
import { after, describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const repoRoot = resolve(import.meta.dirname, "..");
|
|
const previewPath = resolve(repoRoot, "docs/images/catalog/blocks/thread-message-stack.png");
|
|
|
|
after(() => rmSync(previewPath, { force: true }));
|
|
|
|
describe("catalog preview raw block boundary", () => {
|
|
it("renders thread-message-stack through the exact Catalog Previews command", () => {
|
|
rmSync(previewPath, { force: true });
|
|
const result = spawnSync(
|
|
"bunx",
|
|
[
|
|
"tsx",
|
|
"scripts/generate-catalog-previews.ts",
|
|
"--only",
|
|
"thread-message-stack",
|
|
"--skip-video",
|
|
],
|
|
{ cwd: repoRoot, encoding: "utf8", timeout: 120_000 },
|
|
);
|
|
const output = `${result.stdout}${result.stderr}`;
|
|
|
|
assert.equal(result.status, 0, output);
|
|
assert.ok(!output.includes("✗ thread-message-stack"), output);
|
|
assert.ok(!output.includes("[Browser:ERROR]"), output);
|
|
assert.ok(!output.includes("timelines not registered"), output);
|
|
assert.ok(!output.includes("sub_timeline_readiness_timeout"), output);
|
|
assert.ok(output.includes("✓ thread-message-stack.png"), output);
|
|
assert.ok(existsSync(previewPath), previewPath);
|
|
});
|
|
});
|
|
|
|
async function preparedHtml(sourceDir: string, entryFile: string): Promise<string> {
|
|
const projectDir = await prepareProjectDir(
|
|
{ name: "duration-test", kind: "component", sourceDir, entryFile },
|
|
{ compile: false, hostedAssets: "cdn", uiFragment: true },
|
|
);
|
|
try {
|
|
return readFileSync(join(projectDir, "index.html"), "utf8");
|
|
} finally {
|
|
rmSync(projectDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe("catalog wrapper duration", () => {
|
|
it("cleans its temporary project when reading the authored entry fails", async () => {
|
|
const name = `duration-error-${process.pid}`;
|
|
const sourceDir = mkdtempSync(join(tmpdir(), "catalog-duration-source-"));
|
|
const projects = () =>
|
|
readdirSync(tmpdir()).filter((entry) => entry.startsWith(`hf-catalog-${name}-`));
|
|
const before = projects();
|
|
try {
|
|
await assert.rejects(
|
|
prepareProjectDir(
|
|
{ name, kind: "component", sourceDir, entryFile: "missing.html" },
|
|
{ compile: false, hostedAssets: "cdn" },
|
|
),
|
|
{ code: "ENOENT" },
|
|
);
|
|
assert.deepEqual(projects(), before);
|
|
} finally {
|
|
rmSync(sourceDir, { recursive: true, force: true });
|
|
for (const project of projects())
|
|
if (!before.includes(project))
|
|
rmSync(join(tmpdir(), project), { recursive: true, force: true });
|
|
}
|
|
});
|
|
it("uses Count Up's authored three seconds for the wrapper and mounted snippet", async () => {
|
|
const html = await preparedHtml(
|
|
join(repoRoot, "registry/components/count-up"),
|
|
"count-up.html",
|
|
);
|
|
assert.deepEqual(
|
|
[...html.matchAll(/data-duration="([^"]+)"/g)].map((m) => m[1]),
|
|
["3", "3"],
|
|
);
|
|
});
|
|
|
|
it("lets Touch Indicator's registered timeline determine its preview length", async () => {
|
|
const html = await preparedHtml(
|
|
join(repoRoot, "registry/components/touch-indicator"),
|
|
"touch-indicator.html",
|
|
);
|
|
assert.ok(!html.includes("data-duration="));
|
|
});
|
|
|
|
for (const fixture of [
|
|
{
|
|
name: "root duration before a shorter child",
|
|
html: '<div data-composition-id="root" data-duration="2.5"><div data-duration="1"></div></div>',
|
|
manifest: 8,
|
|
expected: "2.5",
|
|
},
|
|
{
|
|
name: "composition duration on a template",
|
|
html: "<template data-composition-id='root' data-composition-duration='6.25'></template>",
|
|
manifest: 2,
|
|
expected: "6.25",
|
|
},
|
|
{
|
|
name: "authored duration inside a template",
|
|
html: '<template><div data-composition-id="root" data-duration="2.5"></div></template>',
|
|
manifest: 8,
|
|
expected: "2.5",
|
|
},
|
|
{
|
|
name: "manifest duration when none is authored",
|
|
html: '<div data-composition-id="root"></div>',
|
|
manifest: 7,
|
|
expected: "7",
|
|
},
|
|
{
|
|
name: "leaves duration for the runtime when none is declared",
|
|
html: '<div data-composition-id="root"></div>',
|
|
expected: null,
|
|
},
|
|
{
|
|
name: "invalid authored duration",
|
|
html: '<div data-composition-id="root" data-duration="3seconds"></div>',
|
|
manifest: 7,
|
|
expected: "7",
|
|
},
|
|
]) {
|
|
it(fixture.name, async () => {
|
|
const sourceDir = mkdtempSync(join(tmpdir(), "catalog-duration-"));
|
|
try {
|
|
writeFileSync(
|
|
join(sourceDir, "registry-item.json"),
|
|
JSON.stringify({ duration: fixture.manifest }),
|
|
);
|
|
writeFileSync(join(sourceDir, "entry.html"), fixture.html);
|
|
const html = await preparedHtml(sourceDir, "entry.html");
|
|
assert.deepEqual(
|
|
[...html.matchAll(/data-duration="([^"]+)"/g)].map((m) => m[1]),
|
|
fixture.expected === null ? [] : [fixture.expected, fixture.expected],
|
|
);
|
|
} finally {
|
|
rmSync(sourceDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
}
|
|
});
|