Error in user YAML: (<unknown>): did not find expected alphabetic or numeric character while scanning an alias at line 2 column 8
---
trigger: glob
globs: **/*.test.ts,**/*.svelte.test.ts,**/*.ssr.test.ts
---
You are an expert in Svelte 5, SvelteKit, TypeScript, and modern testing with vitest-browser-svelte.
- Aim for 100% test coverage using complete test structure planning
- Start with all describe blocks and test stubs using
.skip - Implement tests incrementally - remove
.skipas you write each test - Test all code paths - every branch, condition, and edge case
- Component Tests:
*.svelte.test.ts- Real browser testing - SSR Tests:
*.ssr.test.ts- Server-side rendering validation - Server Tests:
*.test.ts- API routes, utilities, business logic
// ❌ NEVER use containers
const { container } = render(MyComponent);
// ✅ ALWAYS use locators with auto-retry
render(MyComponent);
const button = page.getByTestId('submit');
await button.click();// ❌ FAILS: Multiple elements match
page.getByRole('link', { name: 'Home' });
// ✅ CORRECT: Use .first(), .nth(), .last()
page.getByRole('link', { name: 'Home' }).first();// ✅ Always use untrack() for $derived
expect(untrack(() => derived_value)).toBe(expected);
// ✅ For getters: get function first, then untrack
const derived_fn = state_object.derived_value;
expect(untrack(() => derived_fn())).toBe(expected);// ✅ Test the full lifecycle: valid → validate → invalid → fix
const form = create_form_state({
email: { value: '', validation_rules: { required: true } },
});
expect(untrack(() => form.is_form_valid())).toBe(true); // Initially valid
form.validate_all_fields();
expect(untrack(() => form.is_form_valid())).toBe(false); // Now invalidServer unit tests with heavy mocking can pass while production breaks due to client-server mismatches.
// ❌ BRITTLE: Heavy mocking hides mismatches
const mock_request = {
formData: vi.fn().mockResolvedValue({
get: vi.fn().mockReturnValue('test@example.com'),
}),
};
// ✅ ROBUST: Real FormData objects catch mismatches
const form_data = new FormData();
form_data.append('email', 'test@example.com');
const request = new Request('http://localhost/register', {
method: 'POST',
body: form_data,
});
// Only mock external services (database), not data structures
vi.mocked(database.create_user).mockResolvedValue({
id: '123',
email: 'test@example.com',
});describe('ComponentName', () => {
describe('Initial Rendering', () => {
test('should render with default props', async () => {
// Implemented test
});
test.skip('should render with all prop variants', async () => {
// TODO: Test all type combinations
});
});
describe('User Interactions', () => {
test.skip('should handle click events', async () => {
// TODO: Real browser click events
});
});
describe('Edge Cases', () => {
test.skip('should handle empty data gracefully', async () => {
// TODO: Test with null/undefined/empty arrays
});
});
describe('Accessibility', () => {
test.skip('should have proper ARIA roles', async () => {
// TODO: Test accessibility features
});
});
});import { render } from 'svelte/server';
test('should render without errors', () => {
expect(() => render(ComponentName)).not.toThrow();
});
test('should render essential content', () => {
const { body } = render(ComponentName);
expect(body).toContain('expected-content');
});Don't test exact implementation details that provide no user value.
// ❌ BRITTLE - Tests exact SVG path data
expect(body).toContain(
'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
);
// ✅ ROBUST - Tests semantic styling and structure
expect(body).toContain('text-success');
expect(body).toContain('<svg');
// ✅ BEST - Tests user-visible behavior
await expect
.element(page.getByRole('img', { name: /success/i }))
.toBeInTheDocument();Why: SVG paths change when icon libraries update. Test CSS classes, semantic structure, and user experience instead.
- Cause: Multiple elements match (common with responsive navigation)
- Solution: Use
.first(),.nth(),.last()to target specific elements
- Cause: Mock function signature doesn't match actual function
- Solution: Update mock to accept correct number of arguments
- Cause: Trying to call
getContextin test - Solution: Skip the test and add TODO comment for Svelte 5
// ❌ WRONG: Looking for link when element has role="button"
page.getByRole('link', { name: 'Submit' }); // <a role="button">Submit</a>
// ✅ CORRECT: Use the actual role
page.getByRole('button', { name: 'Submit' });
// ❌ WRONG: Input role doesn't exist
page.getByRole('input', { name: 'Email' });
// ✅ CORRECT: Use textbox for input elements
page.getByRole('textbox', { name: 'Email' });- Use locators (
page.getBy*()) - never containers - Always await locator assertions:
await expect.element() - Use
.first(),.nth(),.last()for multiple elements - Use
untrack()for$derivedvalues - Use
force: truefor animations:await element.click({ force: true }) - Test form validation lifecycle: initial (valid) → validate → fix
- Use smoke tests for complex reactive components
- Test CSS classes that control appearance (
text-success,h-4 w-4) - Test semantic HTML structure and user experience
- Use real FormData/Request objects in server tests
- Never click SvelteKit form submits - test state directly
- Don't ignore strict mode violations - use
.first()instead - Don't expect forms to be invalid initially
- Don't assume element roles - verify with browser dev tools
- Don't test implementation details (SVG paths, internal markup)
- Don't write brittle tests that break when libraries update
- Don't mock browser APIs - real APIs work in vitest-browser-svelte
- Avoid children props in vitest-browser-svelte
page.getByRole('button', { name: 'Submit' });
page.getByRole('textbox', { name: 'Email' });
page.getByLabel('Email address');
page.getByText('Welcome');// Handle desktop + mobile nav components
page.getByRole('link', { name: 'Home' }).first();
page.getByRole('link', { name: 'Home' }).nth(1);
page.getByRole('link', { name: 'Home' }).last();// ❌ Can cause infinite hangs
await submit_button.click();
// ✅ Test form state directly
render(MyForm, { props: { errors: { email: 'Required' } } });
await expect.element(page.getByText('Required')).toBeInTheDocument();