Compare commits

...

3 Commits

8 changed files with 106 additions and 7 deletions
+6
View File
@@ -43,6 +43,12 @@ jobs:
- name: Type Check - name: Type Check
run: yarn check run: yarn check
- name: Run Unit Tests
run: yarn test:unit
- name: Run Component Tests
run: yarn test:component
publish: publish:
needs: build # Only runs if tests/lint pass needs: build # Only runs if tests/lint pass
runs-on: ubuntu-latest runs-on: ubuntu-latest
+4
View File
@@ -13,6 +13,10 @@ pre-commit:
pre-push: pre-push:
parallel: true parallel: true
commands: commands:
test-unit:
run: yarn test:unit
test-component:
run: yarn test:component
type-check: type-check:
run: yarn tsc --noEmit run: yarn tsc --noEmit
+1
View File
@@ -4,6 +4,7 @@ export {
mapManagerToParams, mapManagerToParams,
} from './lib'; } from './lib';
export { filtersStore } from './model/state/filters.svelte';
export { filterManager } from './model/state/manager.svelte'; export { filterManager } from './model/state/manager.svelte';
export { export {
@@ -1,29 +1,40 @@
import { filterManager } from '$features/GetFonts'; import {
filterManager,
filtersStore,
} from '$features/GetFonts';
import { import {
render, render,
screen, screen,
} from '@testing-library/svelte'; } from '@testing-library/svelte';
import { vi } from 'vitest';
import Filters from './Filters.svelte'; 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
filterManager.setGroups([]); filterManager.setGroups([]);
vi.spyOn(filtersStore, 'filters', 'get').mockReturnValue([]);
});
afterEach(() => {
vi.restoreAllMocks();
}); });
describe('Rendering', () => { describe('Rendering', () => {
it('renders nothing when filter groups are empty', () => { it('renders nothing when filter groups are empty', () => {
const { container } = render(Filters); const { container } = render(Filters);
expect(container.firstElementChild).toBeNull(); // It might render an empty container if the component has one, but we expect no children
expect(container.firstChild?.childNodes.length ?? 0).toBe(0);
}); });
it('renders a label for each filter group', () => { it('renders a label for each filter group', () => {
filterManager.setGroups([ filterManager.setGroups([
{ id: 'cat', label: 'Category', properties: [] }, { id: 'cat', label: 'Categories', properties: [] },
{ id: 'prov', label: 'Provider', properties: [] }, { id: 'prov', label: 'Font Providers', properties: [] },
]); ]);
render(Filters); render(Filters);
expect(screen.getByText('Category')).toBeInTheDocument(); expect(screen.getByText('Categories')).toBeInTheDocument();
expect(screen.getByText('Provider')).toBeInTheDocument(); expect(screen.getByText('Font Providers')).toBeInTheDocument();
}); });
it('renders filter properties within groups', () => { it('renders filter properties within groups', () => {
@@ -114,7 +114,7 @@ describe('ComboControl', () => {
it('opens popover with vertical slider on trigger click', async () => { it('opens popover with vertical slider on trigger click', async () => {
render(ComboControl, { control: makeControl(50), controlLabel: 'Size control' }); render(ComboControl, { control: makeControl(50), controlLabel: 'Size control' });
expect(screen.queryByRole('slider')).not.toBeInTheDocument(); expect(screen.queryByRole('slider')).not.toBeInTheDocument();
await fireEvent.click(screen.getByLabelText('Size control')); await fireEvent.click(screen.getByText('Size control'));
await waitFor(() => expect(screen.getByRole('slider')).toBeInTheDocument()); await waitFor(() => expect(screen.getByRole('slider')).toBeInTheDocument());
}); });
}); });
+29
View File
@@ -0,0 +1,29 @@
import { svelte } from '@sveltejs/vite-plugin-svelte';
import path from 'node:path';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [svelte()],
test: {
environment: 'jsdom',
include: ['src/**/*.svelte.test.ts'],
exclude: ['node_modules', 'dist', 'e2e', '.storybook'],
restoreMocks: true,
setupFiles: ['./vitest.setup.component.ts', './vitest.setup.jsdom.ts'],
globals: true,
},
resolve: {
conditions: ['browser'],
alias: {
$lib: path.resolve(__dirname, './src/lib'),
$app: path.resolve(__dirname, './src/app'),
$shared: path.resolve(__dirname, './src/shared'),
$entities: path.resolve(__dirname, './src/entities'),
$features: path.resolve(__dirname, './src/features'),
$routes: path.resolve(__dirname, './src/routes'),
$widgets: path.resolve(__dirname, './src/widgets'),
},
},
});
+2
View File
@@ -1,3 +1,4 @@
import { queryClient } from '$shared/api/queryClient';
import * as matchers from '@testing-library/jest-dom/matchers'; import * as matchers from '@testing-library/jest-dom/matchers';
import { cleanup } from '@testing-library/svelte'; import { cleanup } from '@testing-library/svelte';
import { import {
@@ -13,6 +14,7 @@ expect.extend(matchers);
afterEach(() => { afterEach(() => {
cleanup(); cleanup();
queryClient.clear();
}); });
// Mock window.matchMedia for components that use it // Mock window.matchMedia for components that use it
+46
View File
@@ -0,0 +1,46 @@
import { vi } from 'vitest';
// jsdom lacks ResizeObserver
global.ResizeObserver = class {
observe = vi.fn();
unobserve = vi.fn();
disconnect = vi.fn();
} as unknown as typeof ResizeObserver;
// jsdom lacks Web Animations API
Element.prototype.animate = vi.fn().mockReturnValue({
onfinish: null,
cancel: vi.fn(),
finish: vi.fn(),
pause: vi.fn(),
play: vi.fn(),
});
// jsdom lacks SVG geometry methods
SVGElement.prototype.getTotalLength = vi.fn(() => 0);
// Robust localStorage mock for jsdom environment
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: vi.fn((key: string) => store[key] || null),
setItem: vi.fn((key: string, value: string) => {
store[key] = value.toString();
}),
removeItem: vi.fn((key: string) => {
delete store[key];
}),
clear: vi.fn(() => {
store = {};
}),
key: vi.fn((index: number) => Object.keys(store)[index] || null),
get length() {
return Object.keys(store).length;
},
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
writable: true,
});