diff --git a/src/entities/Font/api/index.ts b/src/entities/Font/api/index.ts index 0a1dde1..aad2be4 100644 --- a/src/entities/Font/api/index.ts +++ b/src/entities/Font/api/index.ts @@ -9,6 +9,7 @@ export { fetchFontsByIds, fetchProxyFontById, fetchProxyFonts, + seedFontCache, } from './proxy/proxyFonts'; export type { ProxyFontsParams, diff --git a/src/entities/Font/model/store/index.ts b/src/entities/Font/model/store/index.ts index 48dd140..bc6e56a 100644 --- a/src/entities/Font/model/store/index.ts +++ b/src/entities/Font/model/store/index.ts @@ -1,9 +1,6 @@ // Applied fonts manager export * from './appliedFontsStore/appliedFontsStore.svelte'; -// Batch font store -export { BatchFontStore } from './batchFontStore/batchFontStore.svelte'; - // Single FontStore export { createFontStore, diff --git a/src/features/FetchFontsByIds/index.ts b/src/features/FetchFontsByIds/index.ts new file mode 100644 index 0000000..600c651 --- /dev/null +++ b/src/features/FetchFontsByIds/index.ts @@ -0,0 +1 @@ +export { FontsByIdsStore } from './model'; diff --git a/src/features/FetchFontsByIds/model/index.ts b/src/features/FetchFontsByIds/model/index.ts new file mode 100644 index 0000000..c449f87 --- /dev/null +++ b/src/features/FetchFontsByIds/model/index.ts @@ -0,0 +1 @@ +export { FontsByIdsStore } from './store/fontsByIdsStore/fontsByIdsStore.svelte'; diff --git a/src/entities/Font/model/store/batchFontStore/batchFontStore.svelte.ts b/src/features/FetchFontsByIds/model/store/fontsByIdsStore/fontsByIdsStore.svelte.ts similarity index 84% rename from src/entities/Font/model/store/batchFontStore/batchFontStore.svelte.ts rename to src/features/FetchFontsByIds/model/store/fontsByIdsStore/fontsByIdsStore.svelte.ts index 7b0e9fe..9b177a8 100644 --- a/src/entities/Font/model/store/batchFontStore/batchFontStore.svelte.ts +++ b/src/features/FetchFontsByIds/model/store/fontsByIdsStore/fontsByIdsStore.svelte.ts @@ -1,14 +1,14 @@ -import { fontKeys } from '$shared/api/queryKeys'; -import { BaseQueryStore } from '$shared/lib/helpers/BaseQueryStore.svelte'; import { fetchFontsByIds, seedFontCache, -} from '../../../api/proxy/proxyFonts'; +} from '$entities/Font/api/proxy/proxyFonts'; import { FontNetworkError, FontResponseError, -} from '../../../lib/errors/errors'; -import type { UnifiedFont } from '../../types'; +} from '$entities/Font/lib/errors/errors'; +import type { UnifiedFont } from '$entities/Font/model/types'; +import { fontKeys } from '$shared/api/queryKeys'; +import { BaseQueryStore } from '$shared/lib/helpers/BaseQueryStore.svelte'; /** * Internal fetcher that seeds the cache and handles error wrapping. @@ -35,11 +35,10 @@ async function fetchAndSeed(ids: string[]): Promise { } /** - * Reactive store for fetching and caching batches of fonts by ID. - * Integrates with TanStack Query via BaseQueryStore and handles - * normalized cache seeding. + * Reactive store for fetching specific fonts by ID via the proxy batch endpoint. + * Wraps TanStack Query and seeds the detail cache for sibling consumers. */ -export class BatchFontStore extends BaseQueryStore { +export class FontsByIdsStore extends BaseQueryStore { constructor(initialIds: string[] = []) { super({ queryKey: fontKeys.batch(initialIds), diff --git a/src/entities/Font/model/store/batchFontStore/batchFontStore.test.ts b/src/features/FetchFontsByIds/model/store/fontsByIdsStore/fontsByIdsStore.test.ts similarity index 85% rename from src/entities/Font/model/store/batchFontStore/batchFontStore.test.ts rename to src/features/FetchFontsByIds/model/store/fontsByIdsStore/fontsByIdsStore.test.ts index 3b112ac..7849535 100644 --- a/src/entities/Font/model/store/batchFontStore/batchFontStore.test.ts +++ b/src/features/FetchFontsByIds/model/store/fontsByIdsStore/fontsByIdsStore.test.ts @@ -1,3 +1,8 @@ +import * as api from '$entities/Font/api/proxy/proxyFonts'; +import { + FontNetworkError, + FontResponseError, +} from '$entities/Font/lib/errors/errors'; import { queryClient } from '$shared/api/queryClient'; import { fontKeys } from '$shared/api/queryKeys'; import { @@ -7,14 +12,9 @@ import { it, vi, } from 'vitest'; -import * as api from '../../../api/proxy/proxyFonts'; -import { - FontNetworkError, - FontResponseError, -} from '../../../lib/errors/errors'; -import { BatchFontStore } from './batchFontStore.svelte'; +import { FontsByIdsStore } from './fontsByIdsStore.svelte'; -describe('BatchFontStore', () => { +describe('FontsByIdsStore', () => { beforeEach(() => { queryClient.clear(); vi.clearAllMocks(); @@ -23,7 +23,7 @@ describe('BatchFontStore', () => { describe('Fetch Behavior', () => { it('should skip fetch when initialized with empty IDs', async () => { const spy = vi.spyOn(api, 'fetchFontsByIds'); - const store = new BatchFontStore([]); + const store = new FontsByIdsStore([]); expect(spy).not.toHaveBeenCalled(); expect(store.fonts).toEqual([]); }); @@ -31,7 +31,7 @@ describe('BatchFontStore', () => { it('should fetch and seed cache for valid IDs', async () => { const fonts = [{ id: 'a', name: 'A' }] as any[]; vi.spyOn(api, 'fetchFontsByIds').mockResolvedValue(fonts); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts), { timeout: 1000 }); expect(queryClient.getQueryData(fontKeys.detail('a'))).toEqual(fonts[0]); }); @@ -42,7 +42,7 @@ describe('BatchFontStore', () => { vi.spyOn(api, 'fetchFontsByIds').mockImplementation(() => new Promise(r => setTimeout(() => r([{ id: 'a' }] as any), 50)) ); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); expect(store.isLoading).toBe(true); await vi.waitFor(() => expect(store.isLoading).toBe(false), { timeout: 1000 }); }); @@ -51,7 +51,7 @@ describe('BatchFontStore', () => { describe('Error Handling', () => { it('should wrap network failures in FontNetworkError', async () => { vi.spyOn(api, 'fetchFontsByIds').mockRejectedValue(new Error('Network fail')); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); await vi.waitFor(() => expect(store.isError).toBe(true), { timeout: 1000 }); expect(store.error).toBeInstanceOf(FontNetworkError); }); @@ -59,7 +59,7 @@ describe('BatchFontStore', () => { it('should handle malformed API responses with FontResponseError', async () => { // Mocking a malformed response that the store should validate vi.spyOn(api, 'fetchFontsByIds').mockResolvedValue(null as any); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); await vi.waitFor(() => expect(store.isError).toBe(true), { timeout: 1000 }); expect(store.error).toBeInstanceOf(FontResponseError); }); @@ -67,7 +67,7 @@ describe('BatchFontStore', () => { it('should have null error in success state', async () => { const fonts = [{ id: 'a' }] as any[]; vi.spyOn(api, 'fetchFontsByIds').mockResolvedValue(fonts); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts), { timeout: 1000 }); expect(store.error).toBeNull(); }); @@ -78,7 +78,7 @@ describe('BatchFontStore', () => { const fonts1 = [{ id: 'a' }] as any[]; const spy = vi.spyOn(api, 'fetchFontsByIds').mockResolvedValueOnce(fonts1); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts1), { timeout: 1000 }); spy.mockClear(); @@ -97,7 +97,7 @@ describe('BatchFontStore', () => { .mockResolvedValueOnce(fonts1) .mockResolvedValueOnce(fonts2); - const store = new BatchFontStore(['a']); + const store = new FontsByIdsStore(['a']); await vi.waitFor(() => expect(store.fonts).toEqual(fonts1), { timeout: 1000 }); store.setIds(['b']); diff --git a/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts b/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts index cf19174..1c6bc3c 100644 --- a/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts +++ b/src/widgets/ComparisonView/model/stores/comparisonStore.svelte.ts @@ -7,14 +7,13 @@ * * Features: * - Persistent font selection (survives page refresh) - * - Font loading state tracking via BatchFontStore + TanStack Query + * - Font loading state tracking via FontsByIdsStore + TanStack Query * - Sample text management * - Typography controls (size, weight, line height, spacing) * - Slider position for character-by-character morphing */ import { - BatchFontStore, type FontLoadRequestConfig, type UnifiedFont, appliedFontsManager, @@ -22,6 +21,7 @@ import { getFontUrl, } from '$entities/Font'; import { typographySettingsStore } from '$features/AdjustTypography/model'; +import { FontsByIdsStore } from '$features/FetchFontsByIds'; import { createPersistentStore } from '$shared/lib'; import { untrack } from 'svelte'; import { getPretextFontString } from '../../lib'; @@ -51,7 +51,7 @@ const storage = createPersistentStore('glyphdiff:comparison', { /** * Store for managing font comparison state. * - * Uses BatchFontStore (TanStack Query) to fetch fonts by ID, replacing + * Uses FontsByIdsStore (TanStack Query) to fetch fonts by ID, replacing * the previous hand-rolled async fetch approach. Three reactive effects * handle: (1) syncing batch results into fontA/fontB, (2) triggering the * CSS Font Loading API, and (3) falling back to default fonts when @@ -85,17 +85,17 @@ export class ComparisonStore { /** * TanStack Query-backed store for efficient batch font retrieval */ - #batchStore: BatchFontStore; + #fontsByIdsStore: FontsByIdsStore; constructor() { // Synchronously seed the batch store with any IDs already in storage const { fontAId, fontBId } = storage.value; - this.#batchStore = new BatchFontStore(fontAId && fontBId ? [fontAId, fontBId] : []); + this.#fontsByIdsStore = new FontsByIdsStore(fontAId && fontBId ? [fontAId, fontBId] : []); $effect.root(() => { // Effect 1: Sync batch results → fontA / fontB $effect(() => { - const fonts = this.#batchStore.fonts; + const fonts = this.#fontsByIdsStore.fonts; if (fonts.length === 0) { return; } @@ -157,7 +157,7 @@ export class ComparisonStore { const id1 = fonts[0].id; const id2 = fonts[fonts.length - 1].id; storage.value = { fontAId: id1, fontBId: id2 }; - this.#batchStore.setIds([id1, id2]); + this.#fontsByIdsStore.setIds([id1, id2]); }); } }); @@ -316,7 +316,7 @@ export class ComparisonStore { * True if any font is currently being fetched or loaded (reactive) */ get isLoading() { - return this.#batchStore.isLoading || !this.#fontsReady; + return this.#fontsByIdsStore.isLoading || !this.#fontsReady; } /** @@ -325,7 +325,7 @@ export class ComparisonStore { resetAll() { this.#fontA = undefined; this.#fontB = undefined; - this.#batchStore.setIds([]); + this.#fontsByIdsStore.setIds([]); storage.clear(); typographySettingsStore.reset(); } diff --git a/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts b/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts index d42686d..5a3146e 100644 --- a/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts +++ b/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts @@ -1,7 +1,7 @@ /** * Unit tests for ComparisonStore (TanStack Query refactor) * - * Uses the real BatchFontStore so Svelte $state reactivity works correctly. + * Uses the real FontsByIdsStore so Svelte $state reactivity works correctly. * Controls network behaviour via vi.spyOn on the proxyFonts API layer. */ @@ -53,12 +53,8 @@ vi.mock('$shared/lib/helpers/createPersistentStore/createPersistentStore.svelte' vi.mock('$entities/Font', async importOriginal => { const actual = await importOriginal(); - const { BatchFontStore } = await import( - '$entities/Font/model/store/batchFontStore/batchFontStore.svelte' - ); return { ...actual, - BatchFontStore, fontStore: { fonts: [] }, appliedFontsManager: { touch: vi.fn(), @@ -129,7 +125,7 @@ describe('ComparisonStore', () => { }); }); - describe('Restoration from Storage (via BatchFontStore)', () => { + describe('Restoration from Storage (via FontsByIdsStore)', () => { it('should restore fontA and fontB from stored IDs', async () => { mockStorage._value.fontAId = mockFontA.id; mockStorage._value.fontBId = mockFontB.id;