import { describe, it, expect } from "vitest"; import { lintHyperframeHtml } from "../hyperframeLinter.js"; async function findByCode(html: string, code: string, isSubComposition = true) { const result = await lintHyperframeHtml(html, { isSubComposition }); return result.findings.filter((f) => f.code === code); } describe("font rules", () => { describe("google_fonts_import", () => { it("warns on @import url with fonts.googleapis.com without failing lint", async () => { const html = `
`; const result = await lintHyperframeHtml(html, { isSubComposition: true }); const findings = result.findings.filter((f) => f.code === "google_fonts_import"); expect(findings).toHaveLength(1); expect(findings[0]!.severity).toBe("warning"); expect(result.errorCount).toBe(0); }); it("warns on to fonts.googleapis.com", async () => { const html = `
`; const findings = await findByCode(html, "google_fonts_import"); expect(findings).toHaveLength(1); expect(findings[0]!.severity).toBe("warning"); }); it("does not flag local @font-face usage", async () => { const html = `
`; const findings = await findByCode(html, "google_fonts_import"); expect(findings).toHaveLength(0); }); it("does not flag installed registry blocks that bundle Google Fonts", async () => { const html = `\n` + `
`; const findings = await findByCode(html, "google_fonts_import"); expect(findings).toHaveLength(0); }); }); describe("system_font_will_alias", () => { it("flags SF Mono as aliased to JetBrains Mono", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(1); expect(findings[0]!.severity).toBe("info"); expect(findings[0]!.message).toContain("JetBrains Mono"); }); it("flags Helvetica Neue as aliased to Inter", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("Inter"); }); it("does not flag canonical font names", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(0); }); it("does not flag Roboto (canonical name)", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(0); }); it("does not flag unknown fonts (handled by font_family_without_font_face)", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(0); }); it("does not flag aliased fonts that have explicit @font-face", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(0); }); it("handles case-insensitive font names", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("Inter"); }); it("reports multiple aliased fonts in one finding", async () => { const html = `
`; const findings = await findByCode(html, "system_font_will_alias"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("Inter"); expect(findings[0]!.message).toContain("JetBrains Mono"); }); }); describe("font_family_without_font_face", () => { it("flags font-family used without @font-face", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("gt walsheim"); }); it("does not flag when @font-face is declared", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("does not flag a system font declared via @font-face src: local()", async () => { // Regression: two independent reports of this rule hard-erroring on OS // system fonts (Hiragino Sans, Microsoft YaHei) that have no downloadable // file. src: local(...) already satisfies the check (extractFontFaceFamilies // only looks at the font-family declaration, not the src value) — the gap // was that the fixHint didn't mention this as an option. const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("fixHint mentions the local() pattern for system fonts", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings[0]!.fixHint).toContain("local("); }); it("does not flag generic font families", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("reports multiple missing families in one finding", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("aeonik"); expect(findings[0]!.message).toContain("feature deck"); }); it("does not flag fonts the producer has pre-bundled", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("still flags Google-Fonts-only fonts not pre-bundled", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("geist"); }); it("does not flag a non-bundled family when a Google Fonts link loads it", async () => { const html = `
`; const result = await lintHyperframeHtml(html, { isSubComposition: true }); expect(result.findings.filter((f) => f.code === "google_fonts_import")).toHaveLength(1); expect( result.findings.filter((f) => f.code === "font_family_without_font_face"), ).toHaveLength(0); expect(result.errorCount).toBe(0); }); it("parses unquoted Google Fonts link href values", async () => { const html = `
`; const result = await lintHyperframeHtml(html, { isSubComposition: true }); expect(result.findings.filter((f) => f.code === "google_fonts_import")).toHaveLength(1); expect( result.findings.filter((f) => f.code === "font_family_without_font_face"), ).toHaveLength(0); expect(result.errorCount).toBe(0); }); it("parses multiple Google Fonts family parameters and URL-encoded spaces", async () => { const html = `
`; const result = await lintHyperframeHtml(html, { isSubComposition: true }); expect(result.findings.filter((f) => f.code === "google_fonts_import")).toHaveLength(1); expect( result.findings.filter((f) => f.code === "font_family_without_font_face"), ).toHaveLength(0); expect(result.errorCount).toBe(0); }); it("still flags non-bundled families not covered by the Google Fonts URL", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("geist"); }); it("is case-insensitive when matching @font-face to font-family", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("ignores font-family inside @font-face blocks", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("does not flag vendor-prefixed system-font keywords (-apple-system, BlinkMacSystemFont)", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("does not flag installed registry blocks that declare fonts via Google Fonts", async () => { const html = `\n` + `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("matches @font-face even when a CSS comment inside the block contains a brace (#1534)", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("does not flag the -apple-system / BlinkMacSystemFont system-ui stack", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("does not flag a var() font-family indirection it cannot resolve", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("does not flag a var() with a quoted fallback font", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(0); }); it("still flags a real undeclared font sitting next to a system stack", async () => { const html = `
`; const findings = await findByCode(html, "font_family_without_font_face"); expect(findings).toHaveLength(1); expect(findings[0]!.message).toContain("aeonik"); expect(findings[0]!.message).not.toContain("apple-system"); }); }); });