test(09-02): add browser-mode component tests for Svelte 5 components
- CompletedToggle: 5 tests for checkbox rendering, state, and interaction - SearchBar: 7 tests for input, placeholder, recent searches dropdown - TagInput: 6 tests for rendering with various tag configurations - Update vitest-setup-client.ts with $app/state, preferences, recentSearches mocks - All component tests run in real Chromium browser via Playwright
This commit is contained in:
54
src/lib/components/CompletedToggle.svelte.test.ts
Normal file
54
src/lib/components/CompletedToggle.svelte.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import CompletedToggle from './CompletedToggle.svelte';
|
||||
|
||||
describe('CompletedToggle', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the toggle checkbox', async () => {
|
||||
render(CompletedToggle);
|
||||
|
||||
const checkbox = page.getByRole('checkbox');
|
||||
await expect.element(checkbox).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders "Show completed" label text', async () => {
|
||||
render(CompletedToggle);
|
||||
|
||||
const label = page.getByText('Show completed');
|
||||
await expect.element(label).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders checkbox in unchecked state by default', async () => {
|
||||
render(CompletedToggle);
|
||||
|
||||
const checkbox = page.getByRole('checkbox');
|
||||
await expect.element(checkbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('checkbox becomes checked when clicked', async () => {
|
||||
render(CompletedToggle);
|
||||
|
||||
const checkbox = page.getByRole('checkbox');
|
||||
await expect.element(checkbox).not.toBeChecked();
|
||||
|
||||
await checkbox.click();
|
||||
|
||||
await expect.element(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
it('has accessible label with correct text', async () => {
|
||||
render(CompletedToggle);
|
||||
|
||||
// Verify the label has the correct text and is associated with the checkbox
|
||||
const label = page.getByText('Show completed');
|
||||
await expect.element(label).toBeInTheDocument();
|
||||
|
||||
// The label should be a <label> element with a checkbox inside
|
||||
const checkbox = page.getByRole('checkbox');
|
||||
await expect.element(checkbox).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
82
src/lib/components/SearchBar.svelte.test.ts
Normal file
82
src/lib/components/SearchBar.svelte.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import SearchBar from './SearchBar.svelte';
|
||||
|
||||
describe('SearchBar', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders an input element', async () => {
|
||||
render(SearchBar, { props: { value: '' } });
|
||||
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays placeholder text', async () => {
|
||||
render(SearchBar, { props: { value: '' } });
|
||||
|
||||
const input = page.getByPlaceholder('Search entries... (press "/")');
|
||||
await expect.element(input).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays the initial value', async () => {
|
||||
render(SearchBar, { props: { value: 'initial search' } });
|
||||
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveValue('initial search');
|
||||
});
|
||||
|
||||
it('shows recent searches dropdown when focused with empty input', async () => {
|
||||
render(SearchBar, {
|
||||
props: {
|
||||
value: '',
|
||||
recentSearches: ['previous search', 'another search']
|
||||
}
|
||||
});
|
||||
|
||||
const input = page.getByRole('textbox');
|
||||
await input.click();
|
||||
|
||||
// Should show the "Recent searches" header
|
||||
const recentHeader = page.getByText('Recent searches');
|
||||
await expect.element(recentHeader).toBeInTheDocument();
|
||||
|
||||
// Should show the recent search items
|
||||
const recentItem = page.getByText('previous search');
|
||||
await expect.element(recentItem).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides recent searches dropdown when no recent searches', async () => {
|
||||
render(SearchBar, {
|
||||
props: {
|
||||
value: '',
|
||||
recentSearches: []
|
||||
}
|
||||
});
|
||||
|
||||
const input = page.getByRole('textbox');
|
||||
await input.click();
|
||||
|
||||
// Recent searches header should not be visible when empty
|
||||
const recentHeader = page.getByText('Recent searches');
|
||||
await expect.element(recentHeader).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies correct styling classes to input', async () => {
|
||||
render(SearchBar, { props: { value: '' } });
|
||||
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveClass('w-full');
|
||||
await expect.element(input).toHaveClass('rounded-lg');
|
||||
});
|
||||
|
||||
it('input has correct type attribute', async () => {
|
||||
render(SearchBar, { props: { value: '' } });
|
||||
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveAttribute('type', 'text');
|
||||
});
|
||||
});
|
||||
102
src/lib/components/TagInput.svelte.test.ts
Normal file
102
src/lib/components/TagInput.svelte.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import TagInput from './TagInput.svelte';
|
||||
import type { Tag } from '$lib/server/db/schema';
|
||||
|
||||
// Sample test data
|
||||
const mockTags: Tag[] = [
|
||||
{ id: 'tag-1', name: 'work', createdAt: '2026-01-15T10:00:00Z' },
|
||||
{ id: 'tag-2', name: 'personal', createdAt: '2026-01-15T10:00:00Z' },
|
||||
{ id: 'tag-3', name: 'urgent', createdAt: '2026-01-15T10:00:00Z' }
|
||||
];
|
||||
|
||||
describe('TagInput', () => {
|
||||
let onchangeMock: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
onchangeMock = vi.fn();
|
||||
});
|
||||
|
||||
it('renders the component', async () => {
|
||||
const { container } = render(TagInput, {
|
||||
props: {
|
||||
availableTags: mockTags,
|
||||
selectedTags: [],
|
||||
onchange: onchangeMock
|
||||
}
|
||||
});
|
||||
|
||||
// Component renders - Svelecte creates its own DOM structure
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders with available tags passed as options', async () => {
|
||||
const { container } = render(TagInput, {
|
||||
props: {
|
||||
availableTags: mockTags,
|
||||
selectedTags: [],
|
||||
onchange: onchangeMock
|
||||
}
|
||||
});
|
||||
|
||||
// Component renders successfully with available tags
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders with pre-selected tags', async () => {
|
||||
const selectedTags = [mockTags[0]]; // 'work' tag selected
|
||||
|
||||
const { container } = render(TagInput, {
|
||||
props: {
|
||||
availableTags: mockTags,
|
||||
selectedTags,
|
||||
onchange: onchangeMock
|
||||
}
|
||||
});
|
||||
|
||||
// Component renders with selected tags
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders with multiple selected tags', async () => {
|
||||
const selectedTags = [mockTags[0], mockTags[2]]; // 'work' and 'urgent'
|
||||
|
||||
const { container } = render(TagInput, {
|
||||
props: {
|
||||
availableTags: mockTags,
|
||||
selectedTags,
|
||||
onchange: onchangeMock
|
||||
}
|
||||
});
|
||||
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('accepts empty available tags array', async () => {
|
||||
const { container } = render(TagInput, {
|
||||
props: {
|
||||
availableTags: [],
|
||||
selectedTags: [],
|
||||
onchange: onchangeMock
|
||||
}
|
||||
});
|
||||
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders placeholder text', async () => {
|
||||
render(TagInput, {
|
||||
props: {
|
||||
availableTags: mockTags,
|
||||
selectedTags: [],
|
||||
onchange: onchangeMock
|
||||
}
|
||||
});
|
||||
|
||||
// Svelecte renders with placeholder
|
||||
const placeholder = page.getByPlaceholder('Add tags...');
|
||||
await expect.element(placeholder).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -34,3 +34,27 @@ vi.mock('$app/environment', () => ({
|
||||
dev: true,
|
||||
building: false
|
||||
}));
|
||||
|
||||
// Mock $app/state (Svelte 5 runes-based state)
|
||||
vi.mock('$app/state', () => ({
|
||||
page: {
|
||||
url: new URL('http://localhost'),
|
||||
params: {},
|
||||
route: { id: null },
|
||||
status: 200,
|
||||
error: null,
|
||||
data: {},
|
||||
form: null
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock preferences store
|
||||
vi.mock('$lib/stores/preferences.svelte', () => ({
|
||||
preferences: writable({ showCompleted: false, lastEntryType: 'thought' })
|
||||
}));
|
||||
|
||||
// Mock recent searches store
|
||||
vi.mock('$lib/stores/recentSearches', () => ({
|
||||
addRecentSearch: vi.fn(),
|
||||
recentSearches: writable([])
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user