refactor(font): replace fontCatalogStore singleton with lazy getFontCatalog
Swap the eagerly-constructed fontCatalogStore singleton for a lazy getFontCatalog() accessor (plus __resetFontCatalog for tests), so the InfiniteQueryObserver is created on first use rather than at module load. Update the model barrels and all consumers (FontVirtualList, SampleList, SampleListSection) to the accessor. Also extract createFontLoadRequestConfig from FontVirtualList: it resolves a font's URL for a weight and returns a 0-or-1 element array, letting callers flatMap over a list to build load requests and drop unresolvable fonts in one pass.
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
} from 'vitest';
|
||||
import type { UnifiedFont } from '../../model/types';
|
||||
import { createFontLoadRequestContfig } from './createFontLoadRequestContfig';
|
||||
|
||||
/**
|
||||
* Minimal UnifiedFont mock — override only the fields a case exercises.
|
||||
*/
|
||||
function createMockFont(overrides: Partial<UnifiedFont> = {}): UnifiedFont {
|
||||
const baseFont: UnifiedFont = {
|
||||
id: 'test-font',
|
||||
name: 'Test Font',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
subsets: ['latin'],
|
||||
variants: [],
|
||||
styles: {},
|
||||
metadata: {
|
||||
cachedAt: Date.now(),
|
||||
},
|
||||
features: {
|
||||
isVariable: false,
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
|
||||
return { ...baseFont, ...overrides };
|
||||
}
|
||||
|
||||
describe('createFontLoadRequestContfig', () => {
|
||||
it('builds a single-element config when a URL resolves', () => {
|
||||
const font = createMockFont({
|
||||
id: 'roboto',
|
||||
name: 'Roboto',
|
||||
styles: { variants: { '400': 'https://example.com/roboto-400.woff2' } },
|
||||
});
|
||||
|
||||
const result = createFontLoadRequestContfig(font, 400);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: 'roboto',
|
||||
name: 'Roboto',
|
||||
weight: 400,
|
||||
url: 'https://example.com/roboto-400.woff2',
|
||||
isVariable: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an empty array when no URL resolves (flatMap drops the font)', () => {
|
||||
const font = createMockFont({ styles: {} });
|
||||
|
||||
expect(createFontLoadRequestContfig(font, 400)).toEqual([]);
|
||||
});
|
||||
|
||||
it('forwards isVariable from font features', () => {
|
||||
const font = createMockFont({
|
||||
features: { isVariable: true, tags: [] },
|
||||
styles: { variants: { '700': 'https://example.com/inter-vf.woff2' } },
|
||||
});
|
||||
|
||||
const [config] = createFontLoadRequestContfig(font, 700);
|
||||
|
||||
expect(config.isVariable).toBe(true);
|
||||
});
|
||||
|
||||
it('sets isVariable to undefined when features is absent', () => {
|
||||
// features is non-optional on UnifiedFont, but upstream data can be partial —
|
||||
// the optional chain must not throw, and isVariable stays undefined.
|
||||
const font = createMockFont({
|
||||
styles: { variants: { '400': 'https://example.com/font.woff2' } },
|
||||
});
|
||||
// @ts-expect-error — deliberately drop the guaranteed field to exercise the optional chain
|
||||
font.features = undefined;
|
||||
|
||||
const [config] = createFontLoadRequestContfig(font, 400);
|
||||
|
||||
expect(config.isVariable).toBeUndefined();
|
||||
});
|
||||
|
||||
it('uses the resolved fallback URL, not just exact matches', () => {
|
||||
// getFontUrl falls back to styles.regular when the exact weight is missing;
|
||||
// the config must carry whatever URL actually resolved.
|
||||
const font = createMockFont({
|
||||
styles: { regular: 'https://example.com/font-regular.woff2' },
|
||||
});
|
||||
|
||||
const [config] = createFontLoadRequestContfig(font, 900);
|
||||
|
||||
expect(config.url).toBe('https://example.com/font-regular.woff2');
|
||||
expect(config.weight).toBe(900);
|
||||
});
|
||||
|
||||
it('carries the requested weight even when the URL is a shared fallback', () => {
|
||||
const font = createMockFont({
|
||||
styles: { variants: { '400': 'https://example.com/shared.woff2' } },
|
||||
});
|
||||
|
||||
expect(createFontLoadRequestContfig(font, 700)[0].weight).toBe(700);
|
||||
});
|
||||
|
||||
it('propagates the invalid-weight error from getFontUrl', () => {
|
||||
const font = createMockFont();
|
||||
|
||||
expect(() => createFontLoadRequestContfig(font, 450)).toThrow('Invalid weight: 450');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user