@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.
Files changed (52) hide show
  1. package/README.md +137 -0
  2. package/package.json +26 -0
  3. package/plugins/doctl/.claude-plugin/plugin.json +8 -0
  4. package/plugins/doctl/skills/doctl/SKILL.md +93 -0
  5. package/plugins/kysely-postgres/.claude-plugin/plugin.json +8 -0
  6. package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +1101 -0
  7. package/plugins/kysely-postgres/skills/kysely-postgres/references/aggregations.ts +167 -0
  8. package/plugins/kysely-postgres/skills/kysely-postgres/references/ctes.ts +165 -0
  9. package/plugins/kysely-postgres/skills/kysely-postgres/references/expressions.ts +272 -0
  10. package/plugins/kysely-postgres/skills/kysely-postgres/references/joins.ts +206 -0
  11. package/plugins/kysely-postgres/skills/kysely-postgres/references/json-arrays.ts +398 -0
  12. package/plugins/kysely-postgres/skills/kysely-postgres/references/mutations.ts +199 -0
  13. package/plugins/kysely-postgres/skills/kysely-postgres/references/orderby-pagination.ts +117 -0
  14. package/plugins/kysely-postgres/skills/kysely-postgres/references/relations.ts +176 -0
  15. package/plugins/kysely-postgres/skills/kysely-postgres/references/select-where.ts +146 -0
  16. package/plugins/linear/.claude-plugin/plugin.json +8 -0
  17. package/plugins/linear/skills/linear/SKILL.md +1040 -0
  18. package/plugins/linear/skills/linear/bin/linear.mjs +1228 -0
  19. package/plugins/linear/skills/linear/tech-stack.md +273 -0
  20. package/plugins/nitro-testing/.claude-plugin/plugin.json +8 -0
  21. package/plugins/nitro-testing/skills/nitro-testing/SKILL.md +497 -0
  22. package/plugins/nitro-testing/skills/nitro-testing/async-testing.md +270 -0
  23. package/plugins/nitro-testing/skills/nitro-testing/ci-setup.md +226 -0
  24. package/plugins/nitro-testing/skills/nitro-testing/examples/global-setup.ts +90 -0
  25. package/plugins/nitro-testing/skills/nitro-testing/examples/handler.test.ts +167 -0
  26. package/plugins/nitro-testing/skills/nitro-testing/examples/setup.ts +29 -0
  27. package/plugins/nitro-testing/skills/nitro-testing/examples/test-utils-index.ts +297 -0
  28. package/plugins/nitro-testing/skills/nitro-testing/examples/vitest.config.ts +42 -0
  29. package/plugins/nitro-testing/skills/nitro-testing/factories.md +278 -0
  30. package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +512 -0
  31. package/plugins/nitro-testing/skills/nitro-testing/test-utils.md +262 -0
  32. package/plugins/nitro-testing/skills/nitro-testing/transaction-rollback.md +183 -0
  33. package/plugins/nitro-testing/skills/nitro-testing/vitest-config.md +236 -0
  34. package/plugins/nuxt-nitro-api/.claude-plugin/plugin.json +8 -0
  35. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/SKILL.md +260 -0
  36. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/auth-patterns.md +228 -0
  37. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/composables-utils.md +174 -0
  38. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/deep-linking.md +190 -0
  39. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-middleware.ts +32 -0
  40. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-utils.ts +51 -0
  41. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/deep-link-page.vue +61 -0
  42. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/service-util.ts +63 -0
  43. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/sse-endpoint.ts +59 -0
  44. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/validation-endpoint.ts +38 -0
  45. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/fetch-patterns.md +178 -0
  46. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/nitro-tasks.md +243 -0
  47. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/page-structure.md +162 -0
  48. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/server-services.md +238 -0
  49. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/sse.md +221 -0
  50. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/ssr-client.md +166 -0
  51. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/validation.md +131 -0
  52. package/scripts/link-skills.mjs +252 -0
@@ -0,0 +1,512 @@
1
+ # Frontend Testing with @nuxt/test-utils
2
+
3
+ Comprehensive guide to testing Vue components, pages, and utilities in Nuxt 3 applications.
4
+
5
+ ## Dependencies
6
+
7
+ ```bash
8
+ yarn add -D @nuxt/test-utils @vue/test-utils happy-dom vitest @vitest/coverage-v8
9
+ ```
10
+
11
+ | Package | Purpose |
12
+ |---------|---------|
13
+ | `@nuxt/test-utils` | Nuxt-aware testing utilities (`mountSuspended`, `mockNuxtImport`, `registerEndpoint`) |
14
+ | `@vue/test-utils` | Vue component testing (wrapper API) |
15
+ | `happy-dom` | Lightweight DOM implementation |
16
+ | `vitest` | Test runner |
17
+
18
+ ## Vitest Configuration
19
+
20
+ Separate frontend and backend tests using an environment variable:
21
+
22
+ ```typescript
23
+ // vitest.config.ts
24
+ import { defineConfig } from "vitest/config";
25
+ import { defineVitestConfig } from "@nuxt/test-utils/config";
26
+ import path from "path";
27
+
28
+ const isNuxtEnv = process.env.VITEST_ENV === "nuxt";
29
+
30
+ export default isNuxtEnv
31
+ ? defineVitestConfig({
32
+ test: {
33
+ environment: "nuxt",
34
+ globals: true,
35
+ include: [
36
+ "components/**/*.test.ts",
37
+ "pages/**/*.test.ts",
38
+ "utils/**/*.test.ts",
39
+ ],
40
+ },
41
+ })
42
+ : defineConfig({
43
+ test: {
44
+ globals: true,
45
+ environment: "node",
46
+ include: ["server/**/*.test.ts"],
47
+ globalSetup: ["./server/test-utils/global-setup.ts"],
48
+ setupFiles: ["./server/test-utils/setup.ts"],
49
+ coverage: {
50
+ provider: "v8",
51
+ reporter: ["text", "json", "html"],
52
+ },
53
+ },
54
+ resolve: {
55
+ alias: {
56
+ "~": path.resolve(__dirname),
57
+ },
58
+ },
59
+ });
60
+ ```
61
+
62
+ ## Nuxt Test Configuration
63
+
64
+ Create a minimal Nuxt config for testing:
65
+
66
+ ```typescript
67
+ // nuxt.config.test.ts
68
+ export default defineNuxtConfig({
69
+ modules: [
70
+ "@primevue/nuxt-module", // Include UI libraries your components need
71
+ ],
72
+ ssr: false, // Simplifies component testing
73
+ });
74
+ ```
75
+
76
+ ## Package.json Scripts
77
+
78
+ ```json
79
+ {
80
+ "scripts": {
81
+ "test": "vitest",
82
+ "test:run": "vitest run",
83
+ "test:coverage": "vitest run --coverage",
84
+ "test:frontend": "VITEST_ENV=nuxt vitest",
85
+ "test:frontend:run": "VITEST_ENV=nuxt vitest run"
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## Core Testing Patterns
91
+
92
+ ### Basic Component Test
93
+
94
+ ```typescript
95
+ import { describe, it, expect } from "vitest";
96
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
97
+ import ProjectCard from "./ProjectCard.vue";
98
+
99
+ describe("ProjectCard", () => {
100
+ it("renders project information", async () => {
101
+ const wrapper = await mountSuspended(ProjectCard, {
102
+ props: {
103
+ project: {
104
+ id: 1,
105
+ name: "Website Redesign",
106
+ status: "active",
107
+ start_date: "2025-01-15",
108
+ },
109
+ },
110
+ });
111
+
112
+ expect(wrapper.exists()).toBe(true);
113
+ expect(wrapper.text()).toContain("Website Redesign");
114
+ expect(wrapper.text()).toContain("active");
115
+ });
116
+
117
+ it("applies correct CSS class for status", async () => {
118
+ const wrapper = await mountSuspended(ProjectCard, {
119
+ props: {
120
+ project: { id: 1, name: "Test", status: "completed" },
121
+ },
122
+ });
123
+
124
+ expect(wrapper.html()).toMatch(/completed/);
125
+ });
126
+ });
127
+ ```
128
+
129
+ ### Testing with Mocked Composables
130
+
131
+ Mock composables **before** mounting:
132
+
133
+ ```typescript
134
+ import { describe, it, expect } from "vitest";
135
+ import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
136
+ import AddressDisplay from "./AddressDisplay.vue";
137
+
138
+ // Mock must be defined before any mountSuspended calls
139
+ mockNuxtImport("useAddress", () => {
140
+ return () => ({
141
+ getDisplayAddress: (project: any) => {
142
+ return project.address || project.address_details?.formatted_address || "No address";
143
+ },
144
+ formatAddress: (address: any) => `${address.city}, ${address.state}`,
145
+ });
146
+ });
147
+
148
+ mockNuxtImport("useUserSession", () => {
149
+ return () => ({
150
+ user: { id: 1, name: "Test User", email: "test@example.com" },
151
+ loggedIn: true,
152
+ });
153
+ });
154
+
155
+ describe("AddressDisplay", () => {
156
+ it("shows formatted address", async () => {
157
+ const wrapper = await mountSuspended(AddressDisplay, {
158
+ props: {
159
+ project: { address: "123 Main St, New York, NY" },
160
+ },
161
+ });
162
+
163
+ expect(wrapper.text()).toContain("123 Main St");
164
+ });
165
+ });
166
+ ```
167
+
168
+ ### Testing with Mocked API Endpoints
169
+
170
+ ```typescript
171
+ import { describe, it, expect, beforeEach } from "vitest";
172
+ import { mountSuspended, registerEndpoint } from "@nuxt/test-utils/runtime";
173
+ import ProjectsList from "./ProjectsList.vue";
174
+
175
+ describe("ProjectsList", () => {
176
+ beforeEach(() => {
177
+ // Register endpoints before each test
178
+ registerEndpoint("/api/projects", {
179
+ method: "GET",
180
+ handler: () => [
181
+ { id: 1, name: "Project Alpha", status: "active" },
182
+ { id: 2, name: "Project Beta", status: "completed" },
183
+ { id: 3, name: "Project Gamma", status: "active" },
184
+ ],
185
+ });
186
+ });
187
+
188
+ it("renders list of projects", async () => {
189
+ const wrapper = await mountSuspended(ProjectsList);
190
+
191
+ expect(wrapper.text()).toContain("Project Alpha");
192
+ expect(wrapper.text()).toContain("Project Beta");
193
+ expect(wrapper.text()).toContain("Project Gamma");
194
+ });
195
+
196
+ it("filters active projects", async () => {
197
+ registerEndpoint("/api/projects", {
198
+ method: "GET",
199
+ handler: (event) => {
200
+ const status = new URL(event.node.req.url!, "http://localhost").searchParams.get("status");
201
+ const projects = [
202
+ { id: 1, name: "Project Alpha", status: "active" },
203
+ { id: 2, name: "Project Beta", status: "completed" },
204
+ ];
205
+ return status ? projects.filter((p) => p.status === status) : projects;
206
+ },
207
+ });
208
+
209
+ const wrapper = await mountSuspended(ProjectsList, {
210
+ props: { filterStatus: "active" },
211
+ });
212
+
213
+ expect(wrapper.text()).toContain("Project Alpha");
214
+ expect(wrapper.text()).not.toContain("Project Beta");
215
+ });
216
+ });
217
+ ```
218
+
219
+ ### Testing Pages with Route Parameters
220
+
221
+ ```typescript
222
+ import { describe, it, expect } from "vitest";
223
+ import { mountSuspended, registerEndpoint } from "@nuxt/test-utils/runtime";
224
+ import TaskDetailPage from "./[id].vue";
225
+
226
+ describe("Task Detail Page", () => {
227
+ it("loads and displays task details", async () => {
228
+ registerEndpoint("/api/tasks/456", {
229
+ method: "GET",
230
+ handler: () => ({
231
+ id: 456,
232
+ name: "Implement feature X",
233
+ status: "in_progress",
234
+ description: "Detailed description here",
235
+ assignee: { id: 1, name: "John Doe" },
236
+ }),
237
+ });
238
+
239
+ const wrapper = await mountSuspended(TaskDetailPage, {
240
+ route: {
241
+ params: { id: "456" },
242
+ },
243
+ });
244
+
245
+ expect(wrapper.text()).toContain("Implement feature X");
246
+ expect(wrapper.text()).toContain("in_progress");
247
+ });
248
+
249
+ it("handles missing task", async () => {
250
+ registerEndpoint("/api/tasks/999", {
251
+ method: "GET",
252
+ handler: () => {
253
+ throw createError({ statusCode: 404, message: "Task not found" });
254
+ },
255
+ });
256
+
257
+ const wrapper = await mountSuspended(TaskDetailPage, {
258
+ route: {
259
+ params: { id: "999" },
260
+ },
261
+ });
262
+
263
+ expect(wrapper.text()).toContain("not found");
264
+ });
265
+ });
266
+ ```
267
+
268
+ ### Testing with UI Libraries (PrimeVue)
269
+
270
+ Stub complex components and register required services:
271
+
272
+ ```typescript
273
+ import { describe, it, expect } from "vitest";
274
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
275
+ import ToastService from "primevue/toastservice";
276
+ import ConfirmationService from "primevue/confirmationservice";
277
+ import ProjectForm from "./ProjectForm.vue";
278
+
279
+ describe("ProjectForm", () => {
280
+ it("renders form fields", async () => {
281
+ const wrapper = await mountSuspended(ProjectForm, {
282
+ props: {
283
+ project: { id: 1, name: "", status: "draft" },
284
+ },
285
+ global: {
286
+ plugins: [ToastService, ConfirmationService],
287
+ stubs: {
288
+ // Stub complex PrimeVue components
289
+ DataTable: true,
290
+ Column: true,
291
+ Dialog: true,
292
+ Calendar: true,
293
+ Dropdown: true,
294
+ // Keep simple components
295
+ InputText: false,
296
+ Button: false,
297
+ },
298
+ },
299
+ });
300
+
301
+ expect(wrapper.exists()).toBe(true);
302
+ });
303
+ });
304
+ ```
305
+
306
+ ### Testing User Interactions
307
+
308
+ ```typescript
309
+ import { describe, it, expect } from "vitest";
310
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
311
+ import { nextTick } from "vue";
312
+ import Counter from "./Counter.vue";
313
+
314
+ describe("Counter", () => {
315
+ it("increments count on button click", async () => {
316
+ const wrapper = await mountSuspended(Counter);
317
+
318
+ expect(wrapper.text()).toContain("Count: 0");
319
+
320
+ await wrapper.find("button.increment").trigger("click");
321
+ await nextTick();
322
+
323
+ expect(wrapper.text()).toContain("Count: 1");
324
+ });
325
+
326
+ it("emits event on submit", async () => {
327
+ const wrapper = await mountSuspended(Counter);
328
+
329
+ await wrapper.find("button.submit").trigger("click");
330
+
331
+ expect(wrapper.emitted("submit")).toBeTruthy();
332
+ expect(wrapper.emitted("submit")![0]).toEqual([{ count: 0 }]);
333
+ });
334
+ });
335
+ ```
336
+
337
+ ### Testing Utility Functions
338
+
339
+ Pure utilities don't need Nuxt context:
340
+
341
+ ```typescript
342
+ import { describe, it, expect, afterEach } from "vitest";
343
+ import { formatDate, parseDate, isValidDate } from "./dates";
344
+
345
+ describe("date utilities", () => {
346
+ const originalTZ = process.env.TZ;
347
+
348
+ afterEach(() => {
349
+ process.env.TZ = originalTZ;
350
+ });
351
+
352
+ describe("formatDate", () => {
353
+ it("formats ISO date string", () => {
354
+ expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
355
+ });
356
+
357
+ it("handles different timezones correctly", () => {
358
+ // Test that date-only strings don't shift across timezone boundaries
359
+ process.env.TZ = "America/New_York";
360
+ expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
361
+
362
+ process.env.TZ = "America/Los_Angeles";
363
+ expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
364
+
365
+ process.env.TZ = "Europe/London";
366
+ expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
367
+ });
368
+
369
+ it("returns empty string for null/undefined", () => {
370
+ expect(formatDate(null)).toBe("");
371
+ expect(formatDate(undefined)).toBe("");
372
+ });
373
+ });
374
+
375
+ describe("isValidDate", () => {
376
+ it("validates correct dates", () => {
377
+ expect(isValidDate("2025-01-15")).toBe(true);
378
+ expect(isValidDate("2025-12-31")).toBe(true);
379
+ });
380
+
381
+ it("rejects invalid dates", () => {
382
+ expect(isValidDate("not-a-date")).toBe(false);
383
+ expect(isValidDate("2025-13-01")).toBe(false);
384
+ });
385
+ });
386
+ });
387
+ ```
388
+
389
+ ## Wrapper API Reference
390
+
391
+ Common methods on the wrapper returned by `mountSuspended`:
392
+
393
+ | Method | Description |
394
+ |--------|-------------|
395
+ | `wrapper.text()` | All text content (rendered) |
396
+ | `wrapper.html()` | Full HTML output |
397
+ | `wrapper.exists()` | Check if component mounted |
398
+ | `wrapper.props()` | Get all props |
399
+ | `wrapper.props('name')` | Get specific prop |
400
+ | `wrapper.emitted()` | All emitted events |
401
+ | `wrapper.emitted('click')` | Specific event emissions |
402
+ | `wrapper.find(selector)` | Find single element |
403
+ | `wrapper.findAll(selector)` | Find all matching elements |
404
+ | `wrapper.trigger('click')` | Trigger DOM event |
405
+ | `wrapper.setValue(value)` | Set input value |
406
+
407
+ ## Common Gotchas
408
+
409
+ ### 1. Always Use `mountSuspended`
410
+
411
+ ```typescript
412
+ // WRONG - doesn't handle async setup or Nuxt context
413
+ import { mount } from "@vue/test-utils";
414
+ const wrapper = mount(MyComponent);
415
+
416
+ // RIGHT - handles Suspense, async setup, and Nuxt context
417
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
418
+ const wrapper = await mountSuspended(MyComponent);
419
+ ```
420
+
421
+ ### 2. Mock Before Mount
422
+
423
+ ```typescript
424
+ // WRONG - mock after mount has no effect
425
+ const wrapper = await mountSuspended(MyComponent);
426
+ mockNuxtImport("useAuth", () => () => ({ user: null }));
427
+
428
+ // RIGHT - mock before mount
429
+ mockNuxtImport("useAuth", () => () => ({ user: null }));
430
+ const wrapper = await mountSuspended(MyComponent);
431
+ ```
432
+
433
+ ### 3. Register Endpoints Before Mount
434
+
435
+ ```typescript
436
+ // WRONG - component fetches before endpoint exists
437
+ const wrapper = await mountSuspended(DataComponent);
438
+ registerEndpoint("/api/data", { handler: () => [] });
439
+
440
+ // RIGHT - endpoint ready before component mounts
441
+ registerEndpoint("/api/data", { handler: () => [] });
442
+ const wrapper = await mountSuspended(DataComponent);
443
+ ```
444
+
445
+ ### 4. Await All Async Operations
446
+
447
+ ```typescript
448
+ // WRONG - assertion runs before DOM updates
449
+ wrapper.find("button").trigger("click");
450
+ expect(wrapper.text()).toContain("Updated");
451
+
452
+ // RIGHT - wait for Vue to process updates
453
+ await wrapper.find("button").trigger("click");
454
+ await nextTick();
455
+ expect(wrapper.text()).toContain("Updated");
456
+ ```
457
+
458
+ ### 5. Use `wrapper.text()` Over Specific Selectors
459
+
460
+ ```typescript
461
+ // FRAGILE - breaks if component structure changes
462
+ expect(wrapper.find(".project-name span").text()).toBe("My Project");
463
+
464
+ // ROBUST - checks rendered output regardless of structure
465
+ expect(wrapper.text()).toContain("My Project");
466
+ ```
467
+
468
+ ### 6. Date Testing Across Timezones
469
+
470
+ When testing date formatting, test multiple timezones to catch off-by-one-day bugs:
471
+
472
+ ```typescript
473
+ const timezones = ["America/New_York", "America/Los_Angeles", "Europe/London", "Asia/Tokyo"];
474
+
475
+ for (const tz of timezones) {
476
+ it(`formats date correctly in ${tz}`, () => {
477
+ process.env.TZ = tz;
478
+ // Date string "2025-01-15" should always display as Jan 15
479
+ // regardless of timezone
480
+ expect(formatDate("2025-01-15")).toBe("Jan 15, 2025");
481
+ });
482
+ }
483
+ ```
484
+
485
+ ## File Organization
486
+
487
+ Co-locate tests with source files:
488
+
489
+ ```
490
+ components/
491
+ projects/
492
+ ProjectCard.vue
493
+ ProjectCard.test.ts # Component test
494
+ ProjectsList.vue
495
+ ProjectsList.test.ts
496
+ pages/
497
+ projects/
498
+ index.vue
499
+ index.test.ts # Page test
500
+ [id].vue
501
+ [id].test.ts
502
+ utils/
503
+ dates.ts
504
+ dates.test.ts # Utility test
505
+ formatting.ts
506
+ formatting.test.ts
507
+ ```
508
+
509
+ Benefits:
510
+ - Easy to find tests for any file
511
+ - Tests move with components during refactoring
512
+ - Clear coverage visibility