fix(registry): caption components keep the last phrase until its last word ends (#6792)

* fix(registry): caption components keep the last phrase until its last word ends

* test(registry): caption last group stays visible until its last word ends

* test(registry): narrow the timeline lookup for strict typecheck
This commit is contained in:
Miguel Ángel
2026-10-30 06:07:40 +00:00
committed by GitHub
parent b3c47a36a2
commit 32c01c99a1
6 changed files with 90 additions and 4 deletions
@@ -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(
/<script src="https:[^"]*gsap[^"]*"><\/script>/,
`<script>${gsapScript}</script>`,
);
}
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<boolean> {
const page = await browser.newPage();
try {
await page.setContent(stretchedComponent());
return await page.evaluate((t) => {
const timeline = (
window as unknown as { __timelines: Record<string, { seek(t: number): void }> }
).__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);
});
});
@@ -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[] = [];
@@ -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 };
});
@@ -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 };
});
@@ -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 };
});
@@ -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 };
});