From 4810c2b228166fc2814da2eff088b72584805a22 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 13 Jan 2026 19:56:20 +0300 Subject: [PATCH] chore: delete unused code --- src/entities/Font/lib/filterUtils.test.ts | 297 ------------------ src/entities/Font/lib/filterUtils.ts | 100 ------ .../helpers/createFontCollection.svelte.ts | 224 ------------- src/entities/Font/lib/typeGuards.test.ts | 194 ------------ src/entities/Font/lib/typeGuards.ts | 100 ------ .../model/store/__tests__/mocks.svelte.ts | 41 --- .../store/__tests__/unifiedFontStore.test.ts | 202 ------------ .../Font/model/store/fontCollection.svelte.ts | 3 - .../FontManagement/lib/filterBridge.svelte.ts | 115 ------- src/shared/types/collection.ts | 21 -- 10 files changed, 1297 deletions(-) delete mode 100644 src/entities/Font/lib/filterUtils.test.ts delete mode 100644 src/entities/Font/lib/filterUtils.ts delete mode 100644 src/entities/Font/lib/helpers/createFontCollection.svelte.ts delete mode 100644 src/entities/Font/lib/typeGuards.test.ts delete mode 100644 src/entities/Font/lib/typeGuards.ts delete mode 100644 src/entities/Font/model/store/__tests__/mocks.svelte.ts delete mode 100644 src/entities/Font/model/store/__tests__/unifiedFontStore.test.ts delete mode 100644 src/entities/Font/model/store/fontCollection.svelte.ts delete mode 100644 src/features/FontManagement/lib/filterBridge.svelte.ts delete mode 100644 src/shared/types/collection.ts diff --git a/src/entities/Font/lib/filterUtils.test.ts b/src/entities/Font/lib/filterUtils.test.ts deleted file mode 100644 index 52b957b..0000000 --- a/src/entities/Font/lib/filterUtils.test.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { - describe, - expect, - it, -} from 'vitest'; -import type { UnifiedFont } from '../model/types/normalize'; -import { - filterFonts, - sortFonts, -} from './filterUtils'; - -const createMockFont = (overrides: Partial = {}): UnifiedFont => ({ - id: 'test-1', - name: 'Test Font', - provider: 'google', - category: 'sans-serif', - subsets: ['latin'], - variants: ['400'], - styles: { - regular: 'https://example.com/font.woff2', - }, - metadata: { - cachedAt: Date.now(), - popularity: 100, - }, - features: {}, - ...overrides, -}); - -describe('filterUtils', () => { - describe('filterFonts', () => { - it('should return all fonts when no filters are applied', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Arial' }), - createMockFont({ id: '2', name: 'Times New Roman' }), - ]; - - const result = filterFonts(fonts, { - providers: [], - categories: [], - subsets: [], - searchQuery: '', - }); - - expect(result).toEqual(fonts); - }); - - it('should filter by provider', () => { - const fonts = [ - createMockFont({ id: '1', provider: 'google' }), - createMockFont({ id: '2', provider: 'fontshare' }), - ]; - - const result = filterFonts(fonts, { - providers: ['google'], - categories: [], - subsets: [], - searchQuery: '', - }); - - expect(result).toHaveLength(1); - expect(result[0].provider).toBe('google'); - }); - - it('should filter by category', () => { - const fonts = [ - createMockFont({ id: '1', category: 'sans-serif' }), - createMockFont({ id: '2', category: 'serif' }), - ]; - - const result = filterFonts(fonts, { - providers: [], - categories: ['sans-serif'], - subsets: [], - searchQuery: '', - }); - - expect(result).toHaveLength(1); - expect(result[0].category).toBe('sans-serif'); - }); - - it('should filter by subsets', () => { - const fonts = [ - createMockFont({ id: '1', subsets: ['latin'] }), - createMockFont({ id: '2', subsets: ['cyrillic'] }), - ]; - - const result = filterFonts(fonts, { - providers: [], - categories: [], - subsets: ['latin'], - searchQuery: '', - }); - - expect(result).toHaveLength(1); - expect(result[0].subsets).toContain('latin'); - }); - - it('should filter by search query (case-insensitive)', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Roboto' }), - createMockFont({ id: '2', name: 'Open Sans' }), - ]; - - const result = filterFonts(fonts, { - providers: [], - categories: [], - subsets: [], - searchQuery: 'ROBO', - }); - - expect(result).toHaveLength(1); - expect(result[0].name).toBe('Roboto'); - }); - - it('should apply multiple filters simultaneously', () => { - const fonts = [ - createMockFont({ - id: '1', - name: 'Roboto', - provider: 'google', - category: 'sans-serif', - subsets: ['latin'], - }), - createMockFont({ - id: '2', - name: 'Playfair', - provider: 'google', - category: 'serif', - subsets: ['latin'], - }), - createMockFont({ - id: '3', - name: 'Lato', - provider: 'fontshare', - category: 'sans-serif', - subsets: ['latin'], - }), - ]; - - const result = filterFonts(fonts, { - providers: ['google'], - categories: ['sans-serif'], - subsets: [], - searchQuery: '', - }); - - expect(result).toHaveLength(1); - expect(result[0].name).toBe('Roboto'); - }); - - it('should handle empty result set', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Roboto' }), - ]; - - const result = filterFonts(fonts, { - providers: ['fontshare'], - categories: [], - subsets: [], - searchQuery: '', - }); - - expect(result).toHaveLength(0); - }); - - it('should be case-insensitive for search', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Roboto' }), - createMockFont({ id: '2', name: 'roboto-condensed' }), - ]; - - const result = filterFonts(fonts, { - providers: [], - categories: [], - subsets: [], - searchQuery: 'ROBO', - }); - - expect(result).toHaveLength(2); - }); - }); - - describe('sortFonts', () => { - it('should sort by name ascending', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Zebra' }), - createMockFont({ id: '2', name: 'Apple' }), - createMockFont({ id: '3', name: 'Middle' }), - ]; - - const result = sortFonts(fonts, { field: 'name', direction: 'asc' }); - - expect(result[0].name).toBe('Apple'); - expect(result[1].name).toBe('Middle'); - expect(result[2].name).toBe('Zebra'); - }); - - it('should sort by name descending', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Zebra' }), - createMockFont({ id: '2', name: 'Apple' }), - ]; - - const result = sortFonts(fonts, { field: 'name', direction: 'desc' }); - - expect(result[0].name).toBe('Zebra'); - expect(result[1].name).toBe('Apple'); - }); - - it('should sort by category', () => { - const fonts = [ - createMockFont({ id: '1', category: 'serif' }), - createMockFont({ id: '2', category: 'sans-serif' }), - createMockFont({ id: '3', category: 'display' }), - ]; - - const result = sortFonts(fonts, { field: 'category', direction: 'asc' }); - - expect(result[0].category).toBe('display'); - expect(result[1].category).toBe('sans-serif'); - expect(result[2].category).toBe('serif'); - }); - - it('should sort by popularity (most popular first for asc)', () => { - const fonts = [ - createMockFont({ id: '1', metadata: { cachedAt: Date.now(), popularity: 10 } }), - createMockFont({ id: '2', metadata: { cachedAt: Date.now(), popularity: 100 } }), - createMockFont({ id: '3', metadata: { cachedAt: Date.now(), popularity: 50 } }), - ]; - - const result = sortFonts(fonts, { field: 'popularity', direction: 'asc' }); - - // Higher popularity comes first - expect(result[0].metadata.popularity).toBe(100); - expect(result[1].metadata.popularity).toBe(50); - expect(result[2].metadata.popularity).toBe(10); - }); - - it('should sort by date (most recent first for asc)', () => { - const now = Date.now(); - const fonts = [ - createMockFont({ - id: '1', - metadata: { cachedAt: now - 10000, popularity: 0 }, - }), - createMockFont({ - id: '2', - metadata: { cachedAt: now, popularity: 0 }, - }), - createMockFont({ - id: '3', - metadata: { cachedAt: now - 5000, popularity: 0 }, - }), - ]; - - const result = sortFonts(fonts, { field: 'date', direction: 'asc' }); - - // Most recent comes first - expect(result[0].metadata.cachedAt).toBe(now); - expect(result[1].metadata.cachedAt).toBe(now - 5000); - expect(result[2].metadata.cachedAt).toBe(now - 10000); - }); - - it('should handle undefined popularity values', () => { - const fonts = [ - createMockFont({ - id: '1', - metadata: { cachedAt: Date.now(), popularity: 50 }, - }), - createMockFont({ - id: '2', - metadata: { cachedAt: Date.now() }, - }), - ]; - - const result = sortFonts(fonts, { field: 'popularity', direction: 'asc' }); - - // Undefined should be treated as 0 - expect(result[0].metadata.popularity).toBe(50); - expect(result[1].metadata.popularity).toBe(undefined); - }); - - it('should return a new array without modifying original', () => { - const fonts = [ - createMockFont({ id: '1', name: 'Zebra' }), - createMockFont({ id: '2', name: 'Apple' }), - ]; - - const result = sortFonts(fonts, { field: 'name', direction: 'asc' }); - - expect(result).not.toBe(fonts); - expect(result[0].name).toBe('Apple'); - expect(fonts[0].name).toBe('Zebra'); // Original unchanged - }); - }); -}); diff --git a/src/entities/Font/lib/filterUtils.ts b/src/entities/Font/lib/filterUtils.ts deleted file mode 100644 index 07b1acb..0000000 --- a/src/entities/Font/lib/filterUtils.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * ============================================================================ - * FONT FILTER UTILITIES - * ============================================================================ - * - * Optimized utilities for filtering and sorting fonts in a single pass. - * These utilities are entity-specific and located in the Font entity layer - * following FSD (Feature-Sliced Design) principles. - */ - -import type { - FontFilters, - FontSort, -} from '../model/store/types'; -import type { UnifiedFont } from '../model/types/normalize'; - -/** - * Single-pass filter function for fonts - * - * Applies all filters in a single iteration for O(n) complexity. - * This is more efficient than chaining multiple filter() calls which - * would create intermediate arrays. - * - * @param fonts - The fonts to filter - * @param filters - The filter criteria - * @returns Filtered fonts - */ -export function filterFonts( - fonts: UnifiedFont[], - filters: FontFilters, -): UnifiedFont[] { - const hasSearch = !!filters.searchQuery; - const hasProviders = filters.providers.length > 0; - const hasCategories = filters.categories.length > 0; - const hasSubsets = filters.subsets.length > 0; - - // Fast path: no filters - if (!hasSearch && !hasProviders && !hasCategories && !hasSubsets) { - return fonts; - } - - const searchQuery = hasSearch ? filters.searchQuery.toLowerCase() : ''; - - // Single-pass filter - return fonts.filter(font => { - // Provider filter - if (hasProviders && !filters.providers.includes(font.provider)) { - return false; - } - - // Category filter - if (hasCategories && !filters.categories.includes(font.category)) { - return false; - } - - // Subset filter - if (hasSubsets && !filters.subsets.some(s => font.subsets.includes(s))) { - return false; - } - - // Search filter - if (hasSearch) { - const nameMatch = font.name.toLowerCase().includes(searchQuery); - const familyMatch = font.name.toLowerCase().includes(searchQuery); - if (!nameMatch && !familyMatch) { - return false; - } - } - - return true; - }); -} - -/** - * Sort fonts by specified field and direction - * - * @param fonts - The fonts to sort - * @param sort - The sort configuration - * @returns Sorted fonts - */ -export function sortFonts(fonts: UnifiedFont[], sort: FontSort): UnifiedFont[] { - const { field, direction } = sort; - const multiplier = direction === 'asc' ? 1 : -1; - - return [...fonts].sort((a, b) => { - switch (field) { - case 'name': - return a.name.localeCompare(b.name) * multiplier; - case 'category': - return a.category.localeCompare(b.category) * multiplier; - case 'popularity': - return ((b.metadata.popularity ?? 0) - (a.metadata.popularity ?? 0)) * multiplier; - case 'date': - // Sort by cachedAt timestamp as a proxy for date - return ((b.metadata.cachedAt ?? 0) - (a.metadata.cachedAt ?? 0)) * multiplier; - default: - return 0; - } - }); -} diff --git a/src/entities/Font/lib/helpers/createFontCollection.svelte.ts b/src/entities/Font/lib/helpers/createFontCollection.svelte.ts deleted file mode 100644 index 3af4769..0000000 --- a/src/entities/Font/lib/helpers/createFontCollection.svelte.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Font collection store - * - * Main font collection cache using Svelte stores. - * Integrates with TanStack Query for advanced caching and deduplication. - * - * Provides derived stores for filtered/sorted fonts. - */ - -import type { - FontCollectionFilters, - FontCollectionSort, - FontCollectionState, - UnifiedFont, -} from '$entities/Font/model/types'; -import { createCollectionCache } from '$shared/lib/fetch/collectionCache'; - -export const DEFAULT_SORT: FontCollectionSort = { field: 'name', direction: 'asc' }; - -/** - * Create font collection store - * - * @param initialState - Initial state for collection - * @returns Font collection store instance - * - * @example - * ```ts - * const fontCollection = createFontCollectionStore({ - * fonts: {}, - * filters: {}, - * sort: { field: 'name', direction: 'asc' } - * }); - * - * // Add fonts to collection - * fontCollection.addFonts([font1, font2]); - * - * // Use in component - * $fontCollection.filteredFonts - * ``` - */ -export function createFontCollection( - initialState?: Partial, -) { - const cache = createCollectionCache({ - defaultTTL: 5 * 60 * 1000, // 5 minutes - maxSize: 1000, - }); - - let fonts = $state(initialState?.fonts ?? {}); - let filters = $state(initialState?.filters ?? { searchQuery: '' }); - let sort = $state(initialState?.sort ?? DEFAULT_SORT); - - // const state: Writable = writable({ - // ...defaultState, - // ...initialState, - // }); - - // const isLoading = writable(false); - let isLoading = $state(false); - // const error = writable(); - let error = $state(); - - // Derived store for fonts as array - // const fonts = derived(state, $state => { - // return Object.values($state.fonts); - // }); - // - - const filtrationArray = $derived([ - (font: UnifiedFont) => filters.providers?.includes(font.provider), - (font: UnifiedFont) => filters.categories?.includes(font.category), - (font: UnifiedFont) => filters.subsets?.some(subset => font.subsets.includes(subset)), - (font: UnifiedFont) => - filters.searchQuery - ? font.name.toLowerCase().includes(filters.searchQuery.toLowerCase()) - : true, - ]); - - const filteredFonts = $derived( - Object.values(fonts).filter(font => { - return filtrationArray.every(filter => filter(font)); - }), - ); - - // Derived store for filtered fonts - // const filteredFonts = derived([state, fonts], ([$state, $fonts]) => { - // let filtered = [...$fonts]; - - // // Apply search filter - // if ($state.filters.searchQuery) { - // const query = $state.filters.searchQuery.toLowerCase(); - // filtered = filtered.filter(font => font.name.toLowerCase().includes(query)); - // } - - // // Apply provider filter - // if ($state.filters.provider) { - // filtered = filtered.filter( - // font => font.provider === $state.filters.provider, - // ); - // } - - // // Apply category filter - // if ($state.filters.category) { - // filtered = filtered.filter( - // font => font.category === $state.filters.category, - // ); - // } - - // // Apply subset filter - // if ($state.filters.subsets?.length) { - // filtered = filtered.filter(font => - // $state.filters.subsets!.some(subset => font.subsets.includes(subset as FontSubset)) - // ); - // } - - // // Apply sort - // const { field, direction } = $state.sort; - // const multiplier = direction === 'asc' ? 1 : -1; - - // filtered.sort((a, b) => { - // let comparison = 0; - - // if (field === 'name') { - // comparison = a.name.localeCompare(b.name); - // } else if (field === 'popularity') { - // const aPop = a.metadata.popularity ?? 0; - // const bPop = b.metadata.popularity ?? 0; - // comparison = aPop - bPop; - // } else if (field === 'category') { - // comparison = a.category.localeCompare(b.category); - // } - - // return comparison * multiplier; - // }); - - // return filtered; - // }); - - // Derived store for count - // const count = derived(fonts, $fonts => $fonts.length); - - const count = $derived(fonts.size); - - return { - get fonts() { - return fonts; - }, - set fonts(newFonts) { - fonts = newFonts; - }, - get filteredFonts() { - return filteredFonts; - }, - get filters() { - return filters; - }, - set filters(newFilters) { - filters = newFilters; - }, - get searchQuery() { - return filters.searchQuery; - }, - set searchQuery(newSearchQuery) { - filters = { - ...filters, - searchQuery: newSearchQuery, - }; - }, - get sort() { - return sort; - }, - set sort(newSort) { - sort = newSort; - }, - get count() { - return count; - }, - get isLoading() { - return isLoading; - }, - set isLoading(value) { - isLoading = value; - }, - get error() { - return error; - }, - set error(value) { - error = value; - }, - addFonts(newFonts: UnifiedFont[]) { - fonts = newFonts.reduce((acc, font) => { - cache.set(font.id, font); - return { ...acc, [font.id]: font }; - }, fonts); - }, - addFont(font: UnifiedFont) { - cache.set(font.id, font); - fonts = Object.fromEntries(Object.entries(fonts).concat([[font.id, font]])); - }, - removeFont(fontId: string) { - cache.remove(fontId); - fonts = Object.fromEntries(Object.entries(fonts).filter(([id]) => id !== fontId)); - }, - clearFonts() { - fonts = {}; - cache.clear(); - }, - clearFilters() { - filters = { - searchQuery: '', - providers: [], - categories: [], - }; - }, - clearSort() { - sort = DEFAULT_SORT; - }, - getFontById(fontId: string) { - return fonts[fontId]; - }, - }; -} - -export type FontCollectionStore = ReturnType; diff --git a/src/entities/Font/lib/typeGuards.test.ts b/src/entities/Font/lib/typeGuards.test.ts deleted file mode 100644 index 4190c1e..0000000 --- a/src/entities/Font/lib/typeGuards.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { - describe, - expect, - it, -} from 'vitest'; -import type { - FontCategory, - FontProvider, - FontSubset, -} from '../model/types/common'; -import { - filterValidValues, - isValidFontCategory, - isValidFontFilters, - isValidFontProvider, - isValidFontSubset, - validateFilterValues, -} from './typeGuards'; - -describe('typeGuards', () => { - describe('isValidFontProvider', () => { - it('should return true for valid providers', () => { - expect(isValidFontProvider('google')).toBe(true); - expect(isValidFontProvider('fontshare')).toBe(true); - }); - - it('should return false for invalid providers', () => { - expect(isValidFontProvider('invalid')).toBe(false); - expect(isValidFontProvider('Google')).toBe(false); // Case-sensitive - expect(isValidFontProvider('')).toBe(false); - }); - }); - - describe('isValidFontCategory', () => { - it('should return true for valid categories', () => { - expect(isValidFontCategory('serif')).toBe(true); - expect(isValidFontCategory('sans-serif')).toBe(true); - expect(isValidFontCategory('display')).toBe(true); - expect(isValidFontCategory('handwriting')).toBe(true); - expect(isValidFontCategory('monospace')).toBe(true); - expect(isValidFontCategory('script')).toBe(true); - expect(isValidFontCategory('slab')).toBe(true); - }); - - it('should return false for invalid categories', () => { - expect(isValidFontCategory('invalid')).toBe(false); - expect(isValidFontCategory('Serif')).toBe(false); // Case-sensitive - expect(isValidFontCategory('')).toBe(false); - }); - }); - - describe('isValidFontSubset', () => { - it('should return true for valid subsets', () => { - expect(isValidFontSubset('latin')).toBe(true); - expect(isValidFontSubset('latin-ext')).toBe(true); - expect(isValidFontSubset('cyrillic')).toBe(true); - expect(isValidFontSubset('greek')).toBe(true); - expect(isValidFontSubset('arabic')).toBe(true); - expect(isValidFontSubset('devanagari')).toBe(true); - }); - - it('should return false for invalid subsets', () => { - expect(isValidFontSubset('invalid')).toBe(false); - expect(isValidFontSubset('Latin')).toBe(false); // Case-sensitive - expect(isValidFontSubset('')).toBe(false); - }); - }); - - describe('validateFilterValues', () => { - it('should return true when all values are valid providers', () => { - const values = ['google', 'fontshare']; - expect(validateFilterValues(values, isValidFontProvider)).toBe(true); - }); - - it('should return true when all values are valid categories', () => { - const values = ['serif', 'sans-serif', 'display']; - expect(validateFilterValues(values, isValidFontCategory)).toBe(true); - }); - - it('should return false when any value is invalid', () => { - const values = ['serif', 'invalid-category']; - expect(validateFilterValues(values, isValidFontCategory)).toBe(false); - }); - - it('should return true for empty arrays', () => { - const values: string[] = []; - expect(validateFilterValues(values, isValidFontProvider)).toBe(true); - }); - }); - - describe('filterValidValues', () => { - it('should filter out invalid values', () => { - const values = ['google', 'invalid', 'fontshare', 'another-invalid']; - const result = filterValidValues(values, isValidFontProvider); - - expect(result).toEqual(['google', 'fontshare']); - }); - - it('should return empty array when no values are valid', () => { - const values = ['invalid1', 'invalid2']; - const result = filterValidValues(values, isValidFontProvider); - - expect(result).toEqual([]); - }); - - it('should return all values when all are valid', () => { - const values = ['serif', 'sans-serif']; - const result = filterValidValues(values, isValidFontCategory); - - expect(result).toEqual(['serif', 'sans-serif']); - }); - - it('should return empty array for empty input', () => { - const result = filterValidValues([], isValidFontProvider); - - expect(result).toEqual([]); - }); - }); - - describe('isValidFontFilters', () => { - it('should return true for valid filter object', () => { - const filters = { - providers: ['google', 'fontshare'], - categories: ['serif', 'sans-serif'], - subsets: ['latin'], - }; - - expect(isValidFontFilters(filters)).toBe(true); - }); - - it('should return false when providers are invalid', () => { - const filters = { - providers: ['invalid'], - categories: ['serif'], - subsets: ['latin'], - }; - - expect(isValidFontFilters(filters)).toBe(false); - }); - - it('should return false when categories are invalid', () => { - const filters = { - providers: ['google'], - categories: ['invalid'], - subsets: ['latin'], - }; - - expect(isValidFontFilters(filters)).toBe(false); - }); - - it('should return false when subsets are invalid', () => { - const filters = { - providers: ['google'], - categories: ['serif'], - subsets: ['invalid'], - }; - - expect(isValidFontFilters(filters)).toBe(false); - }); - - it('should return true for empty arrays', () => { - const filters = { - providers: [], - categories: [], - subsets: [], - }; - - expect(isValidFontFilters(filters)).toBe(true); - }); - - it('should type narrow correctly for TypeScript', () => { - const filters: { - providers: string[]; - categories: string[]; - subsets: string[]; - } = { - providers: ['google', 'fontshare'], - categories: ['serif'], - subsets: ['latin'], - }; - - if (isValidFontFilters(filters)) { - // TypeScript should know these are now typed arrays - const provider: FontProvider = filters.providers[0]!; - const category: FontCategory = filters.categories[0]!; - const subset: FontSubset = filters.subsets[0]!; - - expect(provider).toBe('google'); - expect(category).toBe('serif'); - expect(subset).toBe('latin'); - } - }); - }); -}); diff --git a/src/entities/Font/lib/typeGuards.ts b/src/entities/Font/lib/typeGuards.ts deleted file mode 100644 index 5b0878c..0000000 --- a/src/entities/Font/lib/typeGuards.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * ============================================================================ - * FONT TYPE GUARDS - * ============================================================================ - * - * Runtime type validation utilities for font-related types. - * These type guards validate filter values to ensure type safety - * when bridging between UI components and the store. - */ - -import { - FONT_CATEGORIES, - FONT_PROVIDERS, - FONT_SUBSETS, -} from '$features/FilterFonts/model/const/const'; -import type { - FontCategory, - FontProvider, - FontSubset, -} from '../model/types/common'; - -/** - * Type guard for font providers - * - * @param value - The value to validate - * @returns True if the value is a valid FontProvider - */ -export function isValidFontProvider(value: string): value is FontProvider { - return FONT_PROVIDERS.some(p => p.value === value); -} - -/** - * Type guard for font categories - * - * @param value - The value to validate - * @returns True if the value is a valid FontCategory - */ -export function isValidFontCategory(value: string): value is FontCategory { - return FONT_CATEGORIES.some(c => c.value === value); -} - -/** - * Type guard for font subsets - * - * @param value - The value to validate - * @returns True if the value is a valid FontSubset - */ -export function isValidFontSubset(value: string): value is FontSubset { - return FONT_SUBSETS.some(s => s.value === value); -} - -/** - * Validate array of filter values using a type guard - * - * @param values - The array of values to validate - * @param validator - The type guard function to use for validation - * @returns True if all values pass the type guard - */ -export function validateFilterValues( - values: string[], - validator: (value: string) => value is T, -): values is T[] { - return values.every(validator); -} - -/** - * Validate and filter an array of values, keeping only valid ones - * - * @param values - The array of values to filter - * @param validator - The type guard function to use for validation - * @returns Array containing only valid values - */ -export function filterValidValues( - values: string[], - validator: (value: string) => value is T, -): T[] { - return values.filter(validator) as T[]; -} - -/** - * Type guard for FontFilters object - * - * @param filters - The filters object to validate - * @returns True if all filter values are valid - */ -export function isValidFontFilters(filters: { - providers: string[]; - categories: string[]; - subsets: string[]; -}): filters is { - providers: FontProvider[]; - categories: FontCategory[]; - subsets: FontSubset[]; -} { - return ( - validateFilterValues(filters.providers, isValidFontProvider) - && validateFilterValues(filters.categories, isValidFontCategory) - && validateFilterValues(filters.subsets, isValidFontSubset) - ); -} diff --git a/src/entities/Font/model/store/__tests__/mocks.svelte.ts b/src/entities/Font/model/store/__tests__/mocks.svelte.ts deleted file mode 100644 index 32b3e67..0000000 --- a/src/entities/Font/model/store/__tests__/mocks.svelte.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { vi } from 'vitest'; -import type { UnifiedFont } from '../types/normalize'; - -export function createMockStore() { - let fonts = $state([]); - let isLoading = $state(false); - let isFetching = $state(false); - let error = $state(null); - - return { - get fonts() { - console.log('MockStore: get fonts', fonts.length); - return fonts; - }, - set fonts(v) { - console.log('MockStore: set fonts', v.length); - fonts = v; - }, - get isLoading() { - return isLoading; - }, - set isLoading(v) { - isLoading = v; - }, - get isFetching() { - return isFetching; - }, - set isFetching(v) { - isFetching = v; - }, - get error() { - return error; - }, - set error(v) { - error = v; - }, - setParams: vi.fn(), - clearCache: vi.fn(), - cancel: vi.fn(), - }; -} diff --git a/src/entities/Font/model/store/__tests__/unifiedFontStore.test.ts b/src/entities/Font/model/store/__tests__/unifiedFontStore.test.ts deleted file mode 100644 index bb67131..0000000 --- a/src/entities/Font/model/store/__tests__/unifiedFontStore.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Unit tests for unified font store - */ - -import type { - FontCategory, - FontProvider, -} from '$entities/Font/model/types/common'; -import type { UnifiedFont } from '$entities/Font/model/types/normalize'; -import { - beforeEach, - describe, - expect, - it, - vi, -} from 'vitest'; -// Import reactive mock factory -import { tick } from 'svelte'; -import { createMockStore } from './mocks.svelte'; - -// Mock the service stores BEFORE importing the unified store -// We use the reactive mock factory -const mockGoogleFonts = createMockStore(); -const mockFontshareFonts = createMockStore(); - -vi.mock('../../services/fetchGoogleFonts.svelte', () => ({ - createGoogleFontsStore: () => mockGoogleFonts, -})); - -vi.mock('../../services/fetchFontshareFonts.svelte', () => ({ - createFontshareStore: () => mockFontshareFonts, -})); - -// Import store after mocking -// Import factory and type -import { - type UnifiedFontStore, - createUnifiedFontStore, -} from '../unifiedFontStore.svelte'; - -// Mock UnifiedFont for testing -const mockFont = ( - id: string, - name: string, - provider: FontProvider, - category: FontCategory, -): UnifiedFont => ({ - id, - name, - provider, - category, - subsets: ['latin'], - variants: ['regular'], - styles: { - regular: 'https://example.com/font.woff2', - }, - metadata: { - cachedAt: Date.now(), - popularity: 100, - }, - features: { - isVariable: false, - }, -}); - -describe('Unified Font Store', () => { - let unifiedFontStore: UnifiedFontStore; - - beforeEach(() => { - // Reset mock data - mockGoogleFonts.fonts = []; - mockFontshareFonts.fonts = []; - mockGoogleFonts.isLoading = false; - mockFontshareFonts.isLoading = false; - mockGoogleFonts.error = null; - mockFontshareFonts.error = null; - - // Create fresh store instance - unifiedFontStore = createUnifiedFontStore(); - unifiedFontStore.clearFilters(); - vi.clearAllMocks(); - - // Reset mock data - mockGoogleFonts.fonts = []; - mockFontshareFonts.fonts = []; - mockGoogleFonts.isLoading = false; - mockFontshareFonts.isLoading = false; - mockGoogleFonts.error = null; - mockFontshareFonts.error = null; - }); - - describe('Debug Identity', () => { - it('uses the correct mock instance', () => { - // @ts-ignore - expect(unifiedFontStore.providers.google).toBe(mockGoogleFonts); - }); - }); - - describe('Aggregation', () => { - it('aggregates fonts from both providers', async () => { - const googleFont = mockFont('g1', 'Google Font', 'google', 'sans-serif'); - const fontshareFont = mockFont('f1', 'Fontshare Font', 'fontshare', 'serif'); - - mockGoogleFonts.fonts = [googleFont]; - mockFontshareFonts.fonts = [fontshareFont]; - - // Wait for reactivity (Svelte 5 updates are batched) - await tick(); - - // Trigger reactivity updates if needed (Store getters compute on access) - expect(unifiedFontStore.count).toBe(2); - expect(unifiedFontStore.fonts).toContain(googleFont); - expect(unifiedFontStore.fonts).toContain(fontshareFont); - }); - - it('handles empty states', () => { - expect(unifiedFontStore.count).toBe(0); - }); - - it('aggregates loading state', async () => { - mockGoogleFonts.isLoading = true; - await tick(); - expect(unifiedFontStore.isLoading).toBe(true); - - mockGoogleFonts.isLoading = false; - mockFontshareFonts.isLoading = true; - await tick(); - expect(unifiedFontStore.isLoading).toBe(true); - - mockFontshareFonts.isLoading = false; - await tick(); - expect(unifiedFontStore.isLoading).toBe(false); - }); - }); - - describe('Filter Management', () => { - it('sets and applies provider filter', async () => { - const googleFont = mockFont('g1', 'Google Font', 'google', 'sans-serif'); - const fontshareFont = mockFont('f1', 'Fontshare Font', 'fontshare', 'serif'); - - mockGoogleFonts.fonts = [googleFont]; - mockFontshareFonts.fonts = [fontshareFont]; - await tick(); - - // Filter to Google only - unifiedFontStore.setFilter('providers', ['google']); - - expect(unifiedFontStore.filteredFonts.length).toBe(1); - expect(unifiedFontStore.filteredFonts[0].id).toBe('g1'); - - // Filter to Fontshare only - unifiedFontStore.setFilter('providers', ['fontshare']); - expect(unifiedFontStore.filteredFonts.length).toBe(1); - expect(unifiedFontStore.filteredFonts[0].id).toBe('f1'); - }); - - it('sets search query and filters', async () => { - const font1 = mockFont('1', 'Roboto', 'google', 'sans-serif'); - const font2 = mockFont('2', 'Open Sans', 'google', 'sans-serif'); - - mockGoogleFonts.fonts = [font1, font2]; - await tick(); - - unifiedFontStore.searchQuery = 'Robo'; - - expect(unifiedFontStore.filteredFonts.length).toBe(1); - expect(unifiedFontStore.filteredFonts[0].name).toBe('Roboto'); - }); - - it('clears all filters', () => { - unifiedFontStore.setFilter('providers', ['google']); - unifiedFontStore.searchQuery = 'Test'; - - unifiedFontStore.clearFilters(); - - expect(unifiedFontStore.filters.providers).toEqual([]); - expect(unifiedFontStore.searchQuery).toBe(''); - }); - }); - - describe('Interaction with Services', () => { - it('fetchFonts calls setParams on underlying stores', async () => { - await unifiedFontStore.fetchFonts({ - search: 'test', - categories: ['serif'], - }); - - expect(mockGoogleFonts.setParams).toHaveBeenCalledWith(expect.objectContaining({ - category: 'serif', - })); - - // Verify local state update as well - expect(unifiedFontStore.searchQuery).toBe('test'); - }); - - it('clearCache calls clearCache on underlying stores', () => { - unifiedFontStore.clearCache(); - expect(mockGoogleFonts.clearCache).toHaveBeenCalled(); - expect(mockFontshareFonts.clearCache).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/entities/Font/model/store/fontCollection.svelte.ts b/src/entities/Font/model/store/fontCollection.svelte.ts deleted file mode 100644 index 026f021..0000000 --- a/src/entities/Font/model/store/fontCollection.svelte.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createFontCollection } from '../../lib'; - -export const fontCollection = createFontCollection(); diff --git a/src/features/FontManagement/lib/filterBridge.svelte.ts b/src/features/FontManagement/lib/filterBridge.svelte.ts deleted file mode 100644 index ffe71d4..0000000 --- a/src/features/FontManagement/lib/filterBridge.svelte.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { - filterValidValues, - isValidFontCategory, - isValidFontProvider, - isValidFontSubset, -} from '$entities/Font/lib/typeGuards'; -import type { UnifiedFontStore } from '$entities/Font/model/store'; -import { filterManager } from '$features/FilterFonts'; -import type { Property } from '$shared/lib'; - -/** - * ============================================================================ - * FILTER BRIDGE - * ============================================================================ - * - * Bridges the UI filter state (filterManager from FilterFonts feature) with the - * unified font store (unifiedFontStore from Font entity). - * - * OPTIMIZATIONS (P1): - * - Runtime type validation using type guards - * - No more 'as any[]' casts - type-safe assignments - */ - -// ... (comments) - -// ============================================================================ -// FILTER GROUP MAPPING -// ============================================================================ - -/** - * Get selected values from a filter manager group by ID - */ -function getSelectedFilterValues(groupId: string): string[] { - const group = filterManager.getGroup(groupId); - if (!group) return []; - - return group.instance.selectedProperties.map((property: Property) => property.value); -} - -// ============================================================================ -// SYNC LOGIC -// ============================================================================ - -/** - * Sync filter manager state to unified font store - * - * @param store - The unified font store instance - */ -export function syncFilters(store: UnifiedFontStore): void { - const providers = getSelectedFilterValues('providers'); - const categories = getSelectedFilterValues('categories'); - const subsets = getSelectedFilterValues('subsets'); - - // Validate and filter providers - const validProviders = filterValidValues(providers, isValidFontProvider); - - // Validate and filter categories - const validCategories = filterValidValues(categories, isValidFontCategory); - - // Validate and filter subsets - const validSubsets = filterValidValues(subsets, isValidFontSubset); - - // Update unified store filters with validated, type-safe values - store.filters = { - providers: validProviders, - categories: validCategories, - subsets: validSubsets, - searchQuery: store.filters.searchQuery, - }; -} - -// ============================================================================ -// PUBLIC API -// ============================================================================ - -/** - * Apply current filters and fetch fonts from providers - * - * @param store - The unified font store instance - */ -export async function applyFilters(store: UnifiedFontStore): Promise { - await store.fetchFonts(); -} - -/** - * Reset all filters to their default state - * - * @param store - The unified font store instance - */ -export function resetFilters(store: UnifiedFontStore): void { - // Reset filter manager selections - filterManager.deselectAllGlobal(); - - // Clear unified store filters - store.clearFilters(); -} - -/** - * Apply current filter selections and fetch fonts - * - * @param store - The unified font store instance - */ -export async function applyFilterType(store: UnifiedFontStore): Promise { - syncFilters(store); - await store.fetchFonts(); -} - -// ============================================================================ -// RE-EXPORTS FOR CONVENIENCE -// ============================================================================ - -/** - * Re-export filter manager for direct access - */ -export { filterManager } from '$features/FilterFonts'; diff --git a/src/shared/types/collection.ts b/src/shared/types/collection.ts deleted file mode 100644 index 79d4960..0000000 --- a/src/shared/types/collection.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Generic collection API response model - * Use this for APIs that return collections of items - * - * @template T - The type of items in the collection array - * @template K - The key used to access the collection array in the response - */ -export type CollectionApiModel = Record & { - /** - * Number of items returned in the current page/response - */ - count: number; - /** - * Total number of items available across all pages - */ - count_total: number; - /** - * Indicates if there are more items available beyond this page - */ - has_more: boolean; -};