feat(appliedFontsStore): move font loading logic into loadFont function and cover it with tests
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
import type {
|
||||
FontLoadRequestConfig,
|
||||
FontLoadStatus,
|
||||
import {
|
||||
type FontLoadRequestConfig,
|
||||
type FontLoadStatus,
|
||||
} from '../../types';
|
||||
import {
|
||||
getEffectiveConcurrency,
|
||||
loadFont,
|
||||
yieldToMainThread,
|
||||
} from './utils';
|
||||
|
||||
@@ -184,17 +185,12 @@ export class AppliedFontsManager {
|
||||
|
||||
for (const [key, config] of entries) {
|
||||
const buffer = buffers.get(key);
|
||||
if (!buffer) continue;
|
||||
if (!buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const weightRange = config.isVariable ? '100 900' : `${config.weight}`;
|
||||
const font = new FontFace(config.name, buffer, {
|
||||
weight: weightRange,
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
});
|
||||
await font.load();
|
||||
document.fonts.add(font);
|
||||
const font = await loadFont(config, buffer);
|
||||
this.#loadedFonts.set(key, font);
|
||||
this.#buffersByUrl.set(config.url, buffer);
|
||||
this.#urlByKey.set(key, config.url);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency';
|
||||
export { loadFont } from './loadFont/loadFont';
|
||||
export { yieldToMainThread } from './yieldToMainThread/yieldToMainThread';
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/** @vitest-environment jsdom */
|
||||
import { loadFont } from './loadFont';
|
||||
|
||||
describe('loadFont', () => {
|
||||
let mockFontInstance: any;
|
||||
let mockFontFaceSet: { add: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn> };
|
||||
|
||||
beforeEach(() => {
|
||||
mockFontFaceSet = { add: vi.fn(), delete: vi.fn() };
|
||||
Object.defineProperty(document, 'fonts', { value: mockFontFaceSet, configurable: true, writable: true });
|
||||
|
||||
const MockFontFace = vi.fn(
|
||||
function(this: any, name: string, buffer: BufferSource, options: FontFaceDescriptors) {
|
||||
this.name = name;
|
||||
this.buffer = buffer;
|
||||
this.options = options;
|
||||
this.load = vi.fn().mockResolvedValue(this);
|
||||
mockFontInstance = this;
|
||||
},
|
||||
);
|
||||
vi.stubGlobal('FontFace', MockFontFace);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('constructs FontFace with exact weight for static fonts', async () => {
|
||||
const buffer = new ArrayBuffer(8);
|
||||
await loadFont({ name: 'Roboto', weight: 400 }, buffer);
|
||||
|
||||
expect(FontFace).toHaveBeenCalledWith('Roboto', buffer, expect.objectContaining({ weight: '400' }));
|
||||
});
|
||||
|
||||
it('constructs FontFace with weight range for variable fonts', async () => {
|
||||
const buffer = new ArrayBuffer(8);
|
||||
await loadFont({ name: 'Roboto', weight: 400, isVariable: true }, buffer);
|
||||
|
||||
expect(FontFace).toHaveBeenCalledWith('Roboto', buffer, expect.objectContaining({ weight: '100 900' }));
|
||||
});
|
||||
|
||||
it('sets style: normal and display: swap on FontFace options', async () => {
|
||||
await loadFont({ name: 'Lato', weight: 700 }, new ArrayBuffer(8));
|
||||
|
||||
expect(FontFace).toHaveBeenCalledWith(
|
||||
'Lato',
|
||||
expect.anything(),
|
||||
expect.objectContaining({ style: 'normal', display: 'swap' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('passes the buffer as the second argument to FontFace', async () => {
|
||||
const buffer = new ArrayBuffer(16);
|
||||
await loadFont({ name: 'Inter', weight: 400 }, buffer);
|
||||
|
||||
expect(FontFace).toHaveBeenCalledWith('Inter', buffer, expect.anything());
|
||||
});
|
||||
|
||||
it('calls font.load() and adds the font to document.fonts', async () => {
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const result = await loadFont({ name: 'Inter', weight: 400 }, buffer);
|
||||
|
||||
expect(mockFontInstance.load).toHaveBeenCalledOnce();
|
||||
expect(mockFontFaceSet.add).toHaveBeenCalledWith(mockFontInstance);
|
||||
expect(result).toBe(mockFontInstance);
|
||||
});
|
||||
|
||||
it('propagates and logs error when font.load() rejects', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const loadError = new Error('parse failed');
|
||||
const MockFontFace = vi.fn(
|
||||
function(this: any, name: string, buffer: BufferSource, options: FontFaceDescriptors) {
|
||||
this.load = vi.fn().mockRejectedValue(loadError);
|
||||
},
|
||||
);
|
||||
vi.stubGlobal('FontFace', MockFontFace);
|
||||
|
||||
await expect(loadFont({ name: 'Broken', weight: 400 }, new ArrayBuffer(8))).rejects.toThrow('parse failed');
|
||||
expect(consoleSpy).toHaveBeenCalledWith(loadError);
|
||||
});
|
||||
|
||||
it('propagates and logs error when document.fonts.add throws', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const addError = new Error('add failed');
|
||||
mockFontFaceSet.add.mockImplementation(() => {
|
||||
throw addError;
|
||||
});
|
||||
|
||||
await expect(loadFont({ name: 'Broken', weight: 400 }, new ArrayBuffer(8))).rejects.toThrow('add failed');
|
||||
expect(consoleSpy).toHaveBeenCalledWith(addError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { FontLoadRequestConfig } from '../../../../types';
|
||||
|
||||
export type PartialConfig = Pick<FontLoadRequestConfig, 'weight' | 'name' | 'isVariable'>;
|
||||
/**
|
||||
* Loads a font from a buffer and adds it to the document's font collection.
|
||||
* @param config - The font load request configuration.
|
||||
* @param buffer - The buffer containing the font data.
|
||||
* @returns A promise that resolves to the loaded `FontFace`.
|
||||
*/
|
||||
export async function loadFont(config: PartialConfig, buffer: BufferSource): Promise<FontFace> {
|
||||
try {
|
||||
const weightRange = config.isVariable ? '100 900' : `${config.weight}`;
|
||||
const font = new FontFace(config.name, buffer, {
|
||||
weight: weightRange,
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
});
|
||||
await font.load();
|
||||
document.fonts.add(font);
|
||||
|
||||
return font;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user