frontend-testing
Unit and component testing for the frontend with Vitest and Testing Library. Keywords: Vitest, @testing-library/svelte, component tests, vi.mock, render, screen, fireEvent, userEvent, test.ts, spec.ts, describe, it, AAA pattern
Installation
Suivez les instructions du dépôt pour votre agent. Consultez les fichiers et dépendances avant l’installation.
Voir la source ↗GuidesInstructions
Frontend Testing
Documentation: vitest.dev | testing-library.com
Running Tests
npm run test:unit
Framework & Location
- Framework: Vitest + @testing-library/svelte
-
Location: Co-locate with code as
.test.tsor.spec.ts - TDD workflow: When fixing bugs or adding features, write a failing test first
AAA Pattern
Use explicit Arrange, Act, Assert regions:
import { describe, expect, it } from 'vitest';
describe('Calculator', () => {
it('should add two numbers correctly', () => {
// Arrange
const a = 5;
const b = 3;
// Act
const result = add(a, b);
// Assert
expect(result).toBe(8);
});
it('should handle negative numbers', () => {
// Arrange
const a = -5;
const b = 3;
// Act
const result = add(a, b);
// Assert
expect(result).toBe(-2);
});
});
Test Patterns from Codebase
Unit Tests with AAA
From dates.test.ts:
import { describe, expect, it } from 'vitest';
import { getDifferenceInSeconds, getRelativeTimeFormatUnit } from './dates';
describe('getDifferenceInSeconds', () => {
it('should calculate difference in seconds correctly', () => {
// Arrange
const now = new Date();
const past = new Date(now.getTime() - 5000);
// Act
const result = getDifferenceInSeconds(past);
// Assert
expect(result).toBeCloseTo(5, 0);
});
});
describe('getRelativeTimeFormatUnit', () => {
it('should return correct unit for given seconds', () => {
// Arrange & Act & Assert (simple value tests)
expect(getRelativeTimeFormatUnit(30)).toBe('seconds');
expect(getRelativeTimeFormatUnit(1800)).toBe('minutes');
expect(getRelativeTimeFormatUnit(7200)).toBe('hours');
});
it('should handle boundary cases correctly', () => {
// Arrange & Act & Assert
expect(getRelativeTimeFormatUnit(59)).toBe('seconds');
expect(getRelativeTimeFormatUnit(60)).toBe('minutes');
});
});
Testing with Spies
From cached-persisted-state.svelte.test.ts:
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { CachedPersistedState } from './cached-persisted-state.svelte';
describe('CachedPersistedState', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should initialize with default value when storage is empty', () => {
// Arrange & Act
const state = new CachedPersistedState('test-key', 'default');
// Assert
expect(state.current).toBe('default');
});
it('should return cached value without reading storage repeatedly', () => {
// Arrange
const getItemSpy = vi.spyOn(Storage.prototype, 'getItem');
localStorage.setItem('test-key', 'value1');
const state = new CachedPersistedState('test-key', 'default');
getItemSpy.mockClear();
// Act
const val1 = state.current;
const val2 = state.current;
// Assert
expect(val1).toBe('value1');
expect(val2).toBe('value1');
expect(getItemSpy).not.toHaveBeenCalled();
});
});
Testing String Transformations
From helpers.svelte.test.ts:
import { describe, expect, it } from 'vitest';
import { quoteIfSpecialCharacters } from './helpers.svelte';
describe('helpers.svelte', () => {
it('quoteIfSpecialCharacters handles tabs and newlines', () => {
// Arrange & Act & Assert
expect(quoteIfSpecialCharacters('foo\tbar')).toBe('"foo\tbar"');
expect(quoteIfSpecialCharacters('foo\nbar')).toBe('"foo\nbar"');
});
it('quoteIfSpecialCharacters handles empty string and undefined/null', () => {
// Arrange & Act & Assert
expect(quoteIfSpecialCharacters('')).toBe('');
expect(quoteIfSpecialCharacters(undefined)).toBeUndefined();
expect(quoteIfSpecialCharacters(null)).toBeNull();
});
it('quoteIfSpecialCharacters quotes all Lucene special characters', () => {
// Arrange
const luceneSpecials = ['+', '-', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', '\\', '/'];
// Act & Assert
for (const char of luceneSpecials) {
expect(quoteIfSpecialCharacters(char)).toBe(`"${char}"`);
}
});
});
Query Selection Priority
Use accessible queries (not implementation details):
// ✅ Role-based
screen.getByRole('button', { name: /submit/i });
screen.getByRole('textbox', { name: /email/i });
// ✅ Label-based
screen.getByLabelText('Email address');
// ✅ Text-based
screen.getByText('Welcome back');
// ⚠️ Fallback: Test ID
screen.getByTestId('complex-chart');
// ❌ Avoid: Implementation details
screen.getByClassName('btn-primary');
Mocking Modules
import { vi, describe, it, beforeEach, expect } from 'vitest';
import { render, screen } from '@testing-library/svelte';
vi.mock('$lib/api/organizations', () => ({
getOrganizations: vi.fn()
}));
import { getOrganizations } from '$lib/api/organizations';
import OrganizationList from './organization-list.svelte';
describe('OrganizationList', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('displays organizations from API', async () => {
// Arrange
const mockOrganizations = [{ id: '1', name: 'Org One' }];
vi.mocked(getOrganizations).mockResolvedValue(mockOrganizations);
// Act
render(OrganizationList);
// Assert
expect(await screen.findByText('Org One')).toBeInTheDocument();
});
});
Snapshot Testing (Use Sparingly)
it('matches snapshot', () => {
// Arrange & Act
const { container } = render(StaticComponent);
// Assert
expect(container).toMatchSnapshot();
});
Use snapshots only for stable, static components. Prefer explicit assertions for dynamic content.
Exemples
Consultez les exemples du dépôt source.
Voir la source ↗Historique source
- Date d’indexation
- 2026-01-20
- Mise à jour de l’index
- 2026-01-30
- Branch
- main
Les dates concernent ce répertoire. Consultez le dépôt pour l’historique du code.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!