mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(cli): localize remote fonts in snapshot/check capture to match render
This commit is contained in:
@@ -193,11 +193,13 @@ async function captureSnapshots(
|
||||
zoomScale?: number;
|
||||
},
|
||||
): Promise<string[]> {
|
||||
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
|
||||
const { bundleWithLocalizedFonts } = await import("../utils/bundleWithLocalizedFonts.js");
|
||||
|
||||
const numFrames = opts.frames ?? 5;
|
||||
|
||||
const html = await bundleToSingleHtml(projectDir);
|
||||
// Localize fonts (embed remote @font-face as data URIs, matching the render
|
||||
// path) so snapshots render the real font instead of a fallback sans.
|
||||
const html = await bundleWithLocalizedFonts(projectDir);
|
||||
const server = await serveStaticProjectHtml(projectDir, html);
|
||||
|
||||
const savedPaths: string[] = [];
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@hyperframes/core/compiler", () => ({
|
||||
bundleToSingleHtml: vi.fn(async () => "<html><body>bundled</body></html>"),
|
||||
}));
|
||||
|
||||
const injectMock = vi.fn(async (html: string) => html.replace("bundled", "bundled+fonts"));
|
||||
vi.mock("@hyperframes/producer", () => ({
|
||||
injectDeterministicFontFaces: (html: string) => injectMock(html),
|
||||
}));
|
||||
|
||||
import { bundleWithLocalizedFonts } from "./bundleWithLocalizedFonts.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("bundleWithLocalizedFonts", () => {
|
||||
it("localizes fonts on top of the plain bundle", async () => {
|
||||
const html = await bundleWithLocalizedFonts("/project");
|
||||
expect(injectMock).toHaveBeenCalledOnce();
|
||||
expect(html).toBe("<html><body>bundled+fonts</body></html>");
|
||||
});
|
||||
|
||||
it("falls open to the plain bundle when font localization throws", async () => {
|
||||
injectMock.mockRejectedValueOnce(new Error("offline / fetch layer unavailable"));
|
||||
const html = await bundleWithLocalizedFonts("/project");
|
||||
// Never worse than a plain bundleToSingleHtml — the remote <link> still
|
||||
// loads at capture time as before.
|
||||
expect(html).toBe("<html><body>bundled</body></html>");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Bundle a project to a single HTML string AND localize its fonts — fetch and
|
||||
* embed `@font-face` rules for every requested family (including families
|
||||
* declared only via a remote `<link>`, e.g. Google Fonts) as data URIs.
|
||||
*
|
||||
* Why the audit/snapshot paths need this: core's `bundleToSingleHtml` inlines
|
||||
* only LOCAL stylesheets and leaves remote font `<link>`s as-is, so a snapshot
|
||||
* depends on loading the remote font at capture time. The render pipeline
|
||||
* instead localizes fonts in its compile stage, which is why a render embeds
|
||||
* (say) League Gothic correctly while a snapshot of the same composition can
|
||||
* fall back to an un-styled system sans when the remote font loses the race
|
||||
* against the capture. Running the SAME localization the render path uses makes
|
||||
* snapshot/check captures font-faithful and deterministic — no network race.
|
||||
*
|
||||
* Fail-open: if a family can't be fetched (offline, unknown font), the
|
||||
* underlying injector leaves the HTML unchanged, so this never makes a bundle
|
||||
* worse than plain `bundleToSingleHtml`.
|
||||
*/
|
||||
export async function bundleWithLocalizedFonts(projectDir: string): Promise<string> {
|
||||
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
|
||||
const html = await bundleToSingleHtml(projectDir);
|
||||
try {
|
||||
const { injectDeterministicFontFaces } = await import("@hyperframes/producer");
|
||||
return await injectDeterministicFontFaces(html);
|
||||
} catch {
|
||||
// Producer/font localization unavailable (or a fetch layer threw) — fall
|
||||
// back to the plain bundle. Fonts declared via remote <link> still load at
|
||||
// capture time as before; we just lose the deterministic embed.
|
||||
return html;
|
||||
}
|
||||
}
|
||||
@@ -88,8 +88,8 @@ export async function runBrowserCheck(
|
||||
motion: MotionSpecResolution,
|
||||
runGrid: RunAuditGrid,
|
||||
): Promise<CheckBrowserResult> {
|
||||
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
|
||||
const html = await bundleToSingleHtml(project.dir);
|
||||
const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts.js");
|
||||
const html = await bundleWithLocalizedFonts(project.dir);
|
||||
const server = await serveStaticProjectHtml(project.dir, html, "Failed to bind check server");
|
||||
const drafts: RuntimeDraft[] = [];
|
||||
let currentTime = 0;
|
||||
@@ -145,8 +145,8 @@ export async function captureFindingCrops(
|
||||
requests: CheckFindingCropRequest[],
|
||||
): Promise<string[]> {
|
||||
if (requests.length === 0) return [];
|
||||
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
|
||||
const html = await bundleToSingleHtml(project.dir);
|
||||
const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts.js");
|
||||
const html = await bundleWithLocalizedFonts(project.dir);
|
||||
const server = await serveStaticProjectHtml(project.dir, html, "Failed to bind check server");
|
||||
let chromeBrowser: import("puppeteer-core").Browser | undefined;
|
||||
const written: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user