Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a8a61d8
initial setup testing on Arc
cycle4passion Jan 10, 2026
77190c9
more work on test Harness
cycle4passion Jan 11, 2026
b4132b5
fix for empty layers toggle showing small circle
cycle4passion Jan 11, 2026
41b46a4
more harness work
cycle4passion Jan 11, 2026
56945c5
Merge remote-tracking branch 'upstream/docs-v2' into docs-v2
cycle4passion Jan 11, 2026
ebc20ef
Merge branch 'docs-v2' into testing
cycle4passion Jan 11, 2026
90de1e8
first pass testing for Arc. I'm happy for any overhail for bigger pic…
cycle4passion Jan 11, 2026
2d58c5d
Testing second pass
cycle4passion Jan 12, 2026
d122852
Test refinements
cycle4passion Jan 15, 2026
7aae4a6
format
techniq Jan 19, 2026
8333843
Increase testTimeout (back to default 5s) to hopefully fix sporatic m…
techniq Jan 19, 2026
a5c3acd
Remove old playwright test setup (using vitest browser mode)
techniq Jan 19, 2026
84d8a2b
update pnpm-lock.yaml
techniq Jan 19, 2026
281e952
fix browser install for packges/layerchart
techniq Jan 19, 2026
d0bd570
Attempt to fix "TypeError: Failed to fetch dynamically imported modul…
techniq Jan 19, 2026
de19cd5
Move `optimizeDeps` from top-leve to project in attempt to fix CI
techniq Jan 19, 2026
668328f
Try splitting the client (browser) tests from the server/ssr tests
techniq Jan 19, 2026
b1b7366
fix command
techniq Jan 19, 2026
4c9389c
Try to render a basic test
techniq Jan 19, 2026
d482e14
another try
techniq Jan 19, 2026
2e56fb9
add more to optimizeDeps
techniq Jan 19, 2026
0fb5c4a
add runed
techniq Jan 19, 2026
9094743
Add missing `lang="ts"` to fix `Expected "}" but found "Highlighter"`
techniq Jan 19, 2026
dd6093d
Ignore `state_referenced_locally` warnings (for now)
techniq Jan 19, 2026
8a95c44
Restore previous Arc tests
techniq Jan 19, 2026
99ad1bf
Parallelize the CIi workflow into separate jobs
techniq Jan 19, 2026
35b9c38
Cleanup earlier vitest config attempts to fix "failed to fetch dynami…
techniq Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .claude/skills/svelte-testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Sveltest Helper

**Comprehensive testing tools and examples for building robust Svelte
5 applications**

This skill provides a complete testing framework for SvelteKit
projects using vitest-browser-svelte. It includes patterns, best
practices, and real-world examples for client-side, server-side, and
SSR testing.

## What You'll Find Here

- ✅ **Foundation First methodology** - Plan comprehensive test
coverage before coding
- ✅ **Real browser testing** - Using vitest-browser-svelte with
Playwright
- ✅ **Client-Server alignment** - Test with real FormData/Request
objects
- ✅ **Svelte 5 runes patterns** - Proper use of untrack(), $derived,
and $effect
- ✅ **Common pitfalls solved** - Strict mode, form submission,
accessibility
- ✅ **Production-ready examples** - Copy-paste test templates

## For Developers

Use this as a quick reference guide when writing tests:

- Browse `SKILL.md` for quick patterns and reminders
- Check `references/detailed-guide.md` for comprehensive examples
- Follow the "Unbreakable Rules" to avoid common mistakes

## For AI Assistants

This skill is automatically invoked by Claude Code when working with
tests in SvelteKit projects. It provides context-aware guidance for
creating and maintaining high-quality tests.

## Structure

- **SKILL.md** - Quick reference for common patterns
- **references/detailed-guide.md** - Complete testing guide with
examples

## Quick Start

```bash
# Run all tests
pnpm test

# Run specific test types
pnpm test:client # Browser component tests
pnpm test:server # API and server logic tests
pnpm test:ssr # Server-side rendering tests

# Generate coverage
pnpm coverage
```
58 changes: 58 additions & 0 deletions .claude/skills/svelte-testing/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
name: svelte-testing
# prettier-ignore
description: Fix and create Svelte 5 tests with vitest-browser-svelte and Playwright. Use when fixing broken tests, debugging failures, writing unit/SSR/e2e tests, or working with vitest/Playwright.
---

# Svelte Testing

## Quick Start

```typescript
// Client-side component test (.svelte.test.ts)
import { render } from 'vitest-browser-svelte';
import { expect } from 'vitest';
import Button from './button.svelte';

test('button click increments counter', async () => {
const { page } = render(Button);
const button = page.getByRole('button', { name: /click me/i });

await button.click();
await expect.element(button).toHaveTextContent('Clicked: 1');
});
```

## Core Principles

- **Always use locators**: `page.getBy*()` methods, never containers
- **Multiple elements**: Use `.first()`, `.nth()`, `.last()` to avoid
strict mode violations
- **Use untrack()**: When accessing `$derived` values in tests
- **Real API objects**: Test with FormData/Request, minimal mocking

## Reference Files

- [core-principles](references/core-principles.md) |
[foundation-first](references/foundation-first.md) |
[client-examples](references/client-examples.md)
- [server-ssr-examples](references/server-ssr-examples.md) |
[critical-patterns](references/critical-patterns.md)
- [client-server-alignment](references/client-server-alignment.md) |
[troubleshooting](references/troubleshooting.md)

## Notes

- Never click SvelteKit form submit buttons - Always use
`await expect.element()`
- Test files: `.svelte.test.ts` (client), `.ssr.test.ts` (SSR),
`server.test.ts` (API)

<!--
PROGRESSIVE DISCLOSURE GUIDELINES:
- Keep this file ~50 lines total (max ~150 lines)
- Use 1-2 code blocks only (recommend 1)
- Keep description <200 chars for Level 1 efficiency
- Move detailed docs to references/ for Level 3 loading
- This is Level 2 - quick reference ONLY, not a manual
-->
181 changes: 181 additions & 0 deletions .claude/skills/svelte-testing/references/client-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
## Complete Examples

### Example 1: Client-Side Component Test

Real browser testing with user interactions:

```typescript
// button.svelte.test.ts
import { render } from 'vitest-browser-svelte';
import { test, expect, describe } from 'vitest';
import { userEvent } from '@vitest/browser/context';
import Button from './button.svelte';

describe('Button Component', () => {
test('increments counter on click', async () => {
const { page } = render(Button, { props: { label: 'Click me' } });

const button = page.getByRole('button', { name: /click me/i });

await userEvent.click(button);
await expect.element(button).toHaveTextContent('Clicked: 1');

await userEvent.click(button);
await expect.element(button).toHaveTextContent('Clicked: 2');
});

test('supports keyboard interaction', async () => {
render(Button, { props: { label: 'Press me' } });

const button = page.getByRole('button', { name: /press me/i });
await button.focus();
await userEvent.keyboard('{Enter}');

await expect.element(button).toHaveTextContent('Clicked: 1');
});

test('handles multiple buttons with .first()', async () => {
render(ButtonGroup); // Renders multiple buttons

// Handle multiple buttons explicitly
const firstButton = page.getByRole('button').first();
const secondButton = page.getByRole('button').nth(1);

await firstButton.click();
await expect.element(firstButton).toHaveTextContent('Clicked: 1');

await secondButton.click();
await expect
.element(secondButton)
.toHaveTextContent('Clicked: 1');
});
});
```

### Example 2: Testing Svelte 5 Runes

```typescript
// counter.svelte.test.ts
import { render } from 'vitest-browser-svelte';
import { test, expect } from 'vitest';
import { untrack, flushSync } from 'svelte';
import Counter from './counter.svelte';

test('$state and $derived reactivity', async () => {
const { component } = render(Counter);

// Access $state value directly
expect(component.count).toBe(0);

// Update state
component.increment();

// Force synchronous update
flushSync(() => {});

// Access $derived value with untrack
const doubled = untrack(() => component.doubled);
expect(doubled).toBe(2);
});

test('form validation lifecycle', async () => {
const { component } = render(FormComponent);

// Initially valid (no validation run yet)
expect(untrack(() => component.isFormValid())).toBe(true);

// Trigger validation
component.validateAllFields();

// Now invalid (empty required fields)
expect(untrack(() => component.isFormValid())).toBe(false);

// Fix validation errors
component.email.value = 'test@example.com';
component.validateAllFields();

// Valid again
expect(untrack(() => component.isFormValid())).toBe(true);
});
```

### Example 3: Server-Side API Test

Test with real FormData/Request objects:

```typescript
// api/users/server.test.ts
import { test, expect, describe, vi } from 'vitest';
import { POST } from './+server';
import * as database from '$lib/server/database';

vi.mock('$lib/server/database');

describe('POST /api/users', () => {
test('creates user with valid data', async () => {
// Mock only external services
vi.mocked(database.createUser).mockResolvedValue({
id: '123',
email: 'user@example.com',
});

// Use real FormData
const formData = new FormData();
formData.append('email', 'user@example.com');
formData.append('password', 'securepass123');

// Use real Request object
const request = new Request('http://localhost/api/users', {
method: 'POST',
body: formData,
});

const response = await POST({ request });
const data = await response.json();

expect(response.status).toBe(201);
expect(data.email).toBe('user@example.com');
expect(database.createUser).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'securepass123',
});
});

test('rejects invalid email format', async () => {
const formData = new FormData();
formData.append('email', 'invalid-email');
formData.append('password', 'pass123');

const request = new Request('http://localhost/api/users', {
method: 'POST',
body: formData,
});

const response = await POST({ request });
const data = await response.json();

expect(response.status).toBe(400);
expect(data.errors.email).toBeDefined();
expect(database.createUser).not.toHaveBeenCalled();
});

test('handles missing required fields', async () => {
const formData = new FormData();
// Missing email and password

const request = new Request('http://localhost/api/users', {
method: 'POST',
body: formData,
});

const response = await POST({ request });
const data = await response.json();

expect(response.status).toBe(400);
expect(data.errors.email).toBeDefined();
expect(data.errors.password).toBeDefined();
});
});
```

---
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Client-Server Alignment

### The Problem

Heavy mocking in server tests can hide client-server mismatches that
only appear in production.

### The Solution

Use real `FormData` and `Request` objects. Only mock external services
(database, APIs).

```typescript
// ❌ BRITTLE APPROACH
const mockRequest = {
formData: vi.fn().mockResolvedValue({
get: vi.fn((key) => {
if (key === 'email') return 'test@example.com';
if (key === 'password') return 'pass123';
}),
}),
};
// This passes even if real FormData API differs!

// ✅ ROBUST APPROACH
const formData = new FormData();
formData.append('email', 'test@example.com');
formData.append('password', 'pass123');

const request = new Request('http://localhost/register', {
method: 'POST',
body: formData,
});

// Only mock external services
vi.mocked(database.createUser).mockResolvedValue({
id: '123',
email: 'test@example.com',
});

const response = await POST({ request });
```

### Shared Validation Logic

Use the same validation on client and server:

```typescript
// lib/validation.ts
export function validateEmail(email: string) {
if (!email) return 'Email is required';
if (!email.includes('@')) return 'Invalid email format';
return null;
}

// Component test
import { validateEmail } from '$lib/validation';
test('validates email', () => {
expect(validateEmail('')).toBe('Email is required');
expect(validateEmail('invalid')).toBe('Invalid email format');
expect(validateEmail('test@example.com')).toBe(null);
});

// Server test - same validation!
const emailError = validateEmail(formData.get('email'));
if (emailError) {
return json({ errors: { email: emailError } }, { status: 400 });
}
```

---
Loading
Loading