/** * AD-2.5-T1 — typography regression lock (RFC-WS-PRIMEVUE-PLAN-2-5). * * Reads the font-config sources directly (file-content inspection) rather * than relying on getComputedStyle in jsdom — jsdom does not cascade * from imported stylesheets, so a runtime check would always pass. * * Two files are inspected: * 1. apps/app/src/assets/styles/tailwind.css — Tailwind v4 @theme * --font-sans token + the cascade-level html/body font-family + * --crewli-font-family CSS variable. * 2. apps/app/src/@core/scss/template/libs/vuetify/_variables.scss — * the vendored Vuexy SCSS variable $font-family-custom that feeds * Vuetify's $body-font-family on legacy surfaces. * * If either file is moved, update the path constants below. */ import { existsSync, readFileSync, readdirSync } from 'node:fs' import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' const TAILWIND_CSS_PATH = resolve( __dirname, '../../../src/assets/styles/tailwind.css', ) const VUEXY_VARIABLES_PATH = resolve( __dirname, '../../../src/@core/scss/template/libs/vuetify/_variables.scss', ) const PLUGINS_DIR = resolve(__dirname, '../../../src/plugins') const tailwindCss = readFileSync(TAILWIND_CSS_PATH, 'utf-8') const vuexyVariables = readFileSync(VUEXY_VARIABLES_PATH, 'utf-8') /** * All TypeScript plugin files under `apps/app/src/plugins/`, flat-only (the * `registerPlugins` glob also picks up `plugins/*\/index.ts`, but those * sub-plugins are folder-namespaced and unlikely to load fonts — flat * coverage matches where webfontloader.ts lived and where any future * font-loading regression would most likely surface). */ const pluginFiles: Array<{ path: string; content: string }> = existsSync(PLUGINS_DIR) ? readdirSync(PLUGINS_DIR) .filter(f => f.endsWith('.ts')) .map(f => ({ path: f, content: readFileSync(resolve(PLUGINS_DIR, f), 'utf-8') })) : [] /** * Pulls every `font-family: …;` or `--font: …;` declaration * from a CSS source. Skips `var(...)` indirections — those are pointers * to other declarations that are themselves asserted directly. */ function fontDeclarations(css: string): string[] { // `[^;]+` already accepts leading whitespace; explicit `\s*` between // the colon and the value would overlap with it and trip // regexp/no-super-linear-backtracking. We `.trim()` later. const matches = css.match(/(?:font-family|--[\w-]*font[\w-]*):([^;]+);/gi) return (matches ?? []).filter(decl => !/\bvar\(/.test(decl)) } function firstFamily(decl: string): string { return decl .split(':')[1] .split(',')[0] .trim() .replace(/['"]/g, '') } describe('Typography regression lock (AD-2.5-T1)', () => { it('tailwind.css declares Inter as the first font in every direct stack', () => { const decls = fontDeclarations(tailwindCss) expect(decls.length).toBeGreaterThan(0) for (const decl of decls) expect(firstFamily(decl).toLowerCase()).toBe('inter') }) it('Vuexy $font-family-custom declares Inter as the first family', () => { // SCSS variable shape — `$font-family-custom: "Inter", …;` (spans // two lines in the source; the regex eats the value up to the `;`). const match = vuexyVariables.match( // Same backtracking-avoidance as fontDeclarations above: drop \s*. /\$font-family-custom:([^;]+);/i, ) expect(match).not.toBeNull() const family = match![1] .split(',')[0] .trim() .replace(/['"]/g, '') expect(family.toLowerCase()).toBe('inter') }) it('no Public Sans reference survives in either font-config file', () => { expect(tailwindCss).not.toMatch(/Public Sans/i) expect(tailwindCss).not.toMatch(/public-sans/i) expect(vuexyVariables).not.toMatch(/Public Sans/i) expect(vuexyVariables).not.toMatch(/public-sans/i) }) }) /** * AD-2.5-T1 completion (P2-followup): the JS font-loading path. * * P2 inspected CSS only and missed `src/plugins/webfontloader.ts`, which * loaded Public Sans from the Google Fonts CDN via WebFont.load(). This * block locks the JS path so the same drift cannot recur: no plugin may * load fonts via webfontloader, the Google Fonts CDN, or reference * Public Sans by any spelling. */ describe('Typography regression lock — JS font-loading path (AD-2.5-T1 completion)', () => { it('no plugin loads fonts via webfontloader / Google Fonts CDN / Public Sans', () => { for (const { path, content } of pluginFiles) { expect(content, `${path} references webfontloader`).not.toMatch(/webfontloader/i) expect(content, `${path} calls WebFont.load`).not.toMatch(/WebFont\.load/) expect(content, `${path} references Public Sans`).not.toMatch(/Public.?Sans/i) expect(content, `${path} references fonts.googleapis.com`).not.toMatch(/fonts\.googleapis\.com/) } }) it('webfontloader.ts plugin no longer exists', () => { expect(existsSync(resolve(PLUGINS_DIR, 'webfontloader.ts'))).toBe(false) }) })