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 { SvelteMap } from 'svelte/reactivity';
|
||||||
import type {
|
import {
|
||||||
FontLoadRequestConfig,
|
type FontLoadRequestConfig,
|
||||||
FontLoadStatus,
|
type FontLoadStatus,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
getEffectiveConcurrency,
|
getEffectiveConcurrency,
|
||||||
|
loadFont,
|
||||||
yieldToMainThread,
|
yieldToMainThread,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
@@ -184,17 +185,12 @@ export class AppliedFontsManager {
|
|||||||
|
|
||||||
for (const [key, config] of entries) {
|
for (const [key, config] of entries) {
|
||||||
const buffer = buffers.get(key);
|
const buffer = buffers.get(key);
|
||||||
if (!buffer) continue;
|
if (!buffer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const weightRange = config.isVariable ? '100 900' : `${config.weight}`;
|
const font = await loadFont(config, buffer);
|
||||||
const font = new FontFace(config.name, buffer, {
|
|
||||||
weight: weightRange,
|
|
||||||
style: 'normal',
|
|
||||||
display: 'swap',
|
|
||||||
});
|
|
||||||
await font.load();
|
|
||||||
document.fonts.add(font);
|
|
||||||
this.#loadedFonts.set(key, font);
|
this.#loadedFonts.set(key, font);
|
||||||
this.#buffersByUrl.set(config.url, buffer);
|
this.#buffersByUrl.set(config.url, buffer);
|
||||||
this.#urlByKey.set(key, config.url);
|
this.#urlByKey.set(key, config.url);
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency';
|
export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency';
|
||||||
|
export { loadFont } from './loadFont/loadFont';
|
||||||
export { yieldToMainThread } from './yieldToMainThread/yieldToMainThread';
|
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