/** * 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 { readFileSync } 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 tailwindCss = readFileSync(TAILWIND_CSS_PATH, 'utf-8') const vuexyVariables = readFileSync(VUEXY_VARIABLES_PATH, '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) }) })