diff --git a/packages/cli/src/registry/captionLastGroup.chromium.test.ts b/packages/cli/src/registry/captionLastGroup.chromium.test.ts new file mode 100644 index 0000000000..a602b862af --- /dev/null +++ b/packages/cli/src/registry/captionLastGroup.chromium.test.ts @@ -0,0 +1,71 @@ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import puppeteer, { type Browser } from "puppeteer-core"; + +const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH; +const componentHtml = readFileSync( + new URL( + "../../../../registry/components/caption-highlight/caption-highlight.html", + import.meta.url, + ), + "utf8", +); +const gsapScript = readFileSync( + createRequire(new URL("../../../core/package.json", import.meta.url)).resolve( + "gsap/dist/gsap.min.js", + ), + "utf8", +); + +/** Scaled demo words: the last group opens before the old 7.95 s cap and its last word ends after it. */ +const STRETCH = 1.15; +const LAST_WORD_START_S = 7.3 * STRETCH; +const LAST_WORD_END_S = 7.7 * STRETCH; + +function stretchedComponent(): string { + const wordsStart = componentHtml.indexOf("var WORDS = ["); + const wordsEnd = componentHtml.indexOf("];", wordsStart); + const words = componentHtml + .slice(wordsStart, wordsEnd) + .replace(/(start|end): ([\d.]+)/g, (_, key, value) => `${key}: ${Number(value) * STRETCH}`); + return (componentHtml.slice(0, wordsStart) + words + componentHtml.slice(wordsEnd)).replace( + /`, + ); +} + +describe.runIf(executablePath)("caption-highlight last group in Chromium", () => { + let browser: Browser; + + beforeAll(async () => { + browser = await puppeteer.launch({ executablePath, args: ["--no-sandbox"] }); + }); + + afterAll(async () => { + await browser?.close(); + }); + + async function lastGroupVisibleAt(time: number): Promise { + const page = await browser.newPage(); + try { + await page.setContent(stretchedComponent()); + return await page.evaluate((t) => { + const timeline = ( + window as unknown as { __timelines: Record } + ).__timelines["caption-highlight"]; + timeline!.seek(t); + const groups = document.querySelectorAll(".hl-group"); + const style = getComputedStyle(groups[groups.length - 1]!); + return style.visibility !== "hidden" && Number(style.opacity) > 0; + }, time); + } finally { + await page.close(); + } + } + + it("shows the last phrase from its first word to just before its last word ends", async () => { + expect(await lastGroupVisibleAt(LAST_WORD_START_S)).toBe(true); + expect(await lastGroupVisibleAt(LAST_WORD_END_S - 0.05)).toBe(true); + }); +}); diff --git a/packages/cli/src/registry/registryComponents.test.ts b/packages/cli/src/registry/registryComponents.test.ts index 4f3cfa1969..0352fa464b 100644 --- a/packages/cli/src/registry/registryComponents.test.ts +++ b/packages/cli/src/registry/registryComponents.test.ts @@ -56,6 +56,18 @@ describe("registry components", () => { expect(invalidMedia).toEqual([]); }); + it("never caps a caption's last group at a fixed demo-length constant", () => { + const capped: string[] = []; + + for (const entry of readdirSync(componentsDir, { withFileTypes: true })) { + if (!entry.isDirectory() || !entry.name.startsWith("caption-")) continue; + const html = readFileSync(join(componentsDir, entry.name, `${entry.name}.html`), "utf8"); + if (/nextStart\s*=[^;]*:\s*\d+(?:\.\d+)?;/.test(html)) capped.push(entry.name); + } + + expect(capped).toEqual([]); + }); + it("ships demos without source-less media", async () => { const invalidMedia: string[] = []; diff --git a/registry/components/caption-clip-wipe/caption-clip-wipe.html b/registry/components/caption-clip-wipe/caption-clip-wipe.html index 9b5d55e599..41d882b460 100644 --- a/registry/components/caption-clip-wipe/caption-clip-wipe.html +++ b/registry/components/caption-clip-wipe/caption-clip-wipe.html @@ -153,7 +153,8 @@ var GROUPS = RAW_GROUPS.map(function (pair, gi) { var ws = pair[0], we = pair[1]; - var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; + var nextStart = + gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : Infinity; var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05); return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; }); diff --git a/registry/components/caption-glitch-rgb/caption-glitch-rgb.html b/registry/components/caption-glitch-rgb/caption-glitch-rgb.html index e44a67674f..fe212755ec 100644 --- a/registry/components/caption-glitch-rgb/caption-glitch-rgb.html +++ b/registry/components/caption-glitch-rgb/caption-glitch-rgb.html @@ -204,7 +204,7 @@ var ws = pair[0]; var we = pair[1]; var nextStart = - gi + 1 < RAW_GROUPS.length ? TRANSCRIPT[RAW_GROUPS[gi + 1][0]].start : 8.0; + gi + 1 < RAW_GROUPS.length ? TRANSCRIPT[RAW_GROUPS[gi + 1][0]].start : Infinity; var end = Math.min(TRANSCRIPT[we].end + 0.24, nextStart - 0.04); return { wordStart: ws, wordEnd: we, start: TRANSCRIPT[ws].start, end: end }; }); diff --git a/registry/components/caption-highlight/caption-highlight.html b/registry/components/caption-highlight/caption-highlight.html index 2617005887..615084e01f 100644 --- a/registry/components/caption-highlight/caption-highlight.html +++ b/registry/components/caption-highlight/caption-highlight.html @@ -178,7 +178,8 @@ var GROUPS = RAW_GROUPS.map(function (pair, gi) { var ws = pair[0], we = pair[1]; - var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; + var nextStart = + gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : Infinity; var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05); return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; }); diff --git a/registry/components/caption-particle-burst/caption-particle-burst.html b/registry/components/caption-particle-burst/caption-particle-burst.html index 2df4604db8..15ab9c5f06 100644 --- a/registry/components/caption-particle-burst/caption-particle-burst.html +++ b/registry/components/caption-particle-burst/caption-particle-burst.html @@ -174,7 +174,8 @@ var GROUPS = RAW_GROUPS.map(function (pair, gi) { var ws = pair[0], we = pair[1]; - var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; + var nextStart = + gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : Infinity; var gEnd = Math.min(WORDS[we].end + 0.35, nextStart - 0.05); return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; });