feat: extract FontLoadQueue with retry tracking
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
import type { FontLoadRequestConfig } from '../../../types';
|
||||||
|
import { FontLoadQueue } from './FontLoadQueue';
|
||||||
|
|
||||||
|
const config = (id: string): FontLoadRequestConfig => ({
|
||||||
|
id,
|
||||||
|
name: id,
|
||||||
|
url: `https://example.com/${id}.woff2`,
|
||||||
|
weight: 400,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FontLoadQueue', () => {
|
||||||
|
let queue: FontLoadQueue;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
queue = new FontLoadQueue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enqueue returns true for a new key', () => {
|
||||||
|
expect(queue.enqueue('a@400', config('a'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enqueue returns false for an already-queued key', () => {
|
||||||
|
queue.enqueue('a@400', config('a'));
|
||||||
|
expect(queue.enqueue('a@400', config('a'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has returns true after enqueue, false after flush', () => {
|
||||||
|
queue.enqueue('a@400', config('a'));
|
||||||
|
expect(queue.has('a@400')).toBe(true);
|
||||||
|
queue.flush();
|
||||||
|
expect(queue.has('a@400')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('flush returns all entries and atomically clears the queue', () => {
|
||||||
|
queue.enqueue('a@400', config('a'));
|
||||||
|
queue.enqueue('b@700', config('b'));
|
||||||
|
const entries = queue.flush();
|
||||||
|
expect(entries).toHaveLength(2);
|
||||||
|
expect(queue.has('a@400')).toBe(false);
|
||||||
|
expect(queue.has('b@700')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isMaxRetriesReached returns false below MAX_RETRIES', () => {
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
expect(queue.isMaxRetriesReached('a@400')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isMaxRetriesReached returns true at MAX_RETRIES (3)', () => {
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
expect(queue.isMaxRetriesReached('a@400')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clear resets queue and retry counts', () => {
|
||||||
|
queue.enqueue('a@400', config('a'));
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
queue.incrementRetry('a@400');
|
||||||
|
queue.clear();
|
||||||
|
expect(queue.has('a@400')).toBe(false);
|
||||||
|
expect(queue.isMaxRetriesReached('a@400')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import type { FontLoadRequestConfig } from '../../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the font load queue and per-font retry counts.
|
||||||
|
*
|
||||||
|
* Scheduling (when to drain the queue) is handled by the orchestrator —
|
||||||
|
* this class is purely concerned with what is queued and whether retries are exhausted.
|
||||||
|
*/
|
||||||
|
export class FontLoadQueue {
|
||||||
|
#queue = new Map<string, FontLoadRequestConfig>();
|
||||||
|
#retryCounts = new Map<string, number>();
|
||||||
|
|
||||||
|
readonly #MAX_RETRIES = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a font to the queue.
|
||||||
|
* @returns `true` if the key was newly enqueued, `false` if it was already present.
|
||||||
|
*/
|
||||||
|
enqueue(key: string, config: FontLoadRequestConfig): boolean {
|
||||||
|
if (this.#queue.has(key)) return false;
|
||||||
|
this.#queue.set(key, config);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomically snapshots and clears the queue.
|
||||||
|
* @returns All queued entries at the time of the call.
|
||||||
|
*/
|
||||||
|
flush(): Array<[string, FontLoadRequestConfig]> {
|
||||||
|
const entries = Array.from(this.#queue.entries());
|
||||||
|
this.#queue.clear();
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns `true` if the key is currently in the queue. */
|
||||||
|
has(key: string): boolean {
|
||||||
|
return this.#queue.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Increments the retry count for a font key. */
|
||||||
|
incrementRetry(key: string): void {
|
||||||
|
this.#retryCounts.set(key, (this.#retryCounts.get(key) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns `true` if the font has reached or exceeded the maximum retry limit. */
|
||||||
|
isMaxRetriesReached(key: string): boolean {
|
||||||
|
return (this.#retryCounts.get(key) ?? 0) >= this.#MAX_RETRIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears all queued fonts and resets all retry counts. */
|
||||||
|
clear(): void {
|
||||||
|
this.#queue.clear();
|
||||||
|
this.#retryCounts.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user