feat(ComparisonStore): pin fontA/fontB to prevent eviction while on-screen

This commit is contained in:
Ilia Mashkov
2026-04-16 10:55:41 +03:00
parent e509463911
commit d2bce85f9c
2 changed files with 81 additions and 2 deletions

View File

@@ -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);
};
});
}); });
} }

View File

@@ -53,15 +53,19 @@ vi.mock('$shared/lib/helpers/createPersistentStore/createPersistentStore.svelte'
// ── $entities/Font mock — keep real BatchFontStore, stub singletons ─────────── // ── $entities/Font mock — keep real BatchFontStore, stub singletons ───────────
vi.mock('$entities/Font', async () => { vi.mock('$entities/Font', async importOriginal => {
const actual = await importOriginal<typeof import('$entities/Font')>();
const { BatchFontStore } = await import( const { BatchFontStore } = await import(
'$entities/Font/model/store/batchFontStore.svelte' '$entities/Font/model/store/batchFontStore.svelte'
); );
return { return {
...actual,
BatchFontStore, BatchFontStore,
fontStore: { fonts: [] }, fontStore: { fonts: [] },
appliedFontsManager: { appliedFontsManager: {
touch: vi.fn(), touch: vi.fn(),
pin: vi.fn(),
unpin: vi.fn(),
getFontStatus: vi.fn(), getFontStatus: vi.fn(),
ready: vi.fn(() => Promise.resolve()), 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) ───────────────────────────────────────────────────── // ── Imports (after mocks) ─────────────────────────────────────────────────────
import { fontStore } from '$entities/Font'; import {
appliedFontsManager,
fontStore,
} from '$entities/Font';
import * as proxyFonts from '$entities/Font/api/proxy/proxyFonts'; import * as proxyFonts from '$entities/Font/api/proxy/proxyFonts';
import { ComparisonStore } from './comparisonStore.svelte'; import { ComparisonStore } from './comparisonStore.svelte';
@@ -209,4 +224,55 @@ describe('ComparisonStore', () => {
expect(store.fontB).toBeUndefined(); 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 });
});
});
}); });