feat: test coverage for store creators

This commit is contained in:
Ilia Mashkov
2026-01-06 21:34:05 +03:00
parent bea3f7ae7f
commit 931a2df1ee
3 changed files with 243 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import { get } from 'svelte/store';
import {
beforeEach,
describe,
expect,
it,
} from 'vitest';
import {
type ControlModel,
createControlStore,
} from './createControlStore';
describe('createControlStore', () => {
let store: ReturnType<typeof createControlStore<number>>;
beforeEach(() => {
const initialState: ControlModel<number> = {
value: 10,
min: 0,
max: 100,
step: 5,
};
store = createControlStore(initialState);
});
it('initializes with correct state', () => {
expect(get(store)).toEqual({
value: 10,
min: 0,
max: 100,
step: 5,
});
});
it('increases value by step', () => {
store.increase();
expect(get(store).value).toBe(15);
});
it('decreases value by step', () => {
store.decrease();
expect(get(store).value).toBe(5);
});
it('clamps value at maximum', () => {
store.setValue(200);
expect(get(store).value).toBe(100);
});
it('clamps value at minimum', () => {
store.setValue(-10);
expect(get(store).value).toBe(0);
});
it('rounds to step precision', () => {
store.setValue(12.34);
// With step=5, 12.34 is clamped and rounded to nearest integer (0 decimal places)
expect(get(store).value).toBe(12);
});
it('handles decimal steps correctly', () => {
const decimalStore = createControlStore({
value: 1.0,
min: 0,
max: 2,
step: 0.05,
});
decimalStore.increase();
expect(get(decimalStore).value).toBe(1.05);
});
it('isAtMax returns true when at maximum', () => {
store.setValue(100);
expect(store.isAtMax()).toBe(true);
});
it('isAtMax returns false when not at maximum', () => {
expect(store.isAtMax()).toBe(false);
});
it('isAtMin returns true when at minimum', () => {
store.setValue(0);
expect(store.isAtMin()).toBe(true);
});
it('isAtMin returns false when not at minimum', () => {
expect(store.isAtMin()).toBe(false);
});
});

View File

@@ -0,0 +1,136 @@
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);
});
});

View File

@@ -0,0 +1,18 @@
/**
* Shared store exports
*
* Exports all store creators and types for Svelte 5 reactive state management
*/
export { createFilterStore } from './createFilterStore/createFilterStore';
export type {
FilterModel,
FilterStore,
Property,
} from './createFilterStore/createFilterStore';
export { createControlStore } from './createControlStore/createControlStore';
export type {
ControlModel,
ControlStoreModel,
} from './createControlStore/createControlStore';