refactor(createFilter): createFilterStore rewrote to runes
This commit is contained in:
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { createFilter } from '$shared/lib';
|
||||||
type Filter,
|
|
||||||
createFilter,
|
|
||||||
} from '$shared/lib/utils';
|
|
||||||
import type { FilterGroupConfig } from '../../model/const/types/common';
|
import type { FilterGroupConfig } from '../../model/const/types/common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,14 +19,6 @@ export function createFilterManager(configs: FilterGroupConfig[]) {
|
|||||||
groups.some(group => group.instance.selectedProperties.length > 0),
|
groups.some(group => group.instance.selectedProperties.length > 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Derived: total count across all groups
|
|
||||||
const totalSelectedCount = $derived(
|
|
||||||
groups.reduce(
|
|
||||||
(acc, group) => acc + group.instance.selectedProperties.length,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Direct array reference (reactive)
|
// Direct array reference (reactive)
|
||||||
get groups() {
|
get groups() {
|
||||||
@@ -40,9 +29,6 @@ export function createFilterManager(configs: FilterGroupConfig[]) {
|
|||||||
get hasAnySelection() {
|
get hasAnySelection() {
|
||||||
return hasAnySelection;
|
return hasAnySelection;
|
||||||
},
|
},
|
||||||
get totalSelectedCount() {
|
|
||||||
return totalSelectedCount;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Global action
|
// Global action
|
||||||
deselectAllGlobal: () => {
|
deselectAllGlobal: () => {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { SvelteSet } from 'svelte/reactivity';
|
|
||||||
|
|
||||||
export interface Property {
|
export interface Property {
|
||||||
/**
|
/**
|
||||||
* Property identifier
|
* Property identifier
|
||||||
@@ -40,15 +38,8 @@ export function createFilter<T extends FilterModel>(
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedProperties = $derived.by(() => {
|
const selectedProperties = $derived(properties.filter(p => p.selected));
|
||||||
const _ = properties;
|
const selectedCount = $derived(selectedProperties.length);
|
||||||
return properties.filter(p => p.selected);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedCount = $derived.by(() => {
|
|
||||||
const _ = properties;
|
|
||||||
return selectedProperties.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import { get } from 'svelte/store';
|
|
||||||
import {
|
|
||||||
beforeEach,
|
|
||||||
describe,
|
|
||||||
expect,
|
|
||||||
it,
|
|
||||||
} from 'vitest';
|
|
||||||
import {
|
|
||||||
type FilterModel,
|
|
||||||
type Property,
|
|
||||||
createFilterStore,
|
|
||||||
} from './createFilterStore';
|
|
||||||
|
|
||||||
describe('createFilterStore', () => {
|
|
||||||
const mockProperties: Property[] = [
|
|
||||||
{ id: '1', name: 'Sans-serif', selected: false },
|
|
||||||
{ id: '2', name: 'Serif', selected: false },
|
|
||||||
{ id: '3', name: 'Display', selected: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
let store: ReturnType<typeof createFilterStore>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const initialState: FilterModel = {
|
|
||||||
searchQuery: '',
|
|
||||||
properties: mockProperties,
|
|
||||||
};
|
|
||||||
store = createFilterStore(initialState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('initializes with correct state', () => {
|
|
||||||
const state = get(store);
|
|
||||||
expect(state).toEqual({
|
|
||||||
searchQuery: '',
|
|
||||||
properties: mockProperties,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets search query', () => {
|
|
||||||
store.setSearchQuery('serif');
|
|
||||||
const state = get(store);
|
|
||||||
expect(state.searchQuery).toBe('serif');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clears search query', () => {
|
|
||||||
store.setSearchQuery('test');
|
|
||||||
store.clearSearchQuery();
|
|
||||||
const state = get(store);
|
|
||||||
expect(state.searchQuery).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('selects a property', () => {
|
|
||||||
store.selectProperty('1');
|
|
||||||
const state = get(store);
|
|
||||||
const property = state.properties.find(p => p.id === '1');
|
|
||||||
expect(property?.selected).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deselects a property', () => {
|
|
||||||
store.selectProperty('1');
|
|
||||||
store.deselectProperty('1');
|
|
||||||
const state = get(store);
|
|
||||||
const property = state.properties.find(p => p.id === '1');
|
|
||||||
expect(property?.selected).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles property from unselected to selected', () => {
|
|
||||||
store.toggleProperty('1');
|
|
||||||
const state = get(store);
|
|
||||||
const property = state.properties.find(p => p.id === '1');
|
|
||||||
expect(property?.selected).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles property from selected to unselected', () => {
|
|
||||||
store.selectProperty('1');
|
|
||||||
store.toggleProperty('1');
|
|
||||||
const state = get(store);
|
|
||||||
const property = state.properties.find(p => p.id === '1');
|
|
||||||
expect(property?.selected).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('selects all properties', () => {
|
|
||||||
store.selectAllProperties();
|
|
||||||
const state = get(store);
|
|
||||||
expect(state.properties.every(p => p.selected)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deselects all properties', () => {
|
|
||||||
store.selectAllProperties();
|
|
||||||
store.deselectAllProperties();
|
|
||||||
const state = get(store);
|
|
||||||
expect(state.properties.every(p => !p.selected)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets all properties', () => {
|
|
||||||
const allProps = store.getAllProperties();
|
|
||||||
const props = get(allProps);
|
|
||||||
expect(props).toEqual(mockProperties);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets selected properties', () => {
|
|
||||||
store.selectProperty('1');
|
|
||||||
store.selectProperty('3');
|
|
||||||
const selectedProps = store.getSelectedProperties();
|
|
||||||
const props = get(selectedProps);
|
|
||||||
expect(props).toHaveLength(2);
|
|
||||||
expect(props?.[0].id).toBe('1');
|
|
||||||
expect(props?.[1].id).toBe('3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('filters properties by search query', () => {
|
|
||||||
store.setSearchQuery('serif');
|
|
||||||
const filteredProps = store.getFilteredProperties();
|
|
||||||
const props = get(filteredProps);
|
|
||||||
// 'serif' is a substring of 'Sans-serif' (case-sensitive match)
|
|
||||||
expect(props).toHaveLength(1);
|
|
||||||
expect(props?.[0].id).toBe('1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('filter is case-sensitive', () => {
|
|
||||||
store.setSearchQuery('San');
|
|
||||||
const filteredProps = store.getFilteredProperties();
|
|
||||||
const props = get(filteredProps);
|
|
||||||
// 'San' matches 'Sans-serif' exactly (case-sensitive)
|
|
||||||
expect(props).toHaveLength(1);
|
|
||||||
expect(props?.[0].id).toBe('1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('filter returns all properties when query is empty', () => {
|
|
||||||
store.setSearchQuery('');
|
|
||||||
const filteredProps = store.getFilteredProperties();
|
|
||||||
let props: Property[] | undefined = undefined;
|
|
||||||
filteredProps.subscribe(p => (props = p))();
|
|
||||||
expect(props).toHaveLength(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Filter } from '$shared/lib/utils';
|
import type { Filter } from '$shared/lib';
|
||||||
import { Badge } from '$shared/shadcn/ui/badge';
|
import { Badge } from '$shared/shadcn/ui/badge';
|
||||||
import { buttonVariants } from '$shared/shadcn/ui/button';
|
import { buttonVariants } from '$shared/shadcn/ui/button';
|
||||||
import { Checkbox } from '$shared/shadcn/ui/checkbox';
|
import { Checkbox } from '$shared/shadcn/ui/checkbox';
|
||||||
@@ -8,7 +8,6 @@ import { Label } from '$shared/shadcn/ui/label';
|
|||||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { cubicOut } from 'svelte/easing';
|
import { cubicOut } from 'svelte/easing';
|
||||||
import type { FormEventHandler } from 'svelte/elements';
|
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user