refactor(font): replace fontLifecycleManager singleton with lazy accessor

Convert the eager fontLifecycleManager singleton to getFontLifecycleManager()
(+ __resetFontLifecycleManager for tests), so its AbortController/FontFace
bookkeeping is set up on first use rather than at module load. Update consumers
(FontVirtualList, FontList, SampleList) and resolve it once as a field in
comparisonStore; the comparisonStore mock now exposes getFontLifecycleManager.
This commit is contained in:
Ilia Mashkov
2026-06-01 18:49:47 +03:00
parent 839460726e
commit b3bc40b76c
6 changed files with 43 additions and 26 deletions
@@ -20,9 +20,10 @@ import {
} from '$entities/Font';
import {
type FontCatalogStore,
type FontLifecycleManager,
FontsByIdsStore,
fontLifecycleManager,
getFontCatalog,
getFontLifecycleManager,
} from '$entities/Font/model';
import {
type TypographySettingsStore,
@@ -106,12 +107,15 @@ export class ComparisonStore {
#typography: TypographySettingsStore;
#lifecycle: FontLifecycleManager;
constructor() {
// Synchronously seed the batch store with any IDs already in storage
const { fontAId, fontBId } = storage.value;
this.#fontsByIdsStore = new FontsByIdsStore(fontAId && fontBId ? [fontAId, fontBId] : []);
this.#fontCatalog = getFontCatalog();
this.#typography = getTypographySettingsStore();
this.#lifecycle = getFontLifecycleManager();
$effect.root(() => {
// Sync batch results → fontA / fontB
@@ -161,7 +165,7 @@ export class ComparisonStore {
});
if (configs.length > 0) {
fontLifecycleManager.touch(configs);
this.#lifecycle.touch(configs);
this.#checkFontsLoaded();
}
});
@@ -203,17 +207,17 @@ export class ComparisonStore {
const fb = this.#fontB;
const w = this.#typography.weight;
if (fa) {
fontLifecycleManager.pin(fa.id, w, fa.features?.isVariable);
this.#lifecycle.pin(fa.id, w, fa.features?.isVariable);
}
if (fb) {
fontLifecycleManager.pin(fb.id, w, fb.features?.isVariable);
this.#lifecycle.pin(fb.id, w, fb.features?.isVariable);
}
return () => {
if (fa) {
fontLifecycleManager.unpin(fa.id, w, fa.features?.isVariable);
this.#lifecycle.unpin(fa.id, w, fa.features?.isVariable);
}
if (fb) {
fontLifecycleManager.unpin(fb.id, w, fb.features?.isVariable);
this.#lifecycle.unpin(fb.id, w, fb.features?.isVariable);
}
};
});
@@ -63,6 +63,14 @@ vi.mock('$entities/Font', async importOriginal => {
};
});
const mockLifecycle = vi.hoisted(() => ({
touch: vi.fn(),
pin: vi.fn(),
unpin: vi.fn(),
getFontStatus: vi.fn(),
ready: vi.fn(() => Promise.resolve()),
}));
// Stores moved behind the model segment; mock them there. FontsByIdsStore is
// intentionally left real (spread from actual) so $state reactivity works.
vi.mock('$entities/Font/model', async importOriginal => {
@@ -70,13 +78,7 @@ vi.mock('$entities/Font/model', async importOriginal => {
return {
...actual,
getFontCatalog: () => mockFontCatalog,
fontLifecycleManager: {
touch: vi.fn(),
pin: vi.fn(),
unpin: vi.fn(),
getFontStatus: vi.fn(),
ready: vi.fn(() => Promise.resolve()),
},
getFontLifecycleManager: () => mockLifecycle,
};
});
@@ -100,10 +102,7 @@ vi.mock('$features/AdjustTypography/model', () => ({
}));
import * as proxyFonts from '$entities/Font/api/proxy/proxyFonts';
import {
fontLifecycleManager,
getFontCatalog,
} from '$entities/Font/model';
import { getFontCatalog } from '$entities/Font/model';
import { __resetFontCatalog } from '$entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte';
import { ComparisonStore } from './comparisonStore.svelte';
@@ -273,12 +272,12 @@ describe('ComparisonStore', () => {
new ComparisonStore();
await vi.waitFor(() => {
expect(fontLifecycleManager.pin).toHaveBeenCalledWith(
expect(mockLifecycle.pin).toHaveBeenCalledWith(
mockFontA.id,
400,
mockFontA.features?.isVariable,
);
expect(fontLifecycleManager.pin).toHaveBeenCalledWith(
expect(mockLifecycle.pin).toHaveBeenCalledWith(
mockFontB.id,
400,
mockFontB.features?.isVariable,
@@ -299,12 +298,12 @@ describe('ComparisonStore', () => {
store.fontA = mockFontC;
await vi.waitFor(() => {
expect(fontLifecycleManager.unpin).toHaveBeenCalledWith(
expect(mockLifecycle.unpin).toHaveBeenCalledWith(
mockFontA.id,
400,
mockFontA.features?.isVariable,
);
expect(fontLifecycleManager.pin).toHaveBeenCalledWith(
expect(mockLifecycle.pin).toHaveBeenCalledWith(
mockFontC.id,
400,
mockFontC.features?.isVariable,
@@ -11,8 +11,8 @@ import {
VIRTUAL_INDEX_NOT_LOADED,
} from '$entities/Font';
import {
fontLifecycleManager,
getFontCatalog,
getFontLifecycleManager,
} from '$entities/Font/model';
import { getSkeletonWidth } from '$shared/lib/utils';
import {
@@ -33,6 +33,7 @@ import {
const fontCatalog = getFontCatalog();
const comparisonStore = getComparisonStore();
const fontLifecycleManager = getFontLifecycleManager();
const fonts = $derived<UnifiedFont[]>(fontCatalog.fonts);
const side = $derived<Side>(comparisonStore.side);