Files
2026-07-13 12:22:50 +08:00

167 lines
5.6 KiB
JavaScript

const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
const SCRIPT_PATH = path.join(__dirname, '..', 'verify-fonts.js');
const ROOT = path.join(__dirname, '..', '..');
const TYPOGRAPHY_PATH = path.join(
ROOT,
'src',
'theme',
'tokens',
'typography.ts',
);
const THEME_PATH = path.join(ROOT, 'src', 'utils', 'theme.ts');
const ASSETS_DIR = path.join(ROOT, 'src', 'assets', 'fonts');
const ANDROID_DIR = path.join(
ROOT,
'android',
'app',
'src',
'main',
'assets',
'fonts',
);
const INFO_PLIST = path.join(ROOT, 'ios', 'PocketPal', 'Info.plist');
/**
* Run verify-fonts.js against a temp workspace. The temp workspace
* mirrors the relevant inputs (typography.ts, theme.ts, fonts dirs,
* Info.plist) so we can introduce mismatches without touching the real
* project files (which would race with parallel Jest workers).
*/
function runWithOverrides(overrides = {}) {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-fonts-test-'));
try {
// Replicate the directory layout the script reads.
fs.mkdirSync(path.join(tmp, 'src', 'theme', 'tokens'), {recursive: true});
fs.mkdirSync(path.join(tmp, 'src', 'utils'), {recursive: true});
fs.mkdirSync(path.join(tmp, 'src', 'assets', 'fonts'), {recursive: true});
fs.mkdirSync(
path.join(tmp, 'android', 'app', 'src', 'main', 'assets', 'fonts'),
{recursive: true},
);
fs.mkdirSync(path.join(tmp, 'ios', 'PocketPal'), {recursive: true});
fs.copyFileSync(
TYPOGRAPHY_PATH,
path.join(tmp, 'src', 'theme', 'tokens', 'typography.ts'),
);
fs.copyFileSync(THEME_PATH, path.join(tmp, 'src', 'utils', 'theme.ts'));
for (const f of fs.readdirSync(ASSETS_DIR)) {
fs.copyFileSync(
path.join(ASSETS_DIR, f),
path.join(tmp, 'src', 'assets', 'fonts', f),
);
}
for (const f of fs.readdirSync(ANDROID_DIR)) {
fs.copyFileSync(
path.join(ANDROID_DIR, f),
path.join(tmp, 'android', 'app', 'src', 'main', 'assets', 'fonts', f),
);
}
fs.copyFileSync(
INFO_PLIST,
path.join(tmp, 'ios', 'PocketPal', 'Info.plist'),
);
// Apply overrides — relative paths.
for (const [relPath, content] of Object.entries(overrides.files || {})) {
const dest = path.join(tmp, relPath);
fs.mkdirSync(path.dirname(dest), {recursive: true});
fs.writeFileSync(dest, content, 'utf-8');
}
for (const relPath of overrides.remove || []) {
const target = path.join(tmp, relPath);
if (fs.existsSync(target)) {
fs.unlinkSync(target);
}
}
// Patch the script to point at the temp workspace.
let scriptSrc = fs.readFileSync(SCRIPT_PATH, 'utf-8');
scriptSrc = scriptSrc.replace(
/const ROOT = .+;/,
`const ROOT = ${JSON.stringify(tmp)};`,
);
const tmpScript = path.join(tmp, 'verify-fonts.js');
fs.writeFileSync(tmpScript, scriptSrc, 'utf-8');
try {
const output = execSync(`node "${tmpScript}" 2>&1`, {
encoding: 'utf-8',
timeout: 10000,
});
return {exitCode: 0, output};
} catch (e) {
return {
exitCode: e.status,
output: (e.stdout || '') + (e.stderr || ''),
};
}
} finally {
fs.rmSync(tmp, {recursive: true, force: true});
}
}
describe('verify-fonts.js', () => {
it('passes against the current worktree font set', () => {
const result = runWithOverrides();
expect(result.exitCode).toBe(0);
expect(result.output).toContain('Inter-Regular');
expect(result.output).toContain('Fraunces-Regular');
expect(result.output).toContain('JetBrainsMono-Regular');
expect(result.output).toContain('all families bundled on all platforms');
});
it('fails when a TTF is missing from src/assets/fonts', () => {
const result = runWithOverrides({
remove: ['src/assets/fonts/Fraunces-Regular.ttf'],
});
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain(
'Fraunces-Regular: missing src/assets/fonts/Fraunces-Regular.ttf',
);
});
it('fails when a TTF is missing from android assets', () => {
const result = runWithOverrides({
remove: ['android/app/src/main/assets/fonts/JetBrainsMono-Medium.ttf'],
});
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain(
'JetBrainsMono-Medium: missing android/app/src/main/assets/fonts/JetBrainsMono-Medium.ttf',
);
});
it('fails when a font is missing from ios UIAppFonts', () => {
// Strip the JetBrainsMono entries from the plist.
const plistSrc = fs.readFileSync(INFO_PLIST, 'utf-8');
const stripped = plistSrc
.split('\n')
.filter(line => !line.includes('JetBrainsMono'))
.join('\n');
const result = runWithOverrides({
files: {'ios/PocketPal/Info.plist': stripped},
});
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain(
'JetBrainsMono-Regular: missing <string>JetBrainsMono-Regular.ttf</string>',
);
});
it('fails when typography.ts references an unknown family', () => {
// Inject a reference to 'Comic-Sans-Regular' (or any name) by
// appending a fake constant assignment. The script's regex will pick
// it up; no asset will satisfy it.
const src = fs.readFileSync(TYPOGRAPHY_PATH, 'utf-8');
const injected = src + "\nconst _ignored = 'ComicSans-Regular';\n";
const result = runWithOverrides({
files: {'src/theme/tokens/typography.ts': injected},
});
expect(result.exitCode).not.toBe(0);
expect(result.output).toContain('ComicSans-Regular');
});
});