Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c69d7a5b3 | |||
| 993812de0a | |||
| 67c16530af | |||
| fbbb439023 | |||
| c2046770ef | |||
| adfba38063 |
@@ -0,0 +1,11 @@
|
||||
import { render } from '@testing-library/svelte';
|
||||
import BreadcrumbHeader from './BreadcrumbHeader.svelte';
|
||||
|
||||
const context = new Map([['responsive', { isMobile: false, isMobileOrTablet: false }]]);
|
||||
|
||||
describe('BreadcrumbHeader', () => {
|
||||
it('renders nothing when no sections have been scrolled past', () => {
|
||||
const { container } = render(BreadcrumbHeader, { context });
|
||||
expect(container.firstElementChild).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/svelte';
|
||||
import { themeManager } from '../../model';
|
||||
import ThemeSwitch from './ThemeSwitch.svelte';
|
||||
|
||||
const context = new Map([['responsive', { isMobile: false }]]);
|
||||
|
||||
describe('ThemeSwitch', () => {
|
||||
beforeEach(() => {
|
||||
themeManager.setTheme('light');
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders an icon button', () => {
|
||||
render(ThemeSwitch, { context });
|
||||
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has "Toggle theme" title', () => {
|
||||
render(ThemeSwitch, { context });
|
||||
expect(screen.getByTitle('Toggle theme')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an SVG icon', () => {
|
||||
const { container } = render(ThemeSwitch, { context });
|
||||
expect(container.querySelector('svg')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Interaction', () => {
|
||||
it('toggles theme from light to dark on click', async () => {
|
||||
render(ThemeSwitch, { context });
|
||||
expect(themeManager.value).toBe('light');
|
||||
await fireEvent.click(screen.getByRole('button'));
|
||||
expect(themeManager.value).toBe('dark');
|
||||
});
|
||||
|
||||
it('toggles theme from dark to light on click', async () => {
|
||||
themeManager.setTheme('dark');
|
||||
render(ThemeSwitch, { context });
|
||||
await fireEvent.click(screen.getByRole('button'));
|
||||
expect(themeManager.value).toBe('light');
|
||||
});
|
||||
|
||||
it('double click returns to original theme', async () => {
|
||||
render(ThemeSwitch, { context });
|
||||
const btn = screen.getByRole('button');
|
||||
await fireEvent.click(btn);
|
||||
await fireEvent.click(btn);
|
||||
expect(themeManager.value).toBe('light');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { filterManager } from '$features/GetFonts';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/svelte';
|
||||
import Filters from './Filters.svelte';
|
||||
|
||||
describe('Filters', () => {
|
||||
beforeEach(() => {
|
||||
filterManager.setGroups([]);
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders nothing when filter groups are empty', () => {
|
||||
const { container } = render(Filters);
|
||||
expect(container.firstElementChild).toBeNull();
|
||||
});
|
||||
|
||||
it('renders a label for each filter group', () => {
|
||||
filterManager.setGroups([
|
||||
{ id: 'cat', label: 'Category', properties: [] },
|
||||
{ id: 'prov', label: 'Provider', properties: [] },
|
||||
]);
|
||||
render(Filters);
|
||||
expect(screen.getByText('Category')).toBeInTheDocument();
|
||||
expect(screen.getByText('Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders filter properties within groups', () => {
|
||||
filterManager.setGroups([
|
||||
{
|
||||
id: 'cat',
|
||||
label: 'Category',
|
||||
properties: [
|
||||
{ id: 'serif', name: 'Serif', value: 'serif', selected: false },
|
||||
{ id: 'sans', name: 'Sans-Serif', value: 'sans-serif', selected: false },
|
||||
],
|
||||
},
|
||||
]);
|
||||
render(Filters);
|
||||
expect(screen.getByText('Serif')).toBeInTheDocument();
|
||||
expect(screen.getByText('Sans-Serif')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders multiple groups with their properties', () => {
|
||||
filterManager.setGroups([
|
||||
{
|
||||
id: 'cat',
|
||||
label: 'Category',
|
||||
properties: [{ id: 'mono', name: 'Monospace', value: 'monospace', selected: false }],
|
||||
},
|
||||
{
|
||||
id: 'prov',
|
||||
label: 'Provider',
|
||||
properties: [{ id: 'google', name: 'Google', value: 'google', selected: false }],
|
||||
},
|
||||
]);
|
||||
render(Filters);
|
||||
expect(screen.getByText('Monospace')).toBeInTheDocument();
|
||||
expect(screen.getByText('Google')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/svelte';
|
||||
import Header from './Header.svelte';
|
||||
|
||||
const context = new Map([['responsive', { isMobile: false }]]);
|
||||
|
||||
describe('Header', () => {
|
||||
describe('Rendering', () => {
|
||||
it('renders a header element', () => {
|
||||
render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context });
|
||||
expect(document.querySelector('header')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Open Config" title when sidebar is closed', () => {
|
||||
render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context });
|
||||
expect(screen.getByTitle('Open Config')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Close Config" title when sidebar is open', () => {
|
||||
render(Header, { props: { isSidebarOpen: true, onSidebarToggle: () => {} }, context });
|
||||
expect(screen.getByTitle('Close Config')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a text input for the sample text', () => {
|
||||
render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context });
|
||||
expect(screen.getByPlaceholderText('The quick brown fox...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the theme toggle button', () => {
|
||||
render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context });
|
||||
expect(screen.getByTitle('Toggle theme')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Interaction', () => {
|
||||
it('calls onSidebarToggle when toggle button is clicked', async () => {
|
||||
const onSidebarToggle = vi.fn();
|
||||
render(Header, { props: { isSidebarOpen: false, onSidebarToggle }, context });
|
||||
await fireEvent.click(screen.getByTitle('Open Config'));
|
||||
expect(onSidebarToggle).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { filterManager } from '$features/GetFonts';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/svelte';
|
||||
import Search from './Search.svelte';
|
||||
|
||||
describe('Search', () => {
|
||||
beforeEach(() => {
|
||||
filterManager.queryValue = '';
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders a text input', () => {
|
||||
render(Search);
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has "Typeface Search" placeholder', () => {
|
||||
render(Search);
|
||||
expect(screen.getByPlaceholderText('Typeface Search')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Value binding', () => {
|
||||
it('reflects filterManager.queryValue as initial value', () => {
|
||||
filterManager.queryValue = 'Inter';
|
||||
render(Search);
|
||||
expect(screen.getByRole('textbox')).toHaveValue('Inter');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/svelte';
|
||||
import { createRawSnippet } from 'svelte';
|
||||
import { comparisonStore } from '../../model';
|
||||
import Sidebar from './Sidebar.svelte';
|
||||
|
||||
function textSnippet(text: string) {
|
||||
return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
|
||||
}
|
||||
|
||||
describe('Sidebar', () => {
|
||||
afterEach(() => {
|
||||
comparisonStore.side = 'A';
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders the "Configuration" title', () => {
|
||||
render(Sidebar);
|
||||
expect(screen.getByText('Configuration')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Left Font and Right Font toggle buttons', () => {
|
||||
render(Sidebar);
|
||||
expect(screen.getByText('Left Font')).toBeInTheDocument();
|
||||
expect(screen.getByText('Right Font')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders main snippet when provided', () => {
|
||||
render(Sidebar, { props: { main: textSnippet('main-content') } });
|
||||
expect(screen.getByText('main-content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders controls snippet when provided', () => {
|
||||
render(Sidebar, { props: { controls: textSnippet('controls-content') } });
|
||||
expect(screen.getByText('controls-content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nothing in main area when no main snippet', () => {
|
||||
const { container } = render(Sidebar);
|
||||
expect(container.querySelector('[data-main]')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('A/B toggle', () => {
|
||||
it('Left Font button is active in side A mode', () => {
|
||||
comparisonStore.side = 'A';
|
||||
render(Sidebar);
|
||||
const leftBtn = screen.getByText('Left Font').closest('button');
|
||||
expect(leftBtn).toHaveClass('shadow-sm');
|
||||
});
|
||||
|
||||
it('Right Font button is not active in side A mode', () => {
|
||||
comparisonStore.side = 'A';
|
||||
render(Sidebar);
|
||||
const rightBtn = screen.getByText('Right Font').closest('button');
|
||||
expect(rightBtn).not.toHaveClass('shadow-sm');
|
||||
});
|
||||
|
||||
it('Right Font button is active in side B mode', () => {
|
||||
comparisonStore.side = 'B';
|
||||
render(Sidebar);
|
||||
const rightBtn = screen.getByText('Right Font').closest('button');
|
||||
expect(rightBtn).toHaveClass('shadow-sm');
|
||||
});
|
||||
|
||||
it('clicking Right Font switches to side B', async () => {
|
||||
render(Sidebar);
|
||||
expect(comparisonStore.side).toBe('A');
|
||||
await fireEvent.click(screen.getByText('Right Font'));
|
||||
expect(comparisonStore.side).toBe('B');
|
||||
});
|
||||
|
||||
it('clicking Left Font switches back to side A from B', async () => {
|
||||
comparisonStore.side = 'B';
|
||||
render(Sidebar);
|
||||
await fireEvent.click(screen.getByText('Left Font'));
|
||||
expect(comparisonStore.side).toBe('A');
|
||||
});
|
||||
|
||||
it('UI updates reactively after side switch', async () => {
|
||||
render(Sidebar);
|
||||
await fireEvent.click(screen.getByText('Right Font'));
|
||||
const rightBtn = screen.getByText('Right Font').closest('button');
|
||||
await waitFor(() => expect(rightBtn).toHaveClass('shadow-sm'));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { render } from '@testing-library/svelte';
|
||||
import Thumb from './Thumb.svelte';
|
||||
|
||||
describe('Thumb', () => {
|
||||
describe('Rendering', () => {
|
||||
it('renders a container element', () => {
|
||||
const { container } = render(Thumb, { sliderPos: 50, isDragging: false });
|
||||
expect(container.firstElementChild).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies sliderPos as left CSS style', () => {
|
||||
const { container } = render(Thumb, { sliderPos: 30, isDragging: false });
|
||||
const el = container.firstElementChild as HTMLElement;
|
||||
expect(el.style.left).toBe('30%');
|
||||
});
|
||||
|
||||
it('renders two handle squares', () => {
|
||||
const { container } = render(Thumb, { sliderPos: 50, isDragging: false });
|
||||
const handles = container.querySelectorAll('.bg-brand.text-white');
|
||||
expect(handles).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dragging state', () => {
|
||||
it('applies scale-110 to handles when dragging', () => {
|
||||
const { container } = render(Thumb, { sliderPos: 50, isDragging: true });
|
||||
const handles = container.querySelectorAll('.scale-110');
|
||||
expect(handles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('applies scale-100 to handles when not dragging', () => {
|
||||
const { container } = render(Thumb, { sliderPos: 50, isDragging: false });
|
||||
const handles = container.querySelectorAll('.scale-100');
|
||||
expect(handles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('does not apply scale-110 when not dragging', () => {
|
||||
const { container } = render(Thumb, { sliderPos: 50, isDragging: false });
|
||||
expect(container.querySelector('.scale-110')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/svelte';
|
||||
import { layoutManager } from '../../model';
|
||||
import LayoutSwitch from './LayoutSwitch.svelte';
|
||||
|
||||
describe('LayoutSwitch', () => {
|
||||
beforeEach(() => {
|
||||
layoutManager.reset();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('renders two icon buttons', () => {
|
||||
render(LayoutSwitch);
|
||||
expect(screen.getAllByRole('button')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Active state', () => {
|
||||
it('list button is active in list mode', () => {
|
||||
render(LayoutSwitch);
|
||||
const [listBtn] = screen.getAllByRole('button');
|
||||
expect(listBtn).toHaveClass('text-brand');
|
||||
});
|
||||
|
||||
it('grid button is not active in list mode', () => {
|
||||
render(LayoutSwitch);
|
||||
const [, gridBtn] = screen.getAllByRole('button');
|
||||
expect(gridBtn).not.toHaveClass('text-brand');
|
||||
});
|
||||
|
||||
it('grid button is active in grid mode', () => {
|
||||
layoutManager.setMode('grid');
|
||||
render(LayoutSwitch);
|
||||
const [, gridBtn] = screen.getAllByRole('button');
|
||||
expect(gridBtn).toHaveClass('text-brand');
|
||||
});
|
||||
|
||||
it('list button is not active in grid mode', () => {
|
||||
layoutManager.setMode('grid');
|
||||
render(LayoutSwitch);
|
||||
const [listBtn] = screen.getAllByRole('button');
|
||||
expect(listBtn).not.toHaveClass('text-brand');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Interaction', () => {
|
||||
it('clicking second button switches to grid mode', async () => {
|
||||
render(LayoutSwitch);
|
||||
expect(layoutManager.mode).toBe('list');
|
||||
await fireEvent.click(screen.getAllByRole('button')[1]);
|
||||
expect(layoutManager.mode).toBe('grid');
|
||||
});
|
||||
|
||||
it('clicking first button when in grid mode switches to list mode', async () => {
|
||||
layoutManager.setMode('grid');
|
||||
render(LayoutSwitch);
|
||||
await fireEvent.click(screen.getAllByRole('button')[0]);
|
||||
expect(layoutManager.mode).toBe('list');
|
||||
});
|
||||
|
||||
it('active button updates reactively after toggle', async () => {
|
||||
render(LayoutSwitch);
|
||||
const [listBtn, gridBtn] = screen.getAllByRole('button');
|
||||
await fireEvent.click(gridBtn);
|
||||
await waitFor(() => expect(gridBtn).toHaveClass('text-brand'));
|
||||
await waitFor(() => expect(listBtn).not.toHaveClass('text-brand'));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,10 @@ import { defineConfig } from 'vitest/config';
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
|
||||
optimizeDeps: {
|
||||
exclude: ['@lucide/svelte'],
|
||||
},
|
||||
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
|
||||
Reference in New Issue
Block a user