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