Files
NetMesh/lib/fontAvailability.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

225 lines
8.4 KiB
TypeScript

import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import {
extractPrimaryFamily,
detectInstalledWithContext,
isFontInstalled,
setSystemFamilies,
hasAuthoritativeData,
clearFontAvailabilityCache,
subscribeFontAvailability,
getFontAvailabilityVersion,
bundledFamiliesInStack,
} from './fontAvailability';
describe('bundledFamiliesInStack', () => {
it('returns bundled webfonts present in a composed stack, in order', () => {
const stack = 'Consolas, "JetBrains Mono", "Sarasa Mono SC", monospace';
assert.deepEqual(bundledFamiliesInStack(stack), ['JetBrains Mono', 'Sarasa Mono SC']);
});
it('returns nothing when only system fonts are referenced', () => {
assert.deepEqual(bundledFamiliesInStack('Menlo, monospace'), []);
});
it('ignores the generic monospace keyword and dedupes', () => {
const stack = '"JetBrains Mono", "JetBrains Mono", monospace';
assert.deepEqual(bundledFamiliesInStack(stack), ['JetBrains Mono']);
});
it('is case-insensitive on family names', () => {
assert.deepEqual(bundledFamiliesInStack('"jetbrains mono"'), ['JetBrains Mono']);
});
});
describe('extractPrimaryFamily', () => {
it('strips quotes from a quoted name', () => {
assert.equal(extractPrimaryFamily('"Fira Code", monospace'), 'Fira Code');
});
it('returns unquoted single-word names as-is', () => {
assert.equal(extractPrimaryFamily('Menlo, monospace'), 'Menlo');
});
it('returns the first family in a list', () => {
assert.equal(
extractPrimaryFamily('"Source Code Pro", "Fira Code", monospace'),
'Source Code Pro',
);
});
it('handles a single name without comma', () => {
assert.equal(extractPrimaryFamily('Iosevka'), 'Iosevka');
});
});
function makeContextWithInstalledFamilies(installed: Set<string>) {
// Mock canvas measurement: each generic fallback has a stable width;
// a "real" installed font produces a different width per fallback.
// Collision-resistant: position-weighted polynomial hash.
const widthFor = (family: string): number => {
let h = 0;
for (let i = 0; i < family.length; i++) {
h = (h * 31 + family.charCodeAt(i)) >>> 0;
}
return 100 + (h % 9973);
};
return {
measureText: (font: string, _text: string) => {
const match = font.match(/^\d+px\s+(.+)$/);
if (!match) return 0;
const familyList = match[1];
const families = familyList
.split(',')
.map((f) => f.trim().replace(/^["']|["']$/g, ''));
for (const f of families) {
if (installed.has(f) || ['serif', 'sans-serif', 'monospace'].includes(f)) {
return widthFor(f);
}
}
return 0;
},
};
}
describe('detectInstalledWithContext (canvas fallback)', () => {
it('detects an installed font (width differs from all 3 generic fallbacks)', () => {
const ctx = makeContextWithInstalledFamilies(new Set(['Fira Code']));
assert.equal(detectInstalledWithContext('Fira Code', ctx), true);
});
it('rejects a non-installed font (falls through to fallback)', () => {
const ctx = makeContextWithInstalledFamilies(new Set(['Fira Code']));
assert.equal(detectInstalledWithContext('Definitely Not A Font', ctx), false);
});
it('treats KNOWN_BUNDLED_FAMILIES as installed regardless of canvas evidence', () => {
const ctx = makeContextWithInstalledFamilies(new Set());
assert.equal(detectInstalledWithContext('JetBrains Mono', ctx), true);
assert.equal(detectInstalledWithContext('Sarasa Mono SC', ctx), true);
});
it('treats a font as installed when it matches one generic but differs from the others', () => {
// Regression guard for codex P2 review on PR #940: on macOS the
// `monospace` generic resolves to Menlo, so measure(`"Menlo", monospace`)
// equals measure(`monospace`). The detector must NOT report Menlo
// as uninstalled just because of that single collision — it should
// recognize installation via the other two generic baselines.
const ctx = {
measureText: (font: string): number => {
// "Menlo", X → Menlo's metrics (always 100, regardless of fallback)
if (font.includes('"Menlo"')) return 100;
// Generic baselines
if (font === '72px serif') return 50;
if (font === '72px sans-serif') return 80;
if (font === '72px monospace') return 100; // identical to Menlo
// Unknown family followed by a generic → falls to that generic
const tail = font.split(',').pop()?.trim() ?? '';
if (tail === 'serif') return 50;
if (tail === 'sans-serif') return 80;
if (tail === 'monospace') return 100;
return 0;
},
};
assert.equal(detectInstalledWithContext('Menlo', ctx), true);
});
it('still reports a clearly-uninstalled font as missing even with the looser rule', () => {
// "Some" semantics must not introduce false positives for fonts
// that genuinely aren't installed — those fall through to each
// generic and match all three baselines.
const ctx = makeContextWithInstalledFamilies(new Set(['Menlo']));
assert.equal(detectInstalledWithContext('Definitely Not Installed', ctx), false);
});
});
describe('isFontInstalled with authoritative system data', () => {
beforeEach(() => {
clearFontAvailabilityCache();
});
it('returns true for bundled families even without authoritative data', () => {
assert.equal(hasAuthoritativeData(), false);
assert.equal(isFontInstalled('JetBrains Mono'), true);
assert.equal(isFontInstalled('Sarasa Mono SC'), true);
});
it('answers from authoritative set once setSystemFamilies has run', () => {
setSystemFamilies(new Set(['menlo', 'fira code']));
assert.equal(hasAuthoritativeData(), true);
assert.equal(isFontInstalled('Menlo'), true);
assert.equal(isFontInstalled('Fira Code'), true);
assert.equal(isFontInstalled('Sarasa Mono SC'), true, 'bundled wins over set');
assert.equal(isFontInstalled('PingFang SC'), false, 'not in authoritative set');
assert.equal(isFontInstalled('Programmer Fonts'), false, 'fictitious name');
});
it('lookup is case-insensitive (set stores lowercase)', () => {
setSystemFamilies(new Set(['microsoft yahei ui']));
assert.equal(isFontInstalled('Microsoft YaHei UI'), true);
assert.equal(isFontInstalled('MICROSOFT YAHEI UI'), true);
});
it('falls back to safe-default (true) without DOM and without authoritative data', () => {
assert.equal(hasAuthoritativeData(), false);
assert.equal(isFontInstalled('Some Unknown Font'), true);
});
it('a null authoritative set means we re-enter fallback mode', () => {
setSystemFamilies(new Set(['menlo']));
assert.equal(hasAuthoritativeData(), true);
setSystemFamilies(null);
assert.equal(hasAuthoritativeData(), false);
});
});
describe('font availability subscription', () => {
beforeEach(() => {
clearFontAvailabilityCache();
});
it('notifies subscribers when setSystemFamilies is called', () => {
// Regression guard for codex P2 review on PR #940:
// TerminalCjkFontSelect memoizes visibleOptions on [value] but the
// filter calls isFontInstalled which depends on systemFamilies.
// Subscribers wired via useSyncExternalStore must fire so memos
// recompute when authoritative data arrives.
let calls = 0;
const unsubscribe = subscribeFontAvailability(() => {
calls += 1;
});
setSystemFamilies(new Set(['menlo']));
assert.equal(calls, 1);
setSystemFamilies(new Set(['menlo', 'fira code']));
assert.equal(calls, 2);
setSystemFamilies(null);
assert.equal(calls, 3);
unsubscribe();
setSystemFamilies(new Set(['menlo']));
assert.equal(calls, 3, 'unsubscribe stops notifications');
});
it('version monotonically increases on each setSystemFamilies call', () => {
const v0 = getFontAvailabilityVersion();
setSystemFamilies(new Set(['menlo']));
const v1 = getFontAvailabilityVersion();
setSystemFamilies(new Set(['menlo', 'fira code']));
const v2 = getFontAvailabilityVersion();
assert.ok(v1 > v0, 'first call bumps version');
assert.ok(v2 > v1, 'second call bumps version');
});
it('clearFontAvailabilityCache also notifies subscribers', () => {
let calls = 0;
subscribeFontAvailability(() => {
calls += 1;
});
setSystemFamilies(new Set(['menlo']));
const after = calls;
clearFontAvailabilityCache();
assert.ok(calls > after, 'clear notifies too');
});
});