From d2bce85f9cffbfbb7e9b82c0880b8be86bc511a9 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Apr 2026 10:55:41 +0300 Subject: [PATCH] feat(ComparisonStore): pin fontA/fontB to prevent eviction while on-screen --- .../model/stores/comparisonStore.svelte.ts | 13 ++++ .../model/stores/comparisonStore.test.ts | 70 ++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts b/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts index 2bf9844..aef2e02 100644 --- a/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts +++ b/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts @@ -134,6 +134,19 @@ export class ComparisonStore { }); } }); + + // Effect 4: Pin fontA/fontB so eviction never removes on-screen fonts + $effect(() => { + const fa = this.#fontA; + const fb = this.#fontB; + const w = typographySettingsStore.weight; + if (fa) appliedFontsManager.pin(fa.id, w, fa.features?.isVariable); + if (fb) appliedFontsManager.pin(fb.id, w, fb.features?.isVariable); + return () => { + if (fa) appliedFontsManager.unpin(fa.id, w, fa.features?.isVariable); + if (fb) appliedFontsManager.unpin(fb.id, w, fb.features?.isVariable); + }; + }); }); } diff --git a/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts b/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts index 013d450..049bd65 100644 --- a/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts +++ b/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts @@ -53,15 +53,19 @@ vi.mock('$shared/lib/helpers/createPersistentStore/createPersistentStore.svelte' // ── $entities/Font mock — keep real BatchFontStore, stub singletons ─────────── -vi.mock('$entities/Font', async () => { +vi.mock('$entities/Font', async importOriginal => { + const actual = await importOriginal(); const { BatchFontStore } = await import( '$entities/Font/model/store/batchFontStore.svelte' ); return { + ...actual, BatchFontStore, fontStore: { fonts: [] }, appliedFontsManager: { touch: vi.fn(), + pin: vi.fn(), + unpin: vi.fn(), getFontStatus: vi.fn(), ready: vi.fn(() => Promise.resolve()), }, @@ -80,9 +84,20 @@ vi.mock('$features/SetupFont', () => ({ })), })); +vi.mock('$features/SetupFont/model', () => ({ + typographySettingsStore: { + weight: 400, + renderedSize: 48, + reset: vi.fn(), + }, +})); + // ── Imports (after mocks) ───────────────────────────────────────────────────── -import { fontStore } from '$entities/Font'; +import { + appliedFontsManager, + fontStore, +} from '$entities/Font'; import * as proxyFonts from '$entities/Font/api/proxy/proxyFonts'; import { ComparisonStore } from './comparisonStore.svelte'; @@ -209,4 +224,55 @@ describe('ComparisonStore', () => { expect(store.fontB).toBeUndefined(); }); }); + + // ── Pin / Unpin ─────────────────────────────────────────────────────────── + + describe('Pin / Unpin (eviction guard)', () => { + it('pins fontA and fontB when they are loaded', async () => { + mockStorage._value.fontAId = mockFontA.id; + mockStorage._value.fontBId = mockFontB.id; + vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([mockFontA, mockFontB]); + + new ComparisonStore(); + + await vi.waitFor(() => { + expect(appliedFontsManager.pin).toHaveBeenCalledWith( + mockFontA.id, + 400, + mockFontA.features?.isVariable, + ); + expect(appliedFontsManager.pin).toHaveBeenCalledWith( + mockFontB.id, + 400, + mockFontB.features?.isVariable, + ); + }, { timeout: 2000 }); + }); + + it('unpins the old font when fontA is replaced', async () => { + mockStorage._value.fontAId = mockFontA.id; + mockStorage._value.fontBId = mockFontB.id; + vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([mockFontA, mockFontB]); + + const store = new ComparisonStore(); + await vi.waitFor(() => expect(store.fontA?.id).toBe(mockFontA.id), { timeout: 2000 }); + + const mockFontC: typeof mockFontA = { ...mockFontA, id: 'playfair', name: 'Playfair Display' }; + vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([mockFontC, mockFontB]); + store.fontA = mockFontC; + + await vi.waitFor(() => { + expect(appliedFontsManager.unpin).toHaveBeenCalledWith( + mockFontA.id, + 400, + mockFontA.features?.isVariable, + ); + expect(appliedFontsManager.pin).toHaveBeenCalledWith( + mockFontC.id, + 400, + mockFontC.features?.isVariable, + ); + }, { timeout: 2000 }); + }); + }); });