@gallopsystems/agent-skills 1.0.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/README.md +137 -0
- package/package.json +26 -0
- package/plugins/doctl/.claude-plugin/plugin.json +8 -0
- package/plugins/doctl/skills/doctl/SKILL.md +93 -0
- package/plugins/kysely-postgres/.claude-plugin/plugin.json +8 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +1101 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/aggregations.ts +167 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/ctes.ts +165 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/expressions.ts +272 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/joins.ts +206 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/json-arrays.ts +398 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/mutations.ts +199 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/orderby-pagination.ts +117 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/relations.ts +176 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/select-where.ts +146 -0
- package/plugins/linear/.claude-plugin/plugin.json +8 -0
- package/plugins/linear/skills/linear/SKILL.md +1040 -0
- package/plugins/linear/skills/linear/bin/linear.mjs +1228 -0
- package/plugins/linear/skills/linear/tech-stack.md +273 -0
- package/plugins/nitro-testing/.claude-plugin/plugin.json +8 -0
- package/plugins/nitro-testing/skills/nitro-testing/SKILL.md +497 -0
- package/plugins/nitro-testing/skills/nitro-testing/async-testing.md +270 -0
- package/plugins/nitro-testing/skills/nitro-testing/ci-setup.md +226 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/global-setup.ts +90 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/handler.test.ts +167 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/setup.ts +29 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/test-utils-index.ts +297 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/vitest.config.ts +42 -0
- package/plugins/nitro-testing/skills/nitro-testing/factories.md +278 -0
- package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +512 -0
- package/plugins/nitro-testing/skills/nitro-testing/test-utils.md +262 -0
- package/plugins/nitro-testing/skills/nitro-testing/transaction-rollback.md +183 -0
- package/plugins/nitro-testing/skills/nitro-testing/vitest-config.md +236 -0
- package/plugins/nuxt-nitro-api/.claude-plugin/plugin.json +8 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/SKILL.md +260 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/auth-patterns.md +228 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/composables-utils.md +174 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/deep-linking.md +190 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-middleware.ts +32 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-utils.ts +51 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/deep-link-page.vue +61 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/service-util.ts +63 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/sse-endpoint.ts +59 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/validation-endpoint.ts +38 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/fetch-patterns.md +178 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/nitro-tasks.md +243 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/page-structure.md +162 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/server-services.md +238 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/sse.md +221 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/ssr-client.md +166 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/validation.md +131 -0
- package/scripts/link-skills.mjs +252 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nitro-testing
|
|
3
|
+
description: Test Nuxt 3 / Nitro applications - both API handlers (real PostgreSQL, transaction rollback) and frontend components (@nuxt/test-utils, mountSuspended).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Nuxt / Nitro Testing Patterns
|
|
7
|
+
|
|
8
|
+
Test Nuxt 3 applications end-to-end: API handlers with real PostgreSQL using transaction rollback isolation, and frontend components with @nuxt/test-utils.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
Use this skill when:
|
|
13
|
+
- Testing Nuxt 3 / Nitro API handlers
|
|
14
|
+
- Testing Vue components, pages, or composables in Nuxt
|
|
15
|
+
- Using Kysely or another query builder with PostgreSQL
|
|
16
|
+
- Need real database testing (not mocks)
|
|
17
|
+
- Want fast, isolated tests without truncation
|
|
18
|
+
|
|
19
|
+
## Reference Files
|
|
20
|
+
|
|
21
|
+
**Backend (API Handlers):**
|
|
22
|
+
- [transaction-rollback.md](./transaction-rollback.md) - Core isolation pattern with Vitest fixtures
|
|
23
|
+
- [test-utils.md](./test-utils.md) - Mock events, stubs, and assertion helpers
|
|
24
|
+
- [factories.md](./factories.md) - Transaction-bound factory pattern
|
|
25
|
+
- [vitest-config.md](./vitest-config.md) - Vitest configuration for Nitro
|
|
26
|
+
- [ci-setup.md](./ci-setup.md) - GitHub Actions with PostgreSQL service
|
|
27
|
+
- [async-testing.md](./async-testing.md) - Testing background tasks and automations
|
|
28
|
+
|
|
29
|
+
**Frontend (Components/Pages):**
|
|
30
|
+
- [frontend-testing.md](./frontend-testing.md) - Component testing with @nuxt/test-utils
|
|
31
|
+
|
|
32
|
+
## Example Files
|
|
33
|
+
|
|
34
|
+
- [test-utils-index.ts](./examples/test-utils-index.ts) - Complete test utilities module
|
|
35
|
+
- [global-setup.ts](./examples/global-setup.ts) - Database reset and migration
|
|
36
|
+
- [setup.ts](./examples/setup.ts) - Per-file setup with stubs
|
|
37
|
+
- [handler.test.ts](./examples/handler.test.ts) - Example API handler test
|
|
38
|
+
- [vitest.config.ts](./examples/vitest.config.ts) - Vitest configuration
|
|
39
|
+
|
|
40
|
+
## Core Concept: Transaction Rollback
|
|
41
|
+
|
|
42
|
+
Instead of truncating tables between tests, each test runs inside a database transaction that rolls back at the end:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Each test gets isolated factories and db access
|
|
46
|
+
test("creates user", async ({ factories, db }) => {
|
|
47
|
+
const user = await factories.user({ email: "test@example.com" });
|
|
48
|
+
|
|
49
|
+
// Test your handler
|
|
50
|
+
const event = mockPost({}, { name: "New Item" });
|
|
51
|
+
const result = await handler(event);
|
|
52
|
+
|
|
53
|
+
// Verify in database
|
|
54
|
+
const saved = await db.selectFrom("item").selectAll().execute();
|
|
55
|
+
expect(saved).toHaveLength(1);
|
|
56
|
+
});
|
|
57
|
+
// Transaction auto-rolls back - no cleanup needed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Benefits:
|
|
61
|
+
- **Fast**: No DELETE/TRUNCATE between tests
|
|
62
|
+
- **Isolated**: Tests can't affect each other
|
|
63
|
+
- **Real SQL**: Catches actual database issues
|
|
64
|
+
- **Simple**: No manual cleanup
|
|
65
|
+
|
|
66
|
+
## Quick Setup
|
|
67
|
+
|
|
68
|
+
### 1. Install Dependencies
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
yarn add -D vitest @vitest/coverage-v8
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Create Test Utils Structure
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
server/
|
|
78
|
+
test-utils/
|
|
79
|
+
index.ts # Factories, fixtures, mock helpers
|
|
80
|
+
global-setup.ts # Runs once: reset DB, run migrations
|
|
81
|
+
setup.ts # Runs per-file: stub auto-imports
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Configure Vitest
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// vitest.config.ts
|
|
88
|
+
import { defineConfig } from "vitest/config";
|
|
89
|
+
import path from "path";
|
|
90
|
+
|
|
91
|
+
export default defineConfig({
|
|
92
|
+
test: {
|
|
93
|
+
globals: true,
|
|
94
|
+
environment: "node",
|
|
95
|
+
globalSetup: ["./server/test-utils/global-setup.ts"],
|
|
96
|
+
setupFiles: ["./server/test-utils/setup.ts"],
|
|
97
|
+
},
|
|
98
|
+
resolve: {
|
|
99
|
+
alias: {
|
|
100
|
+
"~": path.resolve(__dirname),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Write Tests
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// server/api/users/index.post.test.ts
|
|
110
|
+
import { describe, test, expect, mockPost, expectHttpError } from "~/server/test-utils";
|
|
111
|
+
import handler from "./index.post";
|
|
112
|
+
|
|
113
|
+
describe("POST /api/users", () => {
|
|
114
|
+
test("creates user with valid data", async ({ factories: _, db }) => {
|
|
115
|
+
const event = mockPost({}, {
|
|
116
|
+
email: "new@example.com",
|
|
117
|
+
name: "New User"
|
|
118
|
+
});
|
|
119
|
+
const result = await handler(event);
|
|
120
|
+
|
|
121
|
+
expect(result.id).toBeDefined();
|
|
122
|
+
expect(result.email).toBe("new@example.com");
|
|
123
|
+
|
|
124
|
+
// Verify persisted
|
|
125
|
+
const saved = await db
|
|
126
|
+
.selectFrom("user")
|
|
127
|
+
.where("id", "=", result.id)
|
|
128
|
+
.selectAll()
|
|
129
|
+
.executeTakeFirst();
|
|
130
|
+
expect(saved?.name).toBe("New User");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("throws 400 for missing email", async ({ factories: _ }) => {
|
|
134
|
+
const event = mockPost({}, { name: "No Email" });
|
|
135
|
+
await expectHttpError(handler(event), { statusCode: 400 });
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Key Patterns
|
|
141
|
+
|
|
142
|
+
### Mock Event Helpers
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// GET with route params and query
|
|
146
|
+
const event = mockGet({ id: 123 }, { include: "details" });
|
|
147
|
+
|
|
148
|
+
// POST with body
|
|
149
|
+
const event = mockPost({}, { name: "Test", status: "active" });
|
|
150
|
+
|
|
151
|
+
// PATCH with route params and body
|
|
152
|
+
const event = mockPatch({ id: 123 }, { status: "completed" });
|
|
153
|
+
|
|
154
|
+
// DELETE with route params
|
|
155
|
+
const event = mockDelete({ id: 123 });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Factory Pattern
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
test("lists user's projects", async ({ factories }) => {
|
|
162
|
+
// Factories are transaction-bound - auto-rolled back
|
|
163
|
+
const user = await factories.user();
|
|
164
|
+
const project1 = await factories.project({ ownerId: user.id });
|
|
165
|
+
const project2 = await factories.project({ ownerId: user.id });
|
|
166
|
+
|
|
167
|
+
const event = mockGet({ userId: user.id });
|
|
168
|
+
const result = await handler(event);
|
|
169
|
+
|
|
170
|
+
expect(result).toHaveLength(2);
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Testing with Related Data
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
test("returns task with job details", async ({ factories }) => {
|
|
178
|
+
// Factories auto-create dependencies
|
|
179
|
+
const job = await factories.job(); // Creates project automatically
|
|
180
|
+
const task = await factories.task({ jobId: job.id });
|
|
181
|
+
|
|
182
|
+
const event = mockGet({ id: task.id });
|
|
183
|
+
const result = await handler(event);
|
|
184
|
+
|
|
185
|
+
expect(result.job.id).toBe(job.id);
|
|
186
|
+
expect(result.job.project).toBeDefined();
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Testing Error Cases
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
test("returns 404 for non-existent resource", async ({ factories: _ }) => {
|
|
194
|
+
const event = mockGet({ id: 999999 });
|
|
195
|
+
await expectHttpError(handler(event), {
|
|
196
|
+
statusCode: 404,
|
|
197
|
+
message: "Not found",
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("returns 400 for invalid input", async ({ factories: _ }) => {
|
|
202
|
+
const event = mockPost({}, { invalidField: true });
|
|
203
|
+
await expectHttpError(handler(event), { statusCode: 400 });
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Auto-Import Stubs
|
|
208
|
+
|
|
209
|
+
The setup file stubs Nuxt/Nitro auto-imports:
|
|
210
|
+
|
|
211
|
+
| Stub | Purpose |
|
|
212
|
+
|------|---------|
|
|
213
|
+
| `defineEventHandler` | Unwraps to return handler directly |
|
|
214
|
+
| `getUserSession` | Returns test user (configurable) |
|
|
215
|
+
| `useDatabase` | Returns test transaction |
|
|
216
|
+
| `createError` | Creates H3-style errors |
|
|
217
|
+
| `getValidatedQuery` | Validates mock query params |
|
|
218
|
+
| `readValidatedBody` | Validates mock body |
|
|
219
|
+
| `getRouterParam` | Returns mock route params |
|
|
220
|
+
|
|
221
|
+
## Key Gotchas
|
|
222
|
+
|
|
223
|
+
1. **Always destructure `factories`** - Even if unused, it sets up the transaction:
|
|
224
|
+
```typescript
|
|
225
|
+
test("...", async ({ factories: _ }) => { ... });
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
2. **Don't use top-level db imports** - Use the `db` fixture instead:
|
|
229
|
+
```typescript
|
|
230
|
+
// ❌ Wrong - uses real db, not transaction
|
|
231
|
+
import { db } from "../utils/db";
|
|
232
|
+
|
|
233
|
+
// ✅ Right - uses test transaction
|
|
234
|
+
test("...", async ({ db }) => { ... });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
3. **Nested transactions work** - Code that calls `db.transaction()` works because we patch the prototype
|
|
238
|
+
|
|
239
|
+
4. **Test file location** - Co-locate with handlers: `handler.ts` → `handler.test.ts`
|
|
240
|
+
|
|
241
|
+
5. **Separate test database** - Always use a dedicated test DB (`myapp-test`, not `myapp`)
|
|
242
|
+
|
|
243
|
+
6. **CI needs PostgreSQL service** - See [ci-setup.md](./ci-setup.md) for GitHub Actions config
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Frontend Testing
|
|
248
|
+
|
|
249
|
+
Test Vue components and pages in Nuxt using `@nuxt/test-utils` with `mountSuspended`.
|
|
250
|
+
|
|
251
|
+
### Setup
|
|
252
|
+
|
|
253
|
+
**Dependencies:**
|
|
254
|
+
```bash
|
|
255
|
+
yarn add -D @nuxt/test-utils @vue/test-utils happy-dom
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Vitest Config** - Use environment variable to separate frontend/backend:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// vitest.config.ts
|
|
262
|
+
import { defineConfig } from "vitest/config";
|
|
263
|
+
import { defineVitestConfig } from "@nuxt/test-utils/config";
|
|
264
|
+
|
|
265
|
+
const isNuxtEnv = process.env.VITEST_ENV === "nuxt";
|
|
266
|
+
|
|
267
|
+
export default isNuxtEnv
|
|
268
|
+
? defineVitestConfig({
|
|
269
|
+
test: {
|
|
270
|
+
environment: "nuxt",
|
|
271
|
+
globals: true,
|
|
272
|
+
include: [
|
|
273
|
+
"components/**/*.test.ts",
|
|
274
|
+
"pages/**/*.test.ts",
|
|
275
|
+
"utils/**/*.test.ts",
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
: defineConfig({
|
|
280
|
+
// ... backend config
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Package.json scripts:**
|
|
285
|
+
```json
|
|
286
|
+
{
|
|
287
|
+
"scripts": {
|
|
288
|
+
"test": "vitest",
|
|
289
|
+
"test:frontend": "VITEST_ENV=nuxt vitest",
|
|
290
|
+
"test:frontend:run": "VITEST_ENV=nuxt vitest run"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Nuxt test config** (`nuxt.config.test.ts`):
|
|
296
|
+
```typescript
|
|
297
|
+
export default defineNuxtConfig({
|
|
298
|
+
modules: ["@primevue/nuxt-module"], // Include UI libraries
|
|
299
|
+
ssr: false, // Disable SSR for simpler component testing
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### File Organization
|
|
304
|
+
|
|
305
|
+
Co-locate tests with source files:
|
|
306
|
+
```
|
|
307
|
+
components/
|
|
308
|
+
ProjectCard.vue
|
|
309
|
+
ProjectCard.test.ts
|
|
310
|
+
pages/
|
|
311
|
+
projects/
|
|
312
|
+
index.vue
|
|
313
|
+
index.test.ts
|
|
314
|
+
utils/
|
|
315
|
+
dates.ts
|
|
316
|
+
dates.test.ts
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Component Testing Pattern
|
|
320
|
+
|
|
321
|
+
Use `mountSuspended` for async-safe component mounting:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { describe, it, expect } from "vitest";
|
|
325
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
326
|
+
import ProjectCard from "./ProjectCard.vue";
|
|
327
|
+
|
|
328
|
+
describe("ProjectCard", () => {
|
|
329
|
+
it("renders project name", async () => {
|
|
330
|
+
const wrapper = await mountSuspended(ProjectCard, {
|
|
331
|
+
props: {
|
|
332
|
+
project: { id: 1, name: "Test Project", status: "active" },
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(wrapper.text()).toContain("Test Project");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("shows active badge when active", async () => {
|
|
340
|
+
const wrapper = await mountSuspended(ProjectCard, {
|
|
341
|
+
props: {
|
|
342
|
+
project: { id: 1, name: "Test", status: "active" },
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(wrapper.html()).toMatch(/active/i);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Mocking Composables
|
|
352
|
+
|
|
353
|
+
Use `mockNuxtImport` for Nuxt composables:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { mockNuxtImport } from "@nuxt/test-utils/runtime";
|
|
357
|
+
|
|
358
|
+
mockNuxtImport("useAddress", () => {
|
|
359
|
+
return () => ({
|
|
360
|
+
getDisplayAddress: (project: any) =>
|
|
361
|
+
project.address || "No address",
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
mockNuxtImport("useUserSession", () => {
|
|
366
|
+
return () => ({
|
|
367
|
+
user: { id: 1, name: "Test User" },
|
|
368
|
+
loggedIn: true,
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Mocking API Endpoints
|
|
374
|
+
|
|
375
|
+
Use `registerEndpoint` to mock API calls:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { registerEndpoint } from "@nuxt/test-utils/runtime";
|
|
379
|
+
|
|
380
|
+
registerEndpoint("/api/projects", {
|
|
381
|
+
method: "GET",
|
|
382
|
+
handler: () => [
|
|
383
|
+
{ id: 1, name: "Project A", status: "active" },
|
|
384
|
+
{ id: 2, name: "Project B", status: "completed" },
|
|
385
|
+
],
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
registerEndpoint("/api/projects/:id", {
|
|
389
|
+
method: "GET",
|
|
390
|
+
handler: (event) => ({
|
|
391
|
+
id: parseInt(event.context.params.id),
|
|
392
|
+
name: "Project Detail",
|
|
393
|
+
}),
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Testing Pages with Routes
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { describe, it, expect } from "vitest";
|
|
401
|
+
import { mountSuspended, registerEndpoint } from "@nuxt/test-utils/runtime";
|
|
402
|
+
import TaskPage from "./[id].vue";
|
|
403
|
+
|
|
404
|
+
describe("Task Detail Page", () => {
|
|
405
|
+
it("renders task details", async () => {
|
|
406
|
+
registerEndpoint("/api/tasks/123", {
|
|
407
|
+
method: "GET",
|
|
408
|
+
handler: () => ({ id: 123, name: "Fix bug", status: "open" }),
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const wrapper = await mountSuspended(TaskPage, {
|
|
412
|
+
route: {
|
|
413
|
+
params: { id: "123" },
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
expect(wrapper.text()).toContain("Fix bug");
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Stubbing UI Library Components
|
|
423
|
+
|
|
424
|
+
For PrimeVue or other UI libraries, stub complex components:
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
428
|
+
import ToastService from "primevue/toastservice";
|
|
429
|
+
import ConfirmationService from "primevue/confirmationservice";
|
|
430
|
+
|
|
431
|
+
const wrapper = await mountSuspended(MyComponent, {
|
|
432
|
+
global: {
|
|
433
|
+
plugins: [ToastService, ConfirmationService],
|
|
434
|
+
stubs: {
|
|
435
|
+
DataTable: true,
|
|
436
|
+
Column: true,
|
|
437
|
+
Dialog: true,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Testing Utility Functions
|
|
444
|
+
|
|
445
|
+
For pure utilities, standard Vitest patterns work:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
449
|
+
import { formatDate, parseDate } from "./dates";
|
|
450
|
+
|
|
451
|
+
describe("date utilities", () => {
|
|
452
|
+
const originalTZ = process.env.TZ;
|
|
453
|
+
|
|
454
|
+
afterEach(() => {
|
|
455
|
+
process.env.TZ = originalTZ;
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("formats date correctly in EST", () => {
|
|
459
|
+
process.env.TZ = "America/New_York";
|
|
460
|
+
expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("formats date correctly in PST", () => {
|
|
464
|
+
process.env.TZ = "America/Los_Angeles";
|
|
465
|
+
expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Async Handling
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { nextTick } from "vue";
|
|
474
|
+
|
|
475
|
+
it("updates after user interaction", async () => {
|
|
476
|
+
const wrapper = await mountSuspended(Counter);
|
|
477
|
+
|
|
478
|
+
await wrapper.find("button").trigger("click");
|
|
479
|
+
await nextTick();
|
|
480
|
+
|
|
481
|
+
expect(wrapper.text()).toContain("Count: 1");
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Frontend Testing Gotchas
|
|
486
|
+
|
|
487
|
+
1. **Use `mountSuspended`** - Not regular `mount`. Handles async setup and Nuxt context.
|
|
488
|
+
|
|
489
|
+
2. **Mock composables before mounting** - `mockNuxtImport` must be called before `mountSuspended`.
|
|
490
|
+
|
|
491
|
+
3. **Register endpoints before mounting** - API mocks must exist before the component fetches.
|
|
492
|
+
|
|
493
|
+
4. **Use `wrapper.text()` for content** - More reliable than searching for specific elements.
|
|
494
|
+
|
|
495
|
+
5. **Await everything** - `mountSuspended`, `trigger()`, `nextTick()` are all async.
|
|
496
|
+
|
|
497
|
+
6. **Separate test configs** - Use `VITEST_ENV` to run frontend and backend tests with different environments.
|