refactor(GetFonts): rename filters/filterManager to available/appliedFilterStore

The 'filters' + 'filterManager' pair didn't reveal the schema-vs-selection
split. Rename to reflect the actual roles:

- FiltersStore / filtersStore       → AvailableFilterStore / availableFilterStore
- createFilterManager / FilterManager → createAppliedFilterStore / AppliedFilterStore
- filterManager singleton            → appliedFilterStore
- mapManagerToParams                 → mapAppliedFiltersToParams

Directories and file basenames follow the new singleton names. Public
barrel signature updated; all consumers (Search, FontSearch, Filters,
FilterControls) point at the new identifiers.
This commit is contained in:
Ilia Mashkov
2026-05-24 18:08:05 +03:00
parent b6494a8cb5
commit e0d39d861f
18 changed files with 153 additions and 153 deletions
+8 -8
View File
@@ -1,16 +1,16 @@
export { mapManagerToParams } from './lib';
export { mapAppliedFiltersToParams } from './lib';
export {
/**
* Filter Manager
*/
createFilterManager,
type FilterManager,
filterManager,
type AppliedFilterStore,
appliedFilterStore,
/**
* Filter Store
*/
filtersStore,
availableFilterStore,
/**
* Filter Manager
*/
createAppliedFilterStore,
/**
* Sort Store
*/
+1 -1
View File
@@ -1 +1 @@
export { mapManagerToParams } from './mapper/mapManagerToParams';
export { mapAppliedFiltersToParams } from './mapper/mapAppliedFiltersToParams';
@@ -4,8 +4,8 @@ import {
expect,
it,
} from 'vitest';
import { createFilterManager } from '../../model/store/filterManager/filterManager.svelte';
import { mapManagerToParams } from './mapManagerToParams';
import { createAppliedFilterStore } from '../../model/store/appliedFilterStore/appliedFilterStore.svelte';
import { mapAppliedFiltersToParams } from './mapAppliedFiltersToParams';
/**
* Build a Property with explicit selection state.
@@ -25,47 +25,47 @@ function group(id: string, props: Array<[string, boolean]>) {
};
}
describe('mapManagerToParams', () => {
describe('mapAppliedFiltersToParams', () => {
describe('search query', () => {
it('omits q when query is empty', () => {
const manager = createFilterManager({ queryValue: '', groups: [] });
expect(mapManagerToParams(manager).q).toBeUndefined();
const manager = createAppliedFilterStore({ queryValue: '', groups: [] });
expect(mapAppliedFiltersToParams(manager).q).toBeUndefined();
});
it('passes the debounced query through as q', () => {
// Constructor seeds both immediate and debounced synchronously.
const manager = createFilterManager({ queryValue: 'roboto', groups: [] });
expect(mapManagerToParams(manager).q).toBe('roboto');
const manager = createAppliedFilterStore({ queryValue: 'roboto', groups: [] });
expect(mapAppliedFiltersToParams(manager).q).toBe('roboto');
});
});
describe('group selections', () => {
it('omits a group entirely when no group with that id exists', () => {
const manager = createFilterManager({ queryValue: '', groups: [] });
const params = mapManagerToParams(manager);
const manager = createAppliedFilterStore({ queryValue: '', groups: [] });
const params = mapAppliedFiltersToParams(manager);
expect(params.providers).toBeUndefined();
expect(params.categories).toBeUndefined();
expect(params.subsets).toBeUndefined();
});
it('omits a group when it exists but has no selections', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: [group('providers', [['google', false], ['fontshare', false]])],
});
expect(mapManagerToParams(manager).providers).toBeUndefined();
expect(mapAppliedFiltersToParams(manager).providers).toBeUndefined();
});
it('returns the selected values for a single group', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: [group('providers', [['google', true], ['fontshare', false]])],
});
expect(mapManagerToParams(manager).providers).toEqual(['google']);
expect(mapAppliedFiltersToParams(manager).providers).toEqual(['google']);
});
it('returns multiple selected values in selection order', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: [
group('categories', [
@@ -76,11 +76,11 @@ describe('mapManagerToParams', () => {
]),
],
});
expect(mapManagerToParams(manager).categories).toEqual(['serif', 'display', 'monospace']);
expect(mapAppliedFiltersToParams(manager).categories).toEqual(['serif', 'display', 'monospace']);
});
it('maps each of the three recognized group ids independently', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: [
group('providers', [['google', true]]),
@@ -88,18 +88,18 @@ describe('mapManagerToParams', () => {
group('subsets', [['latin', true]]),
],
});
const params = mapManagerToParams(manager);
const params = mapAppliedFiltersToParams(manager);
expect(params.providers).toEqual(['google']);
expect(params.categories).toEqual(['serif', 'sans-serif']);
expect(params.subsets).toEqual(['latin']);
});
it('ignores groups whose id does not match providers/categories/subsets', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: [group('weights', [['400', true], ['700', true]])],
});
const params = mapManagerToParams(manager);
const params = mapAppliedFiltersToParams(manager);
expect(params.providers).toBeUndefined();
expect(params.categories).toBeUndefined();
expect(params.subsets).toBeUndefined();
@@ -108,7 +108,7 @@ describe('mapManagerToParams', () => {
describe('combined output', () => {
it('produces a complete param object when query and selections coexist', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'inter',
groups: [
group('providers', [['google', true]]),
@@ -116,7 +116,7 @@ describe('mapManagerToParams', () => {
group('subsets', [['latin', false]]),
],
});
expect(mapManagerToParams(manager)).toEqual({
expect(mapAppliedFiltersToParams(manager)).toEqual({
q: 'inter',
providers: ['google'],
categories: ['sans-serif'],
@@ -1,5 +1,5 @@
import type { ProxyFontsParams } from '$entities/Font/api';
import type { FilterManager } from '../../model';
import type { AppliedFilterStore } from '../../model';
/**
* Maps filter manager to proxy API parameters.
@@ -19,7 +19,7 @@ import type { FilterManager } from '../../model';
* // subsets: ['latin']
* // }
*
* const params = mapManagerToParams(manager);
* const params = mapAppliedFiltersToParams(manager);
* // Returns: {
* // providers: ['google', 'fontshare'],
* // categories: ['sans-serif', 'serif'],
@@ -28,7 +28,7 @@ import type { FilterManager } from '../../model';
* // }
* ```
*/
export function mapManagerToParams(manager: FilterManager): Partial<ProxyFontsParams> {
export function mapAppliedFiltersToParams(manager: AppliedFilterStore): Partial<ProxyFontsParams> {
/**
* Return the list of selected values for a group, or undefined when
* the group is missing or has no selection matches the API's
+11 -11
View File
@@ -16,29 +16,29 @@ export {
/**
* Low-level property selection store
*/
filtersStore,
} from './store/filters/filters.svelte';
availableFilterStore,
} from './store/availableFilterStore/availableFilterStore.svelte';
/**
* Main filter controller
*/
export {
/**
* Factory for constructing a filter manager instance
* Reactive interface returned by `createAppliedFilterStore`
*/
createFilterManager,
/**
* Reactive interface returned by `createFilterManager`
*/
type FilterManager,
type AppliedFilterStore,
/**
* High-level manager for syncing search and filters
*/
filterManager,
} from './store/filterManager/filterManager.svelte';
appliedFilterStore,
/**
* Factory for constructing a filter manager instance
*/
createAppliedFilterStore,
} from './store/appliedFilterStore/appliedFilterStore.svelte';
/**
* Side-effect import: installs the global filterManager+sortStore → fontStore
* Side-effect import: installs the global appliedFilterStore+sortStore → fontStore
* bridge on first import of this feature barrel. No exports.
*/
import './store/bindings.svelte';
@@ -5,12 +5,12 @@
* debounced search input. Provides reactive state for filter selections
* and convenience methods for bulk operations.
*
* The factory (`createFilterManager`) is exported for tests; the app
* consumes the `filterManager` singleton at the bottom of this file.
* The factory (`createAppliedFilterStore`) is exported for tests; the app
* consumes the `appliedFilterStore` singleton at the bottom of this file.
*
* @example
* ```ts
* const manager = createFilterManager({
* const manager = createAppliedFilterStore({
* queryValue: '',
* groups: [
* { id: 'providers', label: 'Provider', properties: [...] },
@@ -39,7 +39,7 @@ import type {
* @param config - Configuration with query value and filter groups
* @returns Filter manager instance with reactive state and methods
*/
export function createFilterManager<TValue extends string>(config: FilterConfig<TValue>) {
export function createAppliedFilterStore<TValue extends string>(config: FilterConfig<TValue>) {
const search = createDebouncedState(config.queryValue ?? '');
// Create filter instances upfront
@@ -125,16 +125,16 @@ export function createFilterManager<TValue extends string>(config: FilterConfig<
};
}
export type FilterManager = ReturnType<typeof createFilterManager>;
export type AppliedFilterStore = ReturnType<typeof createAppliedFilterStore>;
/**
* App-wide filter manager singleton.
*
* Constructed with empty groups; the filtersStore filterManager wiring
* Constructed with empty groups; the availableFilterStore appliedFilterStore wiring
* lives in `./bindings.svelte` and populates groups once backend filter
* metadata arrives.
*/
export const filterManager = createFilterManager({
export const appliedFilterStore = createAppliedFilterStore({
queryValue: '',
groups: [],
});
@@ -7,10 +7,10 @@ import {
it,
vi,
} from 'vitest';
import { createFilterManager } from './filterManager.svelte';
import { createAppliedFilterStore } from './appliedFilterStore.svelte';
/**
* Test Suite for createFilterManager Helper Function
* Test Suite for createAppliedFilterStore Helper Function
*
* This suite tests the filter manager logic including:
* - Debounced query state (immediate vs delayed)
@@ -54,9 +54,9 @@ function createTestGroups(count: number, propertiesPerGroup = 3) {
}));
}
describe('createFilterManager - Initialization', () => {
describe('createAppliedFilterStore - Initialization', () => {
it('creates manager with empty query value', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(2),
});
@@ -66,7 +66,7 @@ describe('createFilterManager - Initialization', () => {
});
it('creates manager with initial query value', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'search term',
groups: createTestGroups(1),
});
@@ -76,7 +76,7 @@ describe('createFilterManager - Initialization', () => {
});
it('creates manager with undefined query value (defaults to empty string)', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
groups: createTestGroups(1),
});
@@ -86,7 +86,7 @@ describe('createFilterManager - Initialization', () => {
it('creates filter groups for each config group', () => {
const groups = createTestGroups(3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -99,7 +99,7 @@ describe('createFilterManager - Initialization', () => {
it('creates filter instances for each group', () => {
const groups = createTestGroups(2, 5);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -118,7 +118,7 @@ describe('createFilterManager - Initialization', () => {
{ id: 'providers', label: 'Providers', properties: createTestProperties(2) },
{ id: 'categories', label: 'Categories', properties: createTestProperties(3) },
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -129,7 +129,7 @@ describe('createFilterManager - Initialization', () => {
it('handles single group', () => {
const groups = createTestGroups(1);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -139,7 +139,7 @@ describe('createFilterManager - Initialization', () => {
});
});
describe('createFilterManager - Debounced Query', () => {
describe('createAppliedFilterStore - Debounced Query', () => {
beforeEach(() => {
vi.useFakeTimers();
});
@@ -149,7 +149,7 @@ describe('createFilterManager - Debounced Query', () => {
});
it('immediate query value updates instantly', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -161,7 +161,7 @@ describe('createFilterManager - Debounced Query', () => {
});
it('debounced query value updates after default delay (300ms)', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -178,7 +178,7 @@ describe('createFilterManager - Debounced Query', () => {
});
it('rapid query changes reset the debounce timer', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -200,7 +200,7 @@ describe('createFilterManager - Debounced Query', () => {
});
it('handles empty string in query', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'initial',
groups: createTestGroups(1),
});
@@ -213,7 +213,7 @@ describe('createFilterManager - Debounced Query', () => {
});
it('preserves initial query value until changed', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'initial search',
groups: createTestGroups(1),
});
@@ -228,9 +228,9 @@ describe('createFilterManager - Debounced Query', () => {
});
});
describe('createFilterManager - hasAnySelection Derived State', () => {
describe('createAppliedFilterStore - hasAnySelection Derived State', () => {
it('returns false when no filters are selected', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(3, 3),
});
@@ -240,7 +240,7 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
it('returns true when one filter in one group is selected', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -255,7 +255,7 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
it('returns true when multiple filters across groups are selected', () => {
const groups = createTestGroups(3, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -272,7 +272,7 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
it('returns false after deselecting all filters', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -286,7 +286,7 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
it('reacts to selection changes in individual groups', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -318,7 +318,7 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
properties: createTestProperties(3, []),
},
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -331,7 +331,7 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
{ id: 'group-0', label: 'Group 0', properties: [] },
{ id: 'group-1', label: 'Group 1', properties: [] },
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -340,10 +340,10 @@ describe('createFilterManager - hasAnySelection Derived State', () => {
});
});
describe('createFilterManager - getGroup() Method', () => {
describe('createAppliedFilterStore - getGroup() Method', () => {
it('returns the correct group by ID', () => {
const groups = createTestGroups(3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -357,7 +357,7 @@ describe('createFilterManager - getGroup() Method', () => {
it('returns undefined for non-existent group ID', () => {
const groups = createTestGroups(2);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -369,7 +369,7 @@ describe('createFilterManager - getGroup() Method', () => {
it('returns group with accessible filter instance', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -385,7 +385,7 @@ describe('createFilterManager - getGroup() Method', () => {
it('returns first group when requested', () => {
const groups = createTestGroups(3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -398,7 +398,7 @@ describe('createFilterManager - getGroup() Method', () => {
it('returns last group when requested', () => {
const groups = createTestGroups(5);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -410,10 +410,10 @@ describe('createFilterManager - getGroup() Method', () => {
});
});
describe('createFilterManager - deselectAllGlobal() Method', () => {
describe('createAppliedFilterStore - deselectAllGlobal() Method', () => {
it('deselects all filters across all groups', () => {
const groups = createTestGroups(3, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -436,7 +436,7 @@ describe('createFilterManager - deselectAllGlobal() Method', () => {
it('handles deselecting when nothing is selected', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -453,7 +453,7 @@ describe('createFilterManager - deselectAllGlobal() Method', () => {
{ id: 'group-0', label: 'Group 0', properties: [] },
{ id: 'group-1', label: 'Group 1', properties: [] },
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -464,7 +464,7 @@ describe('createFilterManager - deselectAllGlobal() Method', () => {
it('can select filters after global deselect', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -482,7 +482,7 @@ describe('createFilterManager - deselectAllGlobal() Method', () => {
it('handles partially selected groups', () => {
const groups = createTestGroups(3, 5);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -505,7 +505,7 @@ describe('createFilterManager - deselectAllGlobal() Method', () => {
});
});
describe('createFilterManager - Complex Scenarios', () => {
describe('createAppliedFilterStore - Complex Scenarios', () => {
beforeEach(() => {
vi.useFakeTimers();
});
@@ -516,7 +516,7 @@ describe('createFilterManager - Complex Scenarios', () => {
it('handles query changes and filter selections together', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -553,7 +553,7 @@ describe('createFilterManager - Complex Scenarios', () => {
},
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -582,7 +582,7 @@ describe('createFilterManager - Complex Scenarios', () => {
it('manages multiple independent filter groups correctly', () => {
const groups = createTestGroups(4, 5);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -607,7 +607,7 @@ describe('createFilterManager - Complex Scenarios', () => {
it('handles toggle operations via getGroup', () => {
const groups = createTestGroups(2, 3);
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -623,9 +623,9 @@ describe('createFilterManager - Complex Scenarios', () => {
});
});
describe('createFilterManager - Interface Compliance', () => {
describe('createAppliedFilterStore - Interface Compliance', () => {
it('exposes queryValue getter', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'test',
groups: createTestGroups(1),
});
@@ -636,7 +636,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('exposes queryValue setter', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'test',
groups: createTestGroups(1),
});
@@ -647,7 +647,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('exposes debouncedQueryValue getter', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: 'test',
groups: createTestGroups(1),
});
@@ -658,7 +658,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('exposes groups getter', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -669,7 +669,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('exposes hasAnySelection getter', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -680,7 +680,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('exposes getGroup method', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -689,7 +689,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('exposes deselectAllGlobal method', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -698,7 +698,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
it('does not expose debouncedQueryValue setter', () => {
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups: createTestGroups(1),
});
@@ -708,7 +708,7 @@ describe('createFilterManager - Interface Compliance', () => {
});
});
describe('createFilterManager - Edge Cases', () => {
describe('createAppliedFilterStore - Edge Cases', () => {
it('handles single property groups', () => {
const groups: Array<{
id: string;
@@ -722,7 +722,7 @@ describe('createFilterManager - Edge Cases', () => {
},
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -749,7 +749,7 @@ describe('createFilterManager - Edge Cases', () => {
},
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -773,7 +773,7 @@ describe('createFilterManager - Edge Cases', () => {
},
];
const manager = createFilterManager({
const manager = createAppliedFilterStore({
queryValue: '',
groups,
});
@@ -6,12 +6,12 @@
*
* @example
* ```ts
* import { filtersStore } from '$features/GetFonts';
* import { availableFilterStore } from '$features/GetFonts';
*
* // Access filters (reactive)
* $: filters = filtersStore.filters;
* $: isLoading = filtersStore.isLoading;
* $: error = filtersStore.error;
* $: filters = availableFilterStore.filters;
* $: isLoading = availableFilterStore.isLoading;
* $: error = availableFilterStore.error;
* ```
*/
@@ -31,7 +31,7 @@ import {
* Fetches and caches filter metadata using fetchProxyFilters()
* Provides reactive access to filter data
*/
export class FiltersStore {
export class AvailableFilterStore {
/**
* TanStack Query result state
*/
@@ -125,4 +125,4 @@ export class FiltersStore {
/**
* Singleton instance
*/
export const filtersStore = new FiltersStore();
export const availableFilterStore = new AvailableFilterStore();
@@ -9,7 +9,7 @@ import {
} from 'vitest';
import * as filtersApi from '../../../api/filters/filters';
import type { FilterMetadata } from '../../../api/filters/filters';
import { FiltersStore } from './filters.svelte';
import { AvailableFilterStore } from './availableFilterStore.svelte';
/**
* Build a minimal FilterMetadata fixture for tests.
@@ -29,8 +29,8 @@ function metadata(id: string, optionValues: string[] = []): FilterMetadata {
} as FilterMetadata;
}
describe('FiltersStore', () => {
let store: FiltersStore;
describe('AvailableFilterStore', () => {
let store: AvailableFilterStore;
beforeEach(() => {
queryClient.clear();
@@ -47,13 +47,13 @@ describe('FiltersStore', () => {
describe('initial state', () => {
it('starts with an empty filter list', () => {
vi.spyOn(filtersApi, 'fetchProxyFilters').mockResolvedValue([]);
store = new FiltersStore();
store = new AvailableFilterStore();
expect(store.filters).toEqual([]);
});
it('reports null error before any failure', () => {
vi.spyOn(filtersApi, 'fetchProxyFilters').mockResolvedValue([]);
store = new FiltersStore();
store = new AvailableFilterStore();
expect(store.error).toBeNull();
});
});
@@ -66,7 +66,7 @@ describe('FiltersStore', () => {
];
vi.spyOn(filtersApi, 'fetchProxyFilters').mockResolvedValue(data);
store = new FiltersStore();
store = new AvailableFilterStore();
await vi.waitFor(() => expect(store.filters).toEqual(data), { timeout: 1000 });
expect(store.isError).toBe(false);
@@ -75,7 +75,7 @@ describe('FiltersStore', () => {
it('calls fetchProxyFilters exactly once for the initial load', async () => {
const spy = vi.spyOn(filtersApi, 'fetchProxyFilters').mockResolvedValue([]);
store = new FiltersStore();
store = new AvailableFilterStore();
await vi.waitFor(() => expect(spy).toHaveBeenCalledTimes(1), { timeout: 1000 });
});
@@ -84,7 +84,7 @@ describe('FiltersStore', () => {
describe('error handling', () => {
it('flips isError and exposes the error message on fetch failure', async () => {
vi.spyOn(filtersApi, 'fetchProxyFilters').mockRejectedValue(new Error('boom'));
store = new FiltersStore();
store = new AvailableFilterStore();
await vi.waitFor(() => expect(store.isError).toBe(true), { timeout: 1000 });
expect(store.error).toBe('boom');
@@ -97,13 +97,13 @@ describe('FiltersStore', () => {
const data = [metadata('providers', ['google'])];
const spy = vi.spyOn(filtersApi, 'fetchProxyFilters').mockResolvedValue(data);
store = new FiltersStore();
store = new AvailableFilterStore();
await vi.waitFor(() => expect(store.filters).toEqual(data), { timeout: 1000 });
expect(spy).toHaveBeenCalledTimes(1);
// A second observer on the same query key should reuse the cached
// result rather than triggering a new request.
const second = new FiltersStore();
const second = new AvailableFilterStore();
try {
// Give the new observer a tick to potentially refetch (it shouldn't).
await new Promise(r => setTimeout(r, 50));
@@ -1,5 +1,5 @@
/**
* Bridges feature-level UI state (filterManager + sortStore) to the
* Bridges feature-level UI state (appliedFilterStore + sortStore) to the
* entity-level fontStore query params.
*
* Centralizing this here means consumers (Search, FontSearch,
@@ -11,22 +11,22 @@
import { fontStore } from '$entities/Font';
import { untrack } from 'svelte';
import { mapManagerToParams } from '../../lib/mapper/mapManagerToParams';
import { filterManager } from './filterManager/filterManager.svelte';
import { filtersStore } from './filters/filters.svelte';
import { mapAppliedFiltersToParams } from '../../lib/mapper/mapAppliedFiltersToParams';
import { appliedFilterStore } from './appliedFilterStore/appliedFilterStore.svelte';
import { availableFilterStore } from './availableFilterStore/availableFilterStore.svelte';
import { sortStore } from './sortStore/sortStore.svelte';
$effect.root(() => {
/**
* Populate filterManager groups when backend filter metadata resolves.
* filtersStore is async; until it loads, filterManager has empty groups
* Populate appliedFilterStore groups when backend filter metadata resolves.
* availableFilterStore is async; until it loads, appliedFilterStore has empty groups
* and the UI renders nothing for them.
*/
$effect(() => {
const dynamicFilters = filtersStore.filters;
const dynamicFilters = availableFilterStore.filters;
if (dynamicFilters.length > 0) {
filterManager.setGroups(
appliedFilterStore.setGroups(
dynamicFilters.map(filter => ({
id: filter.id,
label: filter.name,
@@ -47,7 +47,7 @@ $effect.root(() => {
* into this effect's dependency graph.
*/
$effect(() => {
const params = mapManagerToParams(filterManager);
const params = mapAppliedFiltersToParams(appliedFilterStore);
untrack(() => fontStore.setParams(params));
});
@@ -9,7 +9,7 @@ const { Story } = defineMeta({
docs: {
description: {
component:
'Renders the full list of filter groups managed by filterManager. Each group maps to a collapsible FilterGroup with checkboxes. No props — reads directly from the filterManager singleton.',
'Renders the full list of filter groups managed by appliedFilterStore. Each group maps to a collapsible FilterGroup with checkboxes. No props — reads directly from the appliedFilterStore singleton.',
},
story: { inline: false },
},
@@ -4,10 +4,10 @@
-->
<script lang="ts">
import { FilterGroup } from '$shared/ui';
import { filterManager } from '../../model';
import { appliedFilterStore } from '../../model';
</script>
{#each filterManager.groups as group (group.id)}
{#each appliedFilterStore.groups as group (group.id)}
<FilterGroup
displayedLabel={group.label}
filter={group.instance}
@@ -1,6 +1,6 @@
import {
filterManager,
filtersStore,
appliedFilterStore,
availableFilterStore,
} from '$features/GetFonts';
import {
render,
@@ -11,9 +11,9 @@ import Filters from './Filters.svelte';
describe('Filters', () => {
beforeEach(() => {
// Clear groups and mock filtersStore to be empty so the auto-sync effect doesn't overwrite us
filterManager.setGroups([]);
vi.spyOn(filtersStore, 'filters', 'get').mockReturnValue([]);
// Clear groups and mock availableFilterStore to be empty so the auto-sync effect doesn't overwrite us
appliedFilterStore.setGroups([]);
vi.spyOn(availableFilterStore, 'filters', 'get').mockReturnValue([]);
});
afterEach(() => {
@@ -28,7 +28,7 @@ describe('Filters', () => {
});
it('renders a label for each filter group', () => {
filterManager.setGroups([
appliedFilterStore.setGroups([
{ id: 'cat', label: 'Categories', properties: [] },
{ id: 'prov', label: 'Font Providers', properties: [] },
]);
@@ -38,7 +38,7 @@ describe('Filters', () => {
});
it('renders filter properties within groups', () => {
filterManager.setGroups([
appliedFilterStore.setGroups([
{
id: 'cat',
label: 'Category',
@@ -54,7 +54,7 @@ describe('Filters', () => {
});
it('renders multiple groups with their properties', () => {
filterManager.setGroups([
appliedFilterStore.setGroups([
{
id: 'cat',
label: 'Category',
@@ -10,7 +10,7 @@ const { Story } = defineMeta({
docs: {
description: {
component:
'Sort options and Reset_Filters button rendered below the filter list. Reads sort state from sortStore and dispatches resets via filterManager. Requires responsive context — wrap with Providers.',
'Sort options and Reset_Filters button rendered below the filter list. Reads sort state from sortStore and dispatches resets via appliedFilterStore. Requires responsive context — wrap with Providers.',
},
story: { inline: false },
},
@@ -12,7 +12,7 @@ import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
import { getContext } from 'svelte';
import {
SORT_OPTIONS,
filterManager,
appliedFilterStore,
sortStore,
} from '../../model';
@@ -31,7 +31,7 @@ const responsive = getContext<ResponsiveManager>('responsive');
const isMobileOrTabletPortrait = $derived(responsive.isMobile || responsive.isTabletPortrait);
function handleReset() {
filterManager.deselectAllGlobal();
appliedFilterStore.deselectAllGlobal();
}
</script>
@@ -1,11 +1,11 @@
<!--
Component: Search
Typeface search input for the comparison view.
Writes through filterManager; the global bridge in $features/GetFonts
Writes through appliedFilterStore; the global bridge in $features/GetFonts
propagates the value into fontStore.
-->
<script lang="ts">
import { filterManager } from '$features/GetFonts';
import { appliedFilterStore } from '$features/GetFonts';
import { SearchBar } from '$shared/ui';
</script>
@@ -15,7 +15,7 @@ import { SearchBar } from '$shared/ui';
class="w-full"
placeholder="Typeface Search"
aria-label="Search typefaces"
bind:value={filterManager.queryValue}
bind:value={appliedFilterStore.queryValue}
fullWidth
/>
</div>
@@ -1,4 +1,4 @@
import { filterManager } from '$features/GetFonts';
import { appliedFilterStore } from '$features/GetFonts';
import {
render,
screen,
@@ -7,7 +7,7 @@ import Search from './Search.svelte';
describe('Search', () => {
beforeEach(() => {
filterManager.queryValue = '';
appliedFilterStore.queryValue = '';
});
describe('Rendering', () => {
@@ -23,8 +23,8 @@ describe('Search', () => {
});
describe('Value binding', () => {
it('reflects filterManager.queryValue as initial value', () => {
filterManager.queryValue = 'Inter';
it('reflects appliedFilterStore.queryValue as initial value', () => {
appliedFilterStore.queryValue = 'Inter';
render(Search);
expect(screen.getByRole('textbox')).toHaveValue('Inter');
});
@@ -6,7 +6,7 @@
import {
FilterControls,
Filters,
filterManager,
appliedFilterStore,
} from '$features/GetFonts';
import { springySlideFade } from '$shared/lib';
import {
@@ -58,7 +58,7 @@ function toggleFilters() {
class="w-full"
placeholder="Typeface Search"
aria-label="Search typefaces"
bind:value={filterManager.queryValue}
bind:value={appliedFilterStore.queryValue}
fullWidth
/>