codeforge-dev 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devcontainer/.env +22 -0
- package/.devcontainer/CHANGELOG.md +197 -0
- package/.devcontainer/CLAUDE.md +117 -0
- package/.devcontainer/README.md +222 -0
- package/.devcontainer/config/main-system-prompt.md +502 -0
- package/.devcontainer/config/settings.json +47 -0
- package/.devcontainer/devcontainer.json +94 -0
- package/.devcontainer/features/README.md +113 -0
- package/.devcontainer/features/agent-browser/README.md +65 -0
- package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
- package/.devcontainer/features/agent-browser/install.sh +79 -0
- package/.devcontainer/features/ast-grep/README.md +24 -0
- package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
- package/.devcontainer/features/ast-grep/install.sh +51 -0
- package/.devcontainer/features/ccstatusline/README.md +296 -0
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
- package/.devcontainer/features/ccstatusline/install.sh +290 -0
- package/.devcontainer/features/ccusage/README.md +205 -0
- package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
- package/.devcontainer/features/ccusage/install.sh +132 -0
- package/.devcontainer/features/claude-code/README.md +498 -0
- package/.devcontainer/features/claude-code/config/settings.json +36 -0
- package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
- package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
- package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
- package/.devcontainer/features/claude-code/install.sh +466 -0
- package/.devcontainer/features/claude-monitor/README.md +74 -0
- package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
- package/.devcontainer/features/claude-monitor/install.sh +99 -0
- package/.devcontainer/features/lsp-servers/README.md +85 -0
- package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
- package/.devcontainer/features/lsp-servers/install.sh +116 -0
- package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
- package/.devcontainer/features/mcp-qdrant/README.md +474 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
- package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
- package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
- package/.devcontainer/features/mcp-reasoner/README.md +177 -0
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
- package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
- package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
- package/.devcontainer/features/notify-hook/README.md +86 -0
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
- package/.devcontainer/features/notify-hook/install.sh +38 -0
- package/.devcontainer/features/splitrail/README.md +140 -0
- package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
- package/.devcontainer/features/splitrail/install.sh +129 -0
- package/.devcontainer/features/tree-sitter/README.md +138 -0
- package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
- package/.devcontainer/features/tree-sitter/install.sh +173 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
- package/.devcontainer/scripts/setup-aliases.sh +80 -0
- package/.devcontainer/scripts/setup-config.sh +28 -0
- package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
- package/.devcontainer/scripts/setup-plugins.sh +80 -0
- package/.devcontainer/scripts/setup.sh +58 -0
- package/LICENSE.txt +674 -0
- package/README.md +267 -0
- package/package.json +44 -0
- package/setup.js +83 -0
package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing
|
|
3
|
+
description: >-
|
|
4
|
+
This skill should be used when the user asks to "write tests for a FastAPI endpoint",
|
|
5
|
+
"test a Svelte component", "set up pytest fixtures for FastAPI",
|
|
6
|
+
"configure Vitest for SvelteKit", "mock dependencies in FastAPI tests",
|
|
7
|
+
"test SSE streaming endpoints", "write component tests with Testing Library",
|
|
8
|
+
"set up database fixtures for API tests", or discusses pytest, httpx AsyncClient,
|
|
9
|
+
Vitest, @testing-library/svelte, dependency overrides, or test fixtures.
|
|
10
|
+
version: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Testing (FastAPI + Svelte)
|
|
14
|
+
|
|
15
|
+
## Mental Model
|
|
16
|
+
|
|
17
|
+
Testing verifies **behavior at public interfaces**, not implementation details. For a FastAPI backend, the public interface is the HTTP contract -- request in, response out. For a Svelte frontend, the public interface is what the user sees and interacts with -- rendered text, form inputs, button clicks.
|
|
18
|
+
|
|
19
|
+
Both stacks share core principles: isolate the system under test by replacing external dependencies (databases, APIs, auth) with controlled substitutes; assert on observable outcomes rather than internal state; structure tests as **arrange-act-assert** with clear boundaries between each phase.
|
|
20
|
+
|
|
21
|
+
The key difference is the isolation mechanism. FastAPI uses `app.dependency_overrides` to swap injected dependencies at the application level -- the real handler code runs with fake resources. Svelte tests use `vi.mock()` or MSW (Mock Service Worker) to intercept network calls at the fetch layer -- the real component code runs with fake responses.
|
|
22
|
+
|
|
23
|
+
Assume pytest with pytest-anyio for FastAPI, and Vitest with @testing-library/svelte for Svelte 5.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## FastAPI: AsyncClient Setup
|
|
28
|
+
|
|
29
|
+
The modern async testing pattern uses `httpx.AsyncClient` with `ASGITransport`:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import pytest
|
|
33
|
+
from httpx import ASGITransport, AsyncClient
|
|
34
|
+
from app.main import app
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
async def client():
|
|
38
|
+
transport = ASGITransport(app=app)
|
|
39
|
+
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
40
|
+
yield ac
|
|
41
|
+
|
|
42
|
+
@pytest.mark.anyio
|
|
43
|
+
async def test_read_items(client: AsyncClient):
|
|
44
|
+
response = await client.get("/items/")
|
|
45
|
+
assert response.status_code == 200
|
|
46
|
+
assert isinstance(response.json(), list)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`AsyncClient` does **not** trigger lifespan events. For applications that depend on startup/shutdown:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from asgi_lifespan import LifespanManager
|
|
53
|
+
|
|
54
|
+
@pytest.fixture
|
|
55
|
+
async def client():
|
|
56
|
+
async with LifespanManager(app) as manager:
|
|
57
|
+
transport = ASGITransport(app=manager.app)
|
|
58
|
+
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
59
|
+
yield ac
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The synchronous `TestClient` (from Starlette) is simpler but cannot be used inside `async def` tests:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from fastapi.testclient import TestClient
|
|
66
|
+
|
|
67
|
+
def test_read_items():
|
|
68
|
+
with TestClient(app) as client:
|
|
69
|
+
response = client.get("/items/")
|
|
70
|
+
assert response.status_code == 200
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> **Deep dive:** See `references/fastapi-testing.md` for dependency override patterns, SSE stream testing with httpx-sse, WebSocket testing, background task testing, and database fixture patterns with aiosqlite.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## FastAPI: Dependency Overrides
|
|
78
|
+
|
|
79
|
+
Replace real dependencies with test doubles using `app.dependency_overrides`:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from app.dependencies import get_db, get_current_user
|
|
83
|
+
|
|
84
|
+
async def mock_db():
|
|
85
|
+
yield FakeDatabase()
|
|
86
|
+
|
|
87
|
+
async def mock_user():
|
|
88
|
+
return User(id=1, email="test@example.com", role="admin")
|
|
89
|
+
|
|
90
|
+
@pytest.fixture
|
|
91
|
+
async def client():
|
|
92
|
+
app.dependency_overrides[get_db] = mock_db
|
|
93
|
+
app.dependency_overrides[get_current_user] = mock_user
|
|
94
|
+
transport = ASGITransport(app=app)
|
|
95
|
+
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
96
|
+
yield ac
|
|
97
|
+
app.dependency_overrides.clear()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The override dict maps original dependency callables to replacement callables. Generator dependencies (using `yield`) work as overrides with proper setup/teardown semantics.
|
|
101
|
+
|
|
102
|
+
### Factory Fixture for Dynamic Overrides
|
|
103
|
+
|
|
104
|
+
When different tests need different users or roles, use a factory fixture:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
@pytest.fixture
|
|
108
|
+
def override_user():
|
|
109
|
+
"""Return a function that sets the current user override."""
|
|
110
|
+
def _override(user: User):
|
|
111
|
+
app.dependency_overrides[get_current_user] = lambda: user
|
|
112
|
+
return _override
|
|
113
|
+
|
|
114
|
+
@pytest.mark.anyio
|
|
115
|
+
async def test_admin_access(client, override_user):
|
|
116
|
+
override_user(User(id=1, email="admin@test.com", role="admin"))
|
|
117
|
+
response = await client.get("/admin/settings")
|
|
118
|
+
assert response.status_code == 200
|
|
119
|
+
|
|
120
|
+
@pytest.mark.anyio
|
|
121
|
+
async def test_user_forbidden(client, override_user):
|
|
122
|
+
override_user(User(id=2, email="user@test.com", role="user"))
|
|
123
|
+
response = await client.get("/admin/settings")
|
|
124
|
+
assert response.status_code == 403
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## FastAPI: SSE Stream Testing
|
|
130
|
+
|
|
131
|
+
Test SSE endpoints using `httpx-sse` to parse server-sent events:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from httpx_sse import aconnect_sse
|
|
135
|
+
|
|
136
|
+
@pytest.mark.anyio
|
|
137
|
+
async def test_sse_stream():
|
|
138
|
+
async with AsyncClient(
|
|
139
|
+
transport=ASGITransport(app=app),
|
|
140
|
+
base_url="http://test"
|
|
141
|
+
) as ac:
|
|
142
|
+
async with aconnect_sse(ac, "GET", "/events") as event_source:
|
|
143
|
+
events = []
|
|
144
|
+
async for sse in event_source.aiter_sse():
|
|
145
|
+
events.append(sse)
|
|
146
|
+
if len(events) >= 3:
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
assert len(events) == 3
|
|
150
|
+
assert events[0].event == "update"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For raw stream testing without `httpx-sse`, use `ac.stream()` and parse `data:` lines manually. The `httpx-sse` approach is preferred because it handles multi-line data fields, event types, and reconnection IDs correctly.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Svelte: Vitest Configuration
|
|
158
|
+
|
|
159
|
+
### Installation
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm install -D vitest jsdom @testing-library/svelte @testing-library/jest-dom @testing-library/user-event
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Configuration
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
// vite.config.js
|
|
169
|
+
import { defineConfig } from 'vitest/config'
|
|
170
|
+
import { sveltekit } from '@sveltejs/kit/vite'
|
|
171
|
+
import { svelteTesting } from '@testing-library/svelte/vite'
|
|
172
|
+
|
|
173
|
+
export default defineConfig({
|
|
174
|
+
plugins: [sveltekit(), svelteTesting()],
|
|
175
|
+
test: {
|
|
176
|
+
environment: 'jsdom',
|
|
177
|
+
setupFiles: ['./vitest-setup.js'],
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// vitest-setup.js
|
|
184
|
+
import '@testing-library/jest-dom/vitest'
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The `svelteTesting()` plugin handles automatic cleanup after each test. To use Svelte 5 runes (`$state`, `$derived`) directly in test files, name the file with `.svelte.test.ts` extension.
|
|
188
|
+
|
|
189
|
+
> **Deep dive:** See `references/svelte-testing.md` for component rendering patterns, user event simulation, async state updates, mocking fetch and SSE, mocking SvelteKit modules, and snapshot testing.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Svelte: Component Testing
|
|
194
|
+
|
|
195
|
+
Render components with `render()` and query the DOM with accessible queries:
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
import { render, screen } from '@testing-library/svelte'
|
|
199
|
+
import userEvent from '@testing-library/user-event'
|
|
200
|
+
import { expect, test } from 'vitest'
|
|
201
|
+
import Counter from './Counter.svelte'
|
|
202
|
+
|
|
203
|
+
test('increments count on click', async () => {
|
|
204
|
+
const user = userEvent.setup()
|
|
205
|
+
render(Counter, { initial: 0 })
|
|
206
|
+
|
|
207
|
+
const button = screen.getByRole('button')
|
|
208
|
+
expect(button).toHaveTextContent('0')
|
|
209
|
+
|
|
210
|
+
await user.click(button)
|
|
211
|
+
expect(button).toHaveTextContent('1')
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Query Priority
|
|
216
|
+
|
|
217
|
+
Prefer queries that reflect how users find elements:
|
|
218
|
+
|
|
219
|
+
1. `getByRole` -- accessible role (`button`, `heading`, `textbox`)
|
|
220
|
+
2. `getByLabelText` -- form elements by label
|
|
221
|
+
3. `getByText` -- visible text content
|
|
222
|
+
4. `getByPlaceholderText` -- input placeholders
|
|
223
|
+
5. `getByTestId` -- last resort, `data-testid` attribute
|
|
224
|
+
|
|
225
|
+
### Query Types
|
|
226
|
+
|
|
227
|
+
| Prefix | 0 matches | 1 match | >1 matches | Async |
|
|
228
|
+
|--------|-----------|---------|------------|-------|
|
|
229
|
+
| `getBy` | throws | returns | throws | No |
|
|
230
|
+
| `queryBy` | `null` | returns | throws | No |
|
|
231
|
+
| `findBy` | throws | returns | throws | Yes |
|
|
232
|
+
|
|
233
|
+
Use `getBy` for elements that must exist, `queryBy` to assert absence, `findBy` to wait for async rendering.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Svelte: Async State and Waiting
|
|
238
|
+
|
|
239
|
+
Svelte 5 components update asynchronously. Testing Library provides several mechanisms to handle this:
|
|
240
|
+
|
|
241
|
+
### findBy Queries
|
|
242
|
+
|
|
243
|
+
`findBy` queries poll until the element appears (default timeout: 1000ms). Use them for components that load data asynchronously:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
test('loads and displays items', async () => {
|
|
247
|
+
render(ItemList)
|
|
248
|
+
|
|
249
|
+
// Waits for the element to appear in the DOM
|
|
250
|
+
const item = await screen.findByText('Loaded Item', {}, { timeout: 3000 })
|
|
251
|
+
expect(item).toBeInTheDocument()
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### waitFor
|
|
256
|
+
|
|
257
|
+
`waitFor` repeatedly runs an assertion until it passes or times out. Use it when no single element query captures the expected state:
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
import { waitFor } from '@testing-library/svelte'
|
|
261
|
+
|
|
262
|
+
test('counter reaches target', async () => {
|
|
263
|
+
render(AnimatedCounter, { target: 10 })
|
|
264
|
+
|
|
265
|
+
await waitFor(() => {
|
|
266
|
+
expect(screen.getByTestId('count')).toHaveTextContent('10')
|
|
267
|
+
}, { timeout: 2000 })
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Mocking Fetch with MSW
|
|
272
|
+
|
|
273
|
+
MSW (Mock Service Worker) intercepts requests at the network level. Configure it in `vitest-setup.js` for global availability:
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
import { http, HttpResponse } from 'msw'
|
|
277
|
+
import { setupServer } from 'msw/node'
|
|
278
|
+
|
|
279
|
+
const server = setupServer(
|
|
280
|
+
http.get('/api/items', () => HttpResponse.json({ items: ['Apple'] })),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
beforeAll(() => server.listen())
|
|
284
|
+
afterEach(() => server.resetHandlers())
|
|
285
|
+
afterAll(() => server.close())
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Override handlers per test for error scenarios:
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
test('handles server error', async () => {
|
|
292
|
+
server.use(http.get('/api/items', () => new HttpResponse(null, { status: 500 })))
|
|
293
|
+
render(ItemList)
|
|
294
|
+
expect(await screen.findByText('Error loading items')).toBeInTheDocument()
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Shared Patterns
|
|
301
|
+
|
|
302
|
+
### Test Organization
|
|
303
|
+
|
|
304
|
+
Mirror the source directory structure:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
src/
|
|
308
|
+
routes/
|
|
309
|
+
items/
|
|
310
|
+
+page.svelte
|
|
311
|
+
lib/
|
|
312
|
+
api.ts
|
|
313
|
+
tests/
|
|
314
|
+
routes/
|
|
315
|
+
items/
|
|
316
|
+
page.test.ts
|
|
317
|
+
lib/
|
|
318
|
+
api.test.ts
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
For FastAPI:
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
app/
|
|
325
|
+
routers/
|
|
326
|
+
items.py
|
|
327
|
+
services/
|
|
328
|
+
item_service.py
|
|
329
|
+
tests/
|
|
330
|
+
routers/
|
|
331
|
+
test_items.py
|
|
332
|
+
services/
|
|
333
|
+
test_item_service.py
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Arrange-Act-Assert
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
@pytest.mark.anyio
|
|
340
|
+
async def test_create_item(client: AsyncClient):
|
|
341
|
+
# Arrange
|
|
342
|
+
payload = {"name": "Widget", "price": 9.99}
|
|
343
|
+
|
|
344
|
+
# Act
|
|
345
|
+
response = await client.post("/items/", json=payload)
|
|
346
|
+
|
|
347
|
+
# Assert
|
|
348
|
+
assert response.status_code == 201
|
|
349
|
+
data = response.json()
|
|
350
|
+
assert data["name"] == "Widget"
|
|
351
|
+
assert data["price"] == 9.99
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
```javascript
|
|
355
|
+
test('displays item name', async () => {
|
|
356
|
+
// Arrange
|
|
357
|
+
const item = { name: 'Widget', price: 9.99 }
|
|
358
|
+
|
|
359
|
+
// Act
|
|
360
|
+
render(ItemCard, { item })
|
|
361
|
+
|
|
362
|
+
// Assert
|
|
363
|
+
expect(screen.getByText('Widget')).toBeInTheDocument()
|
|
364
|
+
expect(screen.getByText('$9.99')).toBeInTheDocument()
|
|
365
|
+
})
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Fixture Composition
|
|
369
|
+
|
|
370
|
+
Compose small, focused fixtures rather than building monolithic setup functions:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
@pytest.fixture
|
|
374
|
+
async def db_session(engine):
|
|
375
|
+
async with async_sessionmaker(engine)() as session:
|
|
376
|
+
async with session.begin():
|
|
377
|
+
yield session
|
|
378
|
+
await session.rollback()
|
|
379
|
+
|
|
380
|
+
@pytest.fixture
|
|
381
|
+
async def seeded_db(db_session):
|
|
382
|
+
db_session.add(Item(name="Existing", price=5.00))
|
|
383
|
+
await db_session.flush()
|
|
384
|
+
return db_session
|
|
385
|
+
|
|
386
|
+
@pytest.fixture
|
|
387
|
+
async def client(db_session):
|
|
388
|
+
app.dependency_overrides[get_db] = lambda: db_session
|
|
389
|
+
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
|
|
390
|
+
yield ac
|
|
391
|
+
app.dependency_overrides.clear()
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Ambiguity Policy
|
|
397
|
+
|
|
398
|
+
These defaults apply when the user does not specify a preference. State the assumption when making a choice so the user can override:
|
|
399
|
+
|
|
400
|
+
- **Test runner:** Default to pytest with pytest-anyio for FastAPI. Default to Vitest for Svelte.
|
|
401
|
+
- **Async test style:** Default to `@pytest.mark.anyio` with `AsyncClient`. Use sync `TestClient` only when async is not needed.
|
|
402
|
+
- **HTTP mocking (Svelte):** Default to MSW for fetch mocking. Use `vi.fn()` on `globalThis.fetch` for simple cases without many endpoints.
|
|
403
|
+
- **Component queries:** Default to `screen.getByRole()`. Use `getByTestId` only when no accessible role or text is available.
|
|
404
|
+
- **Database isolation:** Default to per-test transaction rollback. Use per-test database creation only when testing migrations or schema changes.
|
|
405
|
+
- **User events:** Default to `userEvent.setup()` over `fireEvent`. `userEvent` simulates realistic browser interaction sequences.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Reference Files
|
|
410
|
+
|
|
411
|
+
| File | Contents |
|
|
412
|
+
|------|----------|
|
|
413
|
+
| `references/fastapi-testing.md` | AsyncClient setup details, dependency override patterns, SSE stream testing with httpx-sse, WebSocket testing, background task testing, database fixtures with aiosqlite, pytest configuration |
|
|
414
|
+
| `references/svelte-testing.md` | Vitest configuration for SvelteKit, @testing-library/svelte render API, user event simulation, async state updates (act, flushSync, waitFor, findBy), mocking fetch and SSE with MSW, mocking SvelteKit modules ($app/*), snapshot testing |
|