Files
mokosh/tests/build/fonts-present.test.ts
Mark 34a9ce10d4 test(01-12): wave-0 — scaffold RED unit tests (tokens / fonts / icons / no-remote-fonts / manifest-i18n / locale-parity)
Wave 0 of the design-integration plan. Six new test files at tests/build/
and tests/i18n/ pin the contracts that later waves will GREEN:

- tokens-adopted.test.ts (4 cases): src/shared/tokens.css exists +
  parses; src/popup/style.css @imports it; popup/style.css has zero
  hex literals; welcome.css conditional check.
- fonts-present.test.ts: 7 required WOFF2 faces (Lora normal + Plex Sans
  ×4 + Plex Mono ×2) + LICENSE-Lora + LICENSE-IBM-Plex + README +
  optional Lora-Italic (A5 verify-at-execute).
- icons-present.test.ts (15 cases across 3 sizes): existence, size FLOOR
  per assets-spec.md, PNG signature, dimensions, color-type byte === 6
  (RGBA — RED until Wave 2 rsvg-convert overwrites the 16-bit-RGB
  placeholders).
- no-remote-fonts.test.ts: production dist/ contains zero
  fonts.googleapis.com / https://fonts / googleapis substrings (MV3 CSP
  self-host invariant T-01-12-01).
- manifest-i18n.test.ts (10 cases): manifest:name === '__MSG_extName__',
  :description === '__MSG_extDesc__', :default_locale === 'en',
  :action.default_title === '__MSG_tooltipOff__'; _locales/{en,ru}/
  messages.json carry D-07 + D-08 canonical strings.
- locale-parity.test.ts (4 cases): ru→en parity, en→ru symmetric,
  non-empty .message strings (RESEARCH Pitfall 4 mitigation).

Current polarity: 29 RED + 18 GREEN across the 6 new files (placeholders
already clear dim+size floors; no-remote-fonts vacuous-GREEN since
tokens.css doesn't yet exist with remote URLs). Existing 100/100 vitest
baseline preserved (verified SKIP_BUILD=1 npx vitest run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:56:08 +02:00

108 lines
4.5 KiB
TypeScript

// tests/build/fonts-present.test.ts — Plan 01-12 Wave 0 RED unit test.
//
// Asserts that the self-hosted WOFF2 font bundle lives at src/shared/fonts/
// per D-05 typography pairing + R2 Lora substitution (designer reply
// 2026-05-19; Newsreader → Lora for Cyrillic coverage).
//
// Polarity at Wave 0 land: RED across the board (src/shared/fonts/ does
// not exist). Flips GREEN after Wave 1 Task 1 lands the WOFF2 bundle.
//
// NOTE on Lora italic: per RESEARCH §A5, if upstream Lora variable file
// ships italic via the `ital` axis (single multi-axis file), the
// `Lora-Italic-VariableFont.woff2` file is omitted and the @font-face
// rule in tokens.css uses `font-style: oblique 0deg 14deg`. This test
// permits both layouts via the OPTIONAL_FILES set.
import { describe, expect, it } from 'vitest';
import { existsSync, readFileSync, statSync } from 'node:fs';
import { resolve as resolvePath } from 'node:path';
const FONTS_DIR = resolvePath(process.cwd(), 'src/shared/fonts');
const TOKENS_CSS_PATH = resolvePath(process.cwd(), 'src/shared/tokens.css');
/** Required WOFF2 faces — Plex Sans + Plex Mono fixed weights + Lora normal */
const REQUIRED_FONT_FILES: ReadonlyArray<string> = [
'Lora-VariableFont.woff2',
'IBMPlexSans-Regular.woff2',
'IBMPlexSans-Medium.woff2',
'IBMPlexSans-SemiBold.woff2',
'IBMPlexSans-Bold.woff2',
'IBMPlexMono-Regular.woff2',
'IBMPlexMono-Medium.woff2',
];
/** Optional faces — A5 verify: present iff upstream ships separate italic file */
const OPTIONAL_FONT_FILES: ReadonlyArray<string> = [
'Lora-Italic-VariableFont.woff2',
];
/** Companion artifacts (LICENSE + README) */
const REQUIRED_LICENSE_FILES: ReadonlyArray<string> = [
'LICENSE-Lora.txt',
'LICENSE-IBM-Plex.txt',
'README.md',
];
/** WOFF2 magic bytes (RFC 8081): 'wOF2' */
const WOFF2_SIGNATURE = Buffer.from([0x77, 0x4f, 0x46, 0x32]);
describe('Plan 01-12: self-hosted OFL font bundle (Lora + Plex Sans + Plex Mono)', () => {
describe('required WOFF2 faces', () => {
for (const name of REQUIRED_FONT_FILES) {
it(`src/shared/fonts/${name} exists + non-empty + WOFF2 signature`, () => {
const filePath = resolvePath(FONTS_DIR, name);
expect(existsSync(filePath), `Expected ${filePath} (RED until Wave 1 Task 1)`).toBe(true);
const size = statSync(filePath).size;
expect(size, `${name} must be non-empty`).toBeGreaterThan(0);
const head = Buffer.alloc(4);
const fd = require('node:fs').openSync(filePath, 'r');
try {
require('node:fs').readSync(fd, head, 0, 4, 0);
} finally {
require('node:fs').closeSync(fd);
}
expect(
head.equals(WOFF2_SIGNATURE),
`${name} signature must be 'wOF2' (0x77 0x4F 0x46 0x32); got ${head.toString('hex')}`,
).toBe(true);
});
}
});
it('tokens.css references every required WOFF2 file by filename', () => {
if (!existsSync(TOKENS_CSS_PATH)) {
// Will be RED until Wave 1 Task 2 lands tokens.css. Surface a clear diagnostic
// rather than letting downstream substring assertions fail with confusing output.
throw new Error(`src/shared/tokens.css missing — Wave 1 Task 2 lands this file`);
}
const css = readFileSync(TOKENS_CSS_PATH, 'utf8');
for (const name of REQUIRED_FONT_FILES) {
expect(
css.includes(name),
`tokens.css must reference ${name} in a @font-face src url(); RED until Wave 1 Task 2 lands the @font-face block`,
).toBe(true);
}
});
it('LICENSE-Lora.txt + LICENSE-IBM-Plex.txt + README.md exist (OFL attribution)', () => {
for (const name of REQUIRED_LICENSE_FILES) {
const filePath = resolvePath(FONTS_DIR, name);
expect(existsSync(filePath), `Expected ${filePath} (RED until Wave 1 Task 1)`).toBe(true);
expect(statSync(filePath).size, `${name} must be non-empty`).toBeGreaterThan(0);
}
});
it('optional Lora-Italic-VariableFont.woff2 — present iff upstream ships separate italic file', () => {
// A5 in RESEARCH Assumptions Log: variable Lora may consolidate italic
// via the `ital` axis. This test is informational — passes either way.
for (const name of OPTIONAL_FONT_FILES) {
const filePath = resolvePath(FONTS_DIR, name);
if (existsSync(filePath)) {
expect(statSync(filePath).size).toBeGreaterThan(0);
}
// If absent, the README.md should document the consolidation (verified
// empirically at execute time; not asserted here to avoid coupling).
}
});
});