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:
@@ -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 @@
|
||||
export { mapManagerToParams } from './mapper/mapManagerToParams';
|
||||
export { mapAppliedFiltersToParams } from './mapper/mapAppliedFiltersToParams';
|
||||
|
||||
+21
-21
@@ -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'],
|
||||
+3
-3
@@ -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
|
||||
@@ -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';
|
||||
|
||||
+7
-7
@@ -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: [],
|
||||
});
|
||||
+54
-54
@@ -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
-6
@@ -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();
|
||||
+10
-10
@@ -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
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user