chore(appliedFontsStore): move generateFontKey into separate function and cover it with tests
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
type FontLoadStatus,
|
||||
} from '../../types';
|
||||
import {
|
||||
generateFontKey,
|
||||
getEffectiveConcurrency,
|
||||
loadFont,
|
||||
yieldToMainThread,
|
||||
@@ -81,11 +82,6 @@ export class AppliedFontsManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Generates font key: `{id}@vf` for variable, `{id}@{weight}` for static.
|
||||
#getFontKey(id: string, weight: number, isVariable: boolean): string {
|
||||
return isVariable ? `${id.toLowerCase()}@vf` : `${id.toLowerCase()}@${weight}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests fonts to be loaded. Updates usage tracking and queues new fonts.
|
||||
*
|
||||
@@ -93,13 +89,16 @@ export class AppliedFontsManager {
|
||||
* Scheduling: Prefers requestIdleCallback (150ms timeout), falls back to setTimeout(16ms).
|
||||
*/
|
||||
touch(configs: FontLoadRequestConfig[]) {
|
||||
if (this.#abortController.signal.aborted) return;
|
||||
if (this.#abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const now = Date.now();
|
||||
let hasNewItems = false;
|
||||
|
||||
for (const config of configs) {
|
||||
const key = this.#getFontKey(config.id, config.weight, !!config.isVariable);
|
||||
const key = generateFontKey(config);
|
||||
this.#usageTracker.set(key, now);
|
||||
|
||||
const status = this.statuses.get(key);
|
||||
@@ -122,6 +121,9 @@ export class AppliedFontsManager {
|
||||
this.#pendingType = 'timeout';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns optimal concurrent fetches based on Network Information API: 1 for 2G, 2 for 3G, 4 for 4G/default. */
|
||||
@@ -274,17 +276,32 @@ export class AppliedFontsManager {
|
||||
|
||||
/** Returns current loading status for a font, or undefined if never requested. */
|
||||
getFontStatus(id: string, weight: number, isVariable = false) {
|
||||
return this.statuses.get(this.#getFontKey(id, weight, isVariable));
|
||||
try {
|
||||
const key = generateFontKey({ id, weight, isVariable });
|
||||
return this.statuses.get(key);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Pins a font so it is never evicted by #purgeUnused(), regardless of TTL. */
|
||||
pin(id: string, weight: number, isVariable?: boolean): void {
|
||||
this.#pinnedFonts.add(this.#getFontKey(id, weight, !!isVariable));
|
||||
try {
|
||||
const key = generateFontKey({ id, weight, isVariable: !!isVariable });
|
||||
this.#pinnedFonts.add(key);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Unpins a font, allowing it to be evicted by #purgeUnused() once its TTL expires. */
|
||||
unpin(id: string, weight: number, isVariable?: boolean): void {
|
||||
this.#pinnedFonts.delete(this.#getFontKey(id, weight, !!isVariable));
|
||||
try {
|
||||
const key = generateFontKey({ id, weight, isVariable: !!isVariable });
|
||||
this.#pinnedFonts.delete(key);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Waits for all fonts to finish loading using document.fonts.ready. */
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { generateFontKey } from './generateFontKey';
|
||||
|
||||
describe('generateFontKey', () => {
|
||||
it('should throw an error if font id is not provided', () => {
|
||||
const config = { weight: 400, isVariable: false };
|
||||
// @ts-expect-error
|
||||
expect(() => generateFontKey(config)).toThrow('Font id is required');
|
||||
});
|
||||
|
||||
it('should generate a font key for a variable font', () => {
|
||||
const config = { id: 'Roboto', weight: 400, isVariable: true };
|
||||
expect(generateFontKey(config)).toBe('roboto@vf');
|
||||
});
|
||||
|
||||
it('should throw an error if font weight is not provided and is not a variable font', () => {
|
||||
const config = { id: 'Roboto', isVariable: false };
|
||||
// @ts-expect-error
|
||||
expect(() => generateFontKey(config)).toThrow('Font weight is required');
|
||||
});
|
||||
|
||||
it('should generate a font key for a non-variable font', () => {
|
||||
const config = { id: 'Roboto', weight: 400, isVariable: false };
|
||||
expect(generateFontKey(config)).toBe('roboto@400');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { FontLoadRequestConfig } from '../../../../types';
|
||||
|
||||
export type PartialConfig = Pick<FontLoadRequestConfig, 'id' | 'weight' | 'isVariable'>;
|
||||
|
||||
/**
|
||||
* Generates a font key for a given font load request configuration.
|
||||
* @param config - The font load request configuration.
|
||||
* @returns The generated font key.
|
||||
*/
|
||||
export function generateFontKey(config: PartialConfig): string {
|
||||
if (!config.id) {
|
||||
throw new Error('Font id is required');
|
||||
}
|
||||
if (config.isVariable) {
|
||||
return `${config.id.toLowerCase()}@vf`;
|
||||
}
|
||||
|
||||
if (!config.weight) {
|
||||
throw new Error('Font weight is required');
|
||||
}
|
||||
return `${config.id.toLowerCase()}@${config.weight}`;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { generateFontKey } from './generateFontKey/generateFontKey';
|
||||
export { getEffectiveConcurrency } from './getEffectiveConcurrency/getEffectiveConcurrency';
|
||||
export { loadFont } from './loadFont/loadFont';
|
||||
export { yieldToMainThread } from './yieldToMainThread/yieldToMainThread';
|
||||
|
||||
Reference in New Issue
Block a user