omgkit 2.2.0 → 2.3.1

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 (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,1009 +1,149 @@
1
1
  ---
2
- name: vitest
3
- description: Vitest testing for TypeScript/JavaScript with mocking, coverage, snapshot testing, and Vue/React component testing
4
- category: testing
5
- triggers:
6
- - vitest
7
- - javascript testing
8
- - typescript testing
9
- - unit testing js
10
- - vue testing
11
- - react testing
2
+ name: Testing with Vitest
3
+ description: Claude writes fast, reliable tests using Vitest for TypeScript/JavaScript projects. Use when writing unit tests, component tests, setting up mocks, snapshot testing, or configuring test coverage.
12
4
  ---
13
5
 
14
- # Vitest
6
+ # Testing with Vitest
15
7
 
16
- Enterprise-grade **JavaScript/TypeScript testing framework** following industry best practices. This skill covers unit testing, mocking, snapshot testing, component testing, coverage reports, and CI integration patterns used by top engineering teams.
17
-
18
- ## Purpose
19
-
20
- Build comprehensive JavaScript/TypeScript test suites:
21
-
22
- - Write fast and reliable unit tests
23
- - Mock modules, timers, and network requests
24
- - Test Vue and React components
25
- - Implement snapshot testing
26
- - Generate coverage reports
27
- - Run tests in parallel and watch mode
28
- - Integrate with CI/CD pipelines
29
-
30
- ## Features
31
-
32
- ### 1. Configuration Setup
8
+ ## Quick Start
33
9
 
34
10
  ```typescript
35
11
  // vitest.config.ts
36
12
  import { defineConfig } from "vitest/config";
37
- import vue from "@vitejs/plugin-vue";
38
- import react from "@vitejs/plugin-react";
39
- import path from "path";
40
13
 
41
14
  export default defineConfig({
42
- plugins: [vue(), react()],
43
15
  test: {
44
16
  globals: true,
45
17
  environment: "jsdom",
46
- include: ["**/*.{test,spec}.{js,ts,jsx,tsx}"],
47
- exclude: ["**/node_modules/**", "**/dist/**", "**/e2e/**"],
48
18
  setupFiles: ["./tests/setup.ts"],
49
19
  coverage: {
50
20
  provider: "v8",
51
- reporter: ["text", "json", "html", "lcov"],
52
- reportsDirectory: "./coverage",
53
- exclude: [
54
- "node_modules/",
55
- "tests/",
56
- "**/*.d.ts",
57
- "**/*.config.*",
58
- "**/types/**",
59
- ],
60
- thresholds: {
61
- global: {
62
- branches: 80,
63
- functions: 80,
64
- lines: 80,
65
- statements: 80,
66
- },
67
- },
68
- },
69
- pool: "threads",
70
- poolOptions: {
71
- threads: {
72
- singleThread: false,
73
- maxThreads: 4,
74
- minThreads: 1,
75
- },
76
- },
77
- testTimeout: 10000,
78
- hookTimeout: 10000,
79
- reporters: ["default", "json", "junit"],
80
- outputFile: {
81
- json: "./test-results/results.json",
82
- junit: "./test-results/junit.xml",
83
- },
84
- typecheck: {
85
- enabled: true,
86
- checker: "tsc",
87
- },
88
- },
89
- resolve: {
90
- alias: {
91
- "@": path.resolve(__dirname, "./src"),
92
- "@tests": path.resolve(__dirname, "./tests"),
21
+ reporter: ["text", "html"],
22
+ thresholds: { global: { branches: 80, functions: 80, lines: 80 } },
93
23
  },
94
24
  },
95
25
  });
96
- ```
97
26
 
98
- ```typescript
99
27
  // tests/setup.ts
100
- import { beforeAll, afterAll, afterEach, vi } from "vitest";
28
+ import { afterEach, vi } from "vitest";
101
29
  import { cleanup } from "@testing-library/vue";
102
30
  import "@testing-library/jest-dom/vitest";
103
31
 
104
- // Reset mocks after each test
105
- afterEach(() => {
106
- vi.clearAllMocks();
107
- vi.resetAllMocks();
108
- cleanup();
109
- });
32
+ afterEach(() => { vi.clearAllMocks(); cleanup(); });
33
+ ```
110
34
 
111
- // Global setup
112
- beforeAll(() => {
113
- // Mock window.matchMedia
114
- Object.defineProperty(window, "matchMedia", {
115
- writable: true,
116
- value: vi.fn().mockImplementation((query) => ({
117
- matches: false,
118
- media: query,
119
- onchange: null,
120
- addListener: vi.fn(),
121
- removeListener: vi.fn(),
122
- addEventListener: vi.fn(),
123
- removeEventListener: vi.fn(),
124
- dispatchEvent: vi.fn(),
125
- })),
126
- });
35
+ ## Features
127
36
 
128
- // Mock IntersectionObserver
129
- const mockIntersectionObserver = vi.fn();
130
- mockIntersectionObserver.mockReturnValue({
131
- observe: vi.fn(),
132
- unobserve: vi.fn(),
133
- disconnect: vi.fn(),
134
- });
135
- window.IntersectionObserver = mockIntersectionObserver;
136
-
137
- // Mock ResizeObserver
138
- window.ResizeObserver = vi.fn().mockImplementation(() => ({
139
- observe: vi.fn(),
140
- unobserve: vi.fn(),
141
- disconnect: vi.fn(),
142
- }));
143
- });
37
+ | Feature | Description | Reference |
38
+ |---------|-------------|-----------|
39
+ | Unit Testing | Fast isolated tests with describe/it/expect | [Vitest API](https://vitest.dev/api/) |
40
+ | Mocking | Module, function, and timer mocking with vi | [Mocking Guide](https://vitest.dev/guide/mocking.html) |
41
+ | Snapshot Testing | Component and data structure snapshots | [Snapshot Testing](https://vitest.dev/guide/snapshot.html) |
42
+ | Component Testing | Vue/React component testing with Testing Library | [Vue Test Utils](https://test-utils.vuejs.org/) |
43
+ | Coverage Reports | V8/Istanbul coverage with thresholds | [Coverage](https://vitest.dev/guide/coverage.html) |
44
+ | Parallel Execution | Multi-threaded test runner | [Test Runner](https://vitest.dev/guide/features.html) |
144
45
 
145
- // Global teardown
146
- afterAll(() => {
147
- vi.restoreAllMocks();
148
- });
149
- ```
46
+ ## Common Patterns
150
47
 
151
- ### 2. Unit Testing Patterns
48
+ ### Unit Testing with Parametrization
152
49
 
153
50
  ```typescript
154
- // tests/unit/utils/string.test.ts
155
51
  import { describe, it, expect, test } from "vitest";
156
- import {
157
- capitalize,
158
- truncate,
159
- slugify,
160
- camelToKebab,
161
- parseTemplate,
162
- } from "@/utils/string";
163
-
164
- describe("String Utilities", () => {
165
- describe("capitalize", () => {
166
- it("capitalizes the first letter", () => {
167
- expect(capitalize("hello")).toBe("Hello");
168
- });
169
-
170
- it("handles empty strings", () => {
171
- expect(capitalize("")).toBe("");
172
- });
173
-
174
- it("handles single character", () => {
175
- expect(capitalize("a")).toBe("A");
176
- });
177
-
178
- it("preserves rest of string case", () => {
179
- expect(capitalize("hELLO")).toBe("HELLO");
180
- });
181
- });
182
-
183
- describe("truncate", () => {
184
- it("truncates long strings", () => {
185
- expect(truncate("Hello World", 5)).toBe("Hello...");
186
- });
187
-
188
- it("returns original if shorter than limit", () => {
189
- expect(truncate("Hi", 10)).toBe("Hi");
190
- });
191
-
192
- it("uses custom suffix", () => {
193
- expect(truncate("Hello World", 5, "…")).toBe("Hello…");
194
- });
195
-
196
- test.each([
197
- ["Hello World", 5, "...", "Hello..."],
198
- ["Short", 10, "...", "Short"],
199
- ["Test", 4, "", "Test"],
200
- ["Testing", 4, "…", "Test…"],
201
- ])('truncate("%s", %d, "%s") returns "%s"', (str, len, suffix, expected) => {
202
- expect(truncate(str, len, suffix)).toBe(expected);
203
- });
204
- });
205
-
206
- describe("slugify", () => {
207
- it("converts to lowercase", () => {
208
- expect(slugify("HELLO")).toBe("hello");
209
- });
210
-
211
- it("replaces spaces with hyphens", () => {
212
- expect(slugify("Hello World")).toBe("hello-world");
213
- });
214
-
215
- it("removes special characters", () => {
216
- expect(slugify("Hello! World?")).toBe("hello-world");
217
- });
218
-
219
- it("handles multiple spaces", () => {
220
- expect(slugify("Hello World")).toBe("hello-world");
221
- });
222
- });
223
-
224
- describe("parseTemplate", () => {
225
- it("replaces placeholders with values", () => {
226
- const template = "Hello, {{name}}!";
227
- const result = parseTemplate(template, { name: "World" });
228
- expect(result).toBe("Hello, World!");
229
- });
230
-
231
- it("handles multiple placeholders", () => {
232
- const template = "{{greeting}}, {{name}}!";
233
- const result = parseTemplate(template, {
234
- greeting: "Hi",
235
- name: "John",
236
- });
237
- expect(result).toBe("Hi, John!");
238
- });
239
-
240
- it("leaves unmatched placeholders", () => {
241
- const template = "Hello, {{name}}!";
242
- const result = parseTemplate(template, {});
243
- expect(result).toBe("Hello, {{name}}!");
244
- });
245
- });
246
- });
247
-
248
- // tests/unit/utils/async.test.ts
249
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
250
- import { debounce, throttle, retry, timeout } from "@/utils/async";
251
-
252
- describe("Async Utilities", () => {
253
- beforeEach(() => {
254
- vi.useFakeTimers();
255
- });
256
-
257
- afterEach(() => {
258
- vi.useRealTimers();
259
- });
260
-
261
- describe("debounce", () => {
262
- it("delays function execution", () => {
263
- const fn = vi.fn();
264
- const debouncedFn = debounce(fn, 100);
265
-
266
- debouncedFn();
267
- expect(fn).not.toHaveBeenCalled();
268
-
269
- vi.advanceTimersByTime(100);
270
- expect(fn).toHaveBeenCalledTimes(1);
271
- });
272
-
273
- it("resets timer on subsequent calls", () => {
274
- const fn = vi.fn();
275
- const debouncedFn = debounce(fn, 100);
276
-
277
- debouncedFn();
278
- vi.advanceTimersByTime(50);
279
- debouncedFn();
280
- vi.advanceTimersByTime(50);
281
-
282
- expect(fn).not.toHaveBeenCalled();
283
-
284
- vi.advanceTimersByTime(50);
285
- expect(fn).toHaveBeenCalledTimes(1);
286
- });
287
- });
288
-
289
- describe("throttle", () => {
290
- it("limits function calls", () => {
291
- const fn = vi.fn();
292
- const throttledFn = throttle(fn, 100);
293
-
294
- throttledFn();
295
- throttledFn();
296
- throttledFn();
297
-
298
- expect(fn).toHaveBeenCalledTimes(1);
299
-
300
- vi.advanceTimersByTime(100);
301
- throttledFn();
302
52
 
303
- expect(fn).toHaveBeenCalledTimes(2);
304
- });
305
- });
306
-
307
- describe("retry", () => {
308
- it("retries failed operations", async () => {
309
- const fn = vi
310
- .fn()
311
- .mockRejectedValueOnce(new Error("Fail 1"))
312
- .mockRejectedValueOnce(new Error("Fail 2"))
313
- .mockResolvedValueOnce("Success");
314
-
315
- const result = await retry(fn, { maxAttempts: 3, delay: 100 });
316
-
317
- expect(result).toBe("Success");
318
- expect(fn).toHaveBeenCalledTimes(3);
319
- });
320
-
321
- it("throws after max attempts", async () => {
322
- const fn = vi.fn().mockRejectedValue(new Error("Always fails"));
323
-
324
- await expect(retry(fn, { maxAttempts: 3, delay: 10 })).rejects.toThrow(
325
- "Always fails"
326
- );
327
-
328
- expect(fn).toHaveBeenCalledTimes(3);
329
- });
330
- });
331
-
332
- describe("timeout", () => {
333
- it("resolves if promise completes in time", async () => {
334
- const promise = Promise.resolve("Success");
335
- const result = await timeout(promise, 1000);
336
- expect(result).toBe("Success");
337
- });
338
-
339
- it("rejects if promise takes too long", async () => {
340
- const slowPromise = new Promise((resolve) =>
341
- setTimeout(() => resolve("Late"), 2000)
342
- );
343
-
344
- const timeoutPromise = timeout(slowPromise, 1000);
345
-
346
- vi.advanceTimersByTime(1000);
347
-
348
- await expect(timeoutPromise).rejects.toThrow("Operation timed out");
349
- });
53
+ describe("String Utils", () => {
54
+ test.each([
55
+ ["hello", 3, "hel..."],
56
+ ["hi", 10, "hi"],
57
+ ["test", 4, "test"],
58
+ ])('truncate("%s", %d) returns "%s"', (str, len, expected) => {
59
+ expect(truncate(str, len)).toBe(expected);
350
60
  });
351
61
  });
352
62
  ```
353
63
 
354
- ### 3. Mocking Patterns
64
+ ### Mocking Modules and Services
355
65
 
356
66
  ```typescript
357
- // tests/unit/services/user.test.ts
358
67
  import { describe, it, expect, vi, beforeEach, Mock } from "vitest";
359
68
  import { UserService } from "@/services/user";
360
69
  import { apiClient } from "@/lib/api";
361
- import { cache } from "@/lib/cache";
362
70
 
363
- // Mock modules
364
71
  vi.mock("@/lib/api", () => ({
365
- apiClient: {
366
- get: vi.fn(),
367
- post: vi.fn(),
368
- put: vi.fn(),
369
- delete: vi.fn(),
370
- },
371
- }));
372
-
373
- vi.mock("@/lib/cache", () => ({
374
- cache: {
375
- get: vi.fn(),
376
- set: vi.fn(),
377
- delete: vi.fn(),
378
- },
72
+ apiClient: { get: vi.fn(), post: vi.fn() },
379
73
  }));
380
74
 
381
75
  describe("UserService", () => {
382
- let userService: UserService;
383
-
384
- beforeEach(() => {
385
- vi.clearAllMocks();
386
- userService = new UserService();
387
- });
388
-
389
- describe("getUser", () => {
390
- it("fetches user from API", async () => {
391
- const mockUser = { id: "1", name: "John", email: "john@example.com" };
392
- (apiClient.get as Mock).mockResolvedValue({ data: mockUser });
393
- (cache.get as Mock).mockReturnValue(null);
394
-
395
- const user = await userService.getUser("1");
396
-
397
- expect(apiClient.get).toHaveBeenCalledWith("/users/1");
398
- expect(user).toEqual(mockUser);
399
- });
400
-
401
- it("returns cached user if available", async () => {
402
- const cachedUser = { id: "1", name: "John", email: "john@example.com" };
403
- (cache.get as Mock).mockReturnValue(cachedUser);
404
-
405
- const user = await userService.getUser("1");
406
-
407
- expect(cache.get).toHaveBeenCalledWith("user:1");
408
- expect(apiClient.get).not.toHaveBeenCalled();
409
- expect(user).toEqual(cachedUser);
410
- });
411
-
412
- it("caches fetched user", async () => {
413
- const mockUser = { id: "1", name: "John", email: "john@example.com" };
414
- (apiClient.get as Mock).mockResolvedValue({ data: mockUser });
415
- (cache.get as Mock).mockReturnValue(null);
416
-
417
- await userService.getUser("1");
418
-
419
- expect(cache.set).toHaveBeenCalledWith("user:1", mockUser, {
420
- ttl: 3600,
421
- });
422
- });
423
- });
424
-
425
- describe("createUser", () => {
426
- it("creates user and invalidates cache", async () => {
427
- const newUser = { name: "Jane", email: "jane@example.com" };
428
- const createdUser = { id: "2", ...newUser };
429
- (apiClient.post as Mock).mockResolvedValue({ data: createdUser });
430
-
431
- const user = await userService.createUser(newUser);
432
-
433
- expect(apiClient.post).toHaveBeenCalledWith("/users", newUser);
434
- expect(cache.delete).toHaveBeenCalledWith("users:list");
435
- expect(user).toEqual(createdUser);
436
- });
437
-
438
- it("throws on validation error", async () => {
439
- (apiClient.post as Mock).mockRejectedValue({
440
- response: {
441
- status: 400,
442
- data: { errors: { email: "Invalid email" } },
443
- },
444
- });
445
-
446
- await expect(
447
- userService.createUser({ name: "Test", email: "invalid" })
448
- ).rejects.toMatchObject({
449
- response: { status: 400 },
450
- });
451
- });
452
- });
453
- });
454
-
455
- // tests/unit/hooks/useApi.test.ts
456
- import { describe, it, expect, vi, beforeEach } from "vitest";
457
- import { renderHook, waitFor } from "@testing-library/vue";
458
- import { useApi } from "@/hooks/useApi";
459
-
460
- // Mock fetch globally
461
- const mockFetch = vi.fn();
462
- global.fetch = mockFetch;
463
-
464
- describe("useApi", () => {
465
- beforeEach(() => {
466
- mockFetch.mockReset();
467
- });
468
-
469
- it("fetches data successfully", async () => {
470
- const mockData = { users: [{ id: 1, name: "John" }] };
471
- mockFetch.mockResolvedValueOnce({
472
- ok: true,
473
- json: async () => mockData,
474
- });
475
-
476
- const { result } = renderHook(() => useApi("/api/users"));
477
-
478
- expect(result.current.loading).toBe(true);
479
-
480
- await waitFor(() => {
481
- expect(result.current.loading).toBe(false);
482
- });
483
-
484
- expect(result.current.data).toEqual(mockData);
485
- expect(result.current.error).toBeNull();
486
- });
487
-
488
- it("handles fetch errors", async () => {
489
- mockFetch.mockRejectedValueOnce(new Error("Network error"));
490
-
491
- const { result } = renderHook(() => useApi("/api/users"));
492
-
493
- await waitFor(() => {
494
- expect(result.current.loading).toBe(false);
495
- });
496
-
497
- expect(result.current.error).toBeInstanceOf(Error);
498
- expect(result.current.data).toBeNull();
499
- });
500
-
501
- it("refetches on manual trigger", async () => {
502
- const mockData1 = { count: 1 };
503
- const mockData2 = { count: 2 };
504
- mockFetch
505
- .mockResolvedValueOnce({ ok: true, json: async () => mockData1 })
506
- .mockResolvedValueOnce({ ok: true, json: async () => mockData2 });
507
-
508
- const { result } = renderHook(() => useApi("/api/count"));
509
-
510
- await waitFor(() => {
511
- expect(result.current.data).toEqual(mockData1);
512
- });
76
+ beforeEach(() => vi.clearAllMocks());
513
77
 
514
- result.current.refetch();
78
+ it("fetches user from API", async () => {
79
+ const mockUser = { id: "1", name: "John" };
80
+ (apiClient.get as Mock).mockResolvedValue({ data: mockUser });
515
81
 
516
- await waitFor(() => {
517
- expect(result.current.data).toEqual(mockData2);
518
- });
82
+ const user = await new UserService().getUser("1");
519
83
 
520
- expect(mockFetch).toHaveBeenCalledTimes(2);
84
+ expect(apiClient.get).toHaveBeenCalledWith("/users/1");
85
+ expect(user).toEqual(mockUser);
521
86
  });
522
87
  });
523
88
  ```
524
89
 
525
- ### 4. Vue Component Testing
90
+ ### Vue Component Testing
526
91
 
527
92
  ```typescript
528
- // tests/components/Button.test.ts
529
93
  import { describe, it, expect, vi } from "vitest";
530
94
  import { mount } from "@vue/test-utils";
531
95
  import Button from "@/components/Button.vue";
532
96
 
533
97
  describe("Button", () => {
534
- it("renders with default props", () => {
535
- const wrapper = mount(Button, {
536
- slots: {
537
- default: "Click me",
538
- },
539
- });
540
-
541
- expect(wrapper.text()).toContain("Click me");
542
- expect(wrapper.classes()).toContain("btn-primary");
543
- });
544
-
545
- it("applies variant classes", () => {
546
- const wrapper = mount(Button, {
547
- props: { variant: "secondary" },
548
- slots: { default: "Button" },
549
- });
550
-
551
- expect(wrapper.classes()).toContain("btn-secondary");
552
- });
553
-
554
98
  it("emits click event", async () => {
555
- const wrapper = mount(Button, {
556
- slots: { default: "Click" },
557
- });
558
-
99
+ const wrapper = mount(Button, { slots: { default: "Click" } });
559
100
  await wrapper.trigger("click");
560
-
561
101
  expect(wrapper.emitted("click")).toHaveLength(1);
562
102
  });
563
103
 
564
104
  it("is disabled when loading", () => {
565
- const wrapper = mount(Button, {
566
- props: { loading: true },
567
- slots: { default: "Submit" },
568
- });
569
-
105
+ const wrapper = mount(Button, { props: { loading: true } });
570
106
  expect(wrapper.attributes("disabled")).toBeDefined();
571
- expect(wrapper.find(".spinner").exists()).toBe(true);
572
- });
573
-
574
- it("does not emit click when disabled", async () => {
575
- const wrapper = mount(Button, {
576
- props: { disabled: true },
577
- slots: { default: "Click" },
578
- });
579
-
580
- await wrapper.trigger("click");
581
-
582
- expect(wrapper.emitted("click")).toBeUndefined();
583
- });
584
- });
585
-
586
- // tests/components/UserProfile.test.ts
587
- import { describe, it, expect, vi, beforeEach } from "vitest";
588
- import { mount, flushPromises } from "@vue/test-utils";
589
- import { createPinia, setActivePinia } from "pinia";
590
- import UserProfile from "@/components/UserProfile.vue";
591
- import { useUserStore } from "@/stores/user";
592
-
593
- describe("UserProfile", () => {
594
- beforeEach(() => {
595
- setActivePinia(createPinia());
596
- });
597
-
598
- it("displays user information", async () => {
599
- const userStore = useUserStore();
600
- userStore.user = {
601
- id: "1",
602
- name: "John Doe",
603
- email: "john@example.com",
604
- avatar: "https://example.com/avatar.jpg",
605
- };
606
-
607
- const wrapper = mount(UserProfile);
608
-
609
- expect(wrapper.find("[data-testid='user-name']").text()).toBe("John Doe");
610
- expect(wrapper.find("[data-testid='user-email']").text()).toBe(
611
- "john@example.com"
612
- );
613
- expect(wrapper.find("img").attributes("src")).toBe(
614
- "https://example.com/avatar.jpg"
615
- );
616
- });
617
-
618
- it("shows loading state", () => {
619
- const userStore = useUserStore();
620
- userStore.loading = true;
621
-
622
- const wrapper = mount(UserProfile);
623
-
624
- expect(wrapper.find("[data-testid='loading']").exists()).toBe(true);
625
- expect(wrapper.find("[data-testid='user-name']").exists()).toBe(false);
626
- });
627
-
628
- it("handles logout", async () => {
629
- const userStore = useUserStore();
630
- userStore.user = { id: "1", name: "John", email: "john@example.com" };
631
- userStore.logout = vi.fn();
632
-
633
- const wrapper = mount(UserProfile);
634
-
635
- await wrapper.find("[data-testid='logout-btn']").trigger("click");
636
-
637
- expect(userStore.logout).toHaveBeenCalled();
638
- });
639
- });
640
-
641
- // tests/components/SearchInput.test.ts
642
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
643
- import { mount } from "@vue/test-utils";
644
- import SearchInput from "@/components/SearchInput.vue";
645
-
646
- describe("SearchInput", () => {
647
- beforeEach(() => {
648
- vi.useFakeTimers();
649
- });
650
-
651
- afterEach(() => {
652
- vi.useRealTimers();
653
- });
654
-
655
- it("debounces input", async () => {
656
- const wrapper = mount(SearchInput, {
657
- props: { debounce: 300 },
658
- });
659
-
660
- await wrapper.find("input").setValue("test");
661
-
662
- expect(wrapper.emitted("search")).toBeUndefined();
663
-
664
- vi.advanceTimersByTime(300);
665
-
666
- expect(wrapper.emitted("search")).toHaveLength(1);
667
- expect(wrapper.emitted("search")![0]).toEqual(["test"]);
668
- });
669
-
670
- it("clears input on clear button click", async () => {
671
- const wrapper = mount(SearchInput);
672
-
673
- await wrapper.find("input").setValue("test");
674
- await wrapper.find("[data-testid='clear-btn']").trigger("click");
675
-
676
- expect((wrapper.find("input").element as HTMLInputElement).value).toBe("");
677
- expect(wrapper.emitted("clear")).toHaveLength(1);
678
- });
679
- });
680
- ```
681
-
682
- ### 5. Snapshot Testing
683
-
684
- ```typescript
685
- // tests/snapshots/components.test.ts
686
- import { describe, it, expect } from "vitest";
687
- import { mount } from "@vue/test-utils";
688
- import Card from "@/components/Card.vue";
689
- import Badge from "@/components/Badge.vue";
690
- import Alert from "@/components/Alert.vue";
691
-
692
- describe("Component Snapshots", () => {
693
- describe("Card", () => {
694
- it("matches snapshot with default props", () => {
695
- const wrapper = mount(Card, {
696
- slots: {
697
- header: "Card Title",
698
- default: "Card content",
699
- footer: "Card footer",
700
- },
701
- });
702
-
703
- expect(wrapper.html()).toMatchSnapshot();
704
- });
705
-
706
- it("matches snapshot with custom props", () => {
707
- const wrapper = mount(Card, {
708
- props: {
709
- variant: "outlined",
710
- hoverable: true,
711
- },
712
- slots: {
713
- default: "Content",
714
- },
715
- });
716
-
717
- expect(wrapper.html()).toMatchSnapshot();
718
- });
719
- });
720
-
721
- describe("Badge", () => {
722
- it.each(["default", "primary", "success", "warning", "error"] as const)(
723
- "matches snapshot for %s variant",
724
- (variant) => {
725
- const wrapper = mount(Badge, {
726
- props: { variant },
727
- slots: { default: "Badge" },
728
- });
729
-
730
- expect(wrapper.html()).toMatchSnapshot();
731
- }
732
- );
733
- });
734
-
735
- describe("Alert", () => {
736
- it("matches snapshot with icon and close button", () => {
737
- const wrapper = mount(Alert, {
738
- props: {
739
- type: "warning",
740
- title: "Warning",
741
- closable: true,
742
- },
743
- slots: {
744
- default: "This is a warning message",
745
- },
746
- });
747
-
748
- expect(wrapper.html()).toMatchSnapshot();
749
- });
750
- });
751
- });
752
-
753
- // tests/snapshots/data-structures.test.ts
754
- import { describe, it, expect } from "vitest";
755
- import { transformUserData, normalizeApiResponse } from "@/utils/transforms";
756
-
757
- describe("Data Transform Snapshots", () => {
758
- it("transforms user data correctly", () => {
759
- const rawUser = {
760
- id: "123",
761
- first_name: "John",
762
- last_name: "Doe",
763
- email_address: "john.doe@example.com",
764
- created_at: "2024-01-15T10:30:00Z",
765
- roles: ["admin", "user"],
766
- };
767
-
768
- const transformed = transformUserData(rawUser);
769
-
770
- expect(transformed).toMatchInlineSnapshot(`
771
- {
772
- "createdAt": 2024-01-15T10:30:00.000Z,
773
- "email": "john.doe@example.com",
774
- "fullName": "John Doe",
775
- "id": "123",
776
- "isAdmin": true,
777
- "roles": [
778
- "admin",
779
- "user",
780
- ],
781
- }
782
- `);
783
- });
784
-
785
- it("normalizes API response", () => {
786
- const apiResponse = {
787
- data: [
788
- { id: 1, name: "Item 1" },
789
- { id: 2, name: "Item 2" },
790
- ],
791
- meta: {
792
- total: 100,
793
- page: 1,
794
- per_page: 10,
795
- },
796
- };
797
-
798
- const normalized = normalizeApiResponse(apiResponse);
799
-
800
- expect(normalized).toMatchInlineSnapshot(`
801
- {
802
- "items": [
803
- {
804
- "id": 1,
805
- "name": "Item 1",
806
- },
807
- {
808
- "id": 2,
809
- "name": "Item 2",
810
- },
811
- ],
812
- "pagination": {
813
- "currentPage": 1,
814
- "perPage": 10,
815
- "totalItems": 100,
816
- "totalPages": 10,
817
- },
818
- }
819
- `);
820
107
  });
821
108
  });
822
109
  ```
823
110
 
824
- ### 6. React Component Testing
111
+ ### Testing Async Operations with Timers
825
112
 
826
113
  ```typescript
827
- // tests/components/react/Counter.test.tsx
828
- import { describe, it, expect } from "vitest";
829
- import { render, screen, fireEvent } from "@testing-library/react";
830
- import Counter from "@/components/Counter";
831
-
832
- describe("Counter", () => {
833
- it("renders initial count", () => {
834
- render(<Counter initialCount={5} />);
835
-
836
- expect(screen.getByText("Count: 5")).toBeInTheDocument();
837
- });
838
-
839
- it("increments count on button click", async () => {
840
- render(<Counter initialCount={0} />);
841
-
842
- const incrementBtn = screen.getByRole("button", { name: /increment/i });
843
- await fireEvent.click(incrementBtn);
844
-
845
- expect(screen.getByText("Count: 1")).toBeInTheDocument();
846
- });
847
-
848
- it("decrements count on button click", async () => {
849
- render(<Counter initialCount={5} />);
850
-
851
- const decrementBtn = screen.getByRole("button", { name: /decrement/i });
852
- await fireEvent.click(decrementBtn);
853
-
854
- expect(screen.getByText("Count: 4")).toBeInTheDocument();
855
- });
856
-
857
- it("respects min value", async () => {
858
- render(<Counter initialCount={0} min={0} />);
859
-
860
- const decrementBtn = screen.getByRole("button", { name: /decrement/i });
861
- await fireEvent.click(decrementBtn);
862
-
863
- expect(screen.getByText("Count: 0")).toBeInTheDocument();
864
- expect(decrementBtn).toBeDisabled();
865
- });
866
- });
867
-
868
- // tests/components/react/Form.test.tsx
869
- import { describe, it, expect, vi } from "vitest";
870
- import { render, screen, fireEvent, waitFor } from "@testing-library/react";
871
- import userEvent from "@testing-library/user-event";
872
- import ContactForm from "@/components/ContactForm";
873
-
874
- describe("ContactForm", () => {
875
- const mockSubmit = vi.fn();
876
-
877
- it("submits form with valid data", async () => {
878
- const user = userEvent.setup();
879
- render(<ContactForm onSubmit={mockSubmit} />);
880
-
881
- await user.type(screen.getByLabelText(/name/i), "John Doe");
882
- await user.type(screen.getByLabelText(/email/i), "john@example.com");
883
- await user.type(screen.getByLabelText(/message/i), "Hello!");
884
- await user.click(screen.getByRole("button", { name: /submit/i }));
885
-
886
- await waitFor(() => {
887
- expect(mockSubmit).toHaveBeenCalledWith({
888
- name: "John Doe",
889
- email: "john@example.com",
890
- message: "Hello!",
891
- });
892
- });
893
- });
894
-
895
- it("displays validation errors", async () => {
896
- const user = userEvent.setup();
897
- render(<ContactForm onSubmit={mockSubmit} />);
898
-
899
- await user.click(screen.getByRole("button", { name: /submit/i }));
114
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
900
115
 
901
- expect(await screen.findByText(/name is required/i)).toBeInTheDocument();
902
- expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
903
- expect(mockSubmit).not.toHaveBeenCalled();
904
- });
116
+ describe("Debounce", () => {
117
+ beforeEach(() => vi.useFakeTimers());
118
+ afterEach(() => vi.useRealTimers());
905
119
 
906
- it("validates email format", async () => {
907
- const user = userEvent.setup();
908
- render(<ContactForm onSubmit={mockSubmit} />);
120
+ it("delays function execution", () => {
121
+ const fn = vi.fn();
122
+ const debouncedFn = debounce(fn, 100);
909
123
 
910
- await user.type(screen.getByLabelText(/email/i), "invalid-email");
911
- await user.tab();
124
+ debouncedFn();
125
+ expect(fn).not.toHaveBeenCalled();
912
126
 
913
- expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
127
+ vi.advanceTimersByTime(100);
128
+ expect(fn).toHaveBeenCalledTimes(1);
914
129
  });
915
130
  });
916
131
  ```
917
132
 
918
- ## Use Cases
919
-
920
- ### CI/CD Integration
921
-
922
- ```yaml
923
- # .github/workflows/test.yml
924
- name: Tests
925
-
926
- on: [push, pull_request]
927
-
928
- jobs:
929
- test:
930
- runs-on: ubuntu-latest
931
-
932
- steps:
933
- - uses: actions/checkout@v4
934
-
935
- - name: Setup Node.js
936
- uses: actions/setup-node@v4
937
- with:
938
- node-version: "20"
939
- cache: "npm"
940
-
941
- - name: Install dependencies
942
- run: npm ci
943
-
944
- - name: Run type check
945
- run: npm run type-check
946
-
947
- - name: Run unit tests
948
- run: npm run test:unit -- --coverage
949
-
950
- - name: Run component tests
951
- run: npm run test:components
952
-
953
- - name: Upload coverage
954
- uses: codecov/codecov-action@v4
955
- with:
956
- file: ./coverage/lcov.info
957
- ```
958
-
959
- ### Watch Mode Development
960
-
961
- ```json
962
- // package.json
963
- {
964
- "scripts": {
965
- "test": "vitest",
966
- "test:unit": "vitest run",
967
- "test:watch": "vitest watch",
968
- "test:ui": "vitest --ui",
969
- "test:coverage": "vitest run --coverage",
970
- "test:changed": "vitest --changed HEAD~1"
971
- }
972
- }
973
- ```
974
-
975
133
  ## Best Practices
976
134
 
977
- ### Do's
978
-
979
- - Use descriptive test names
980
- - Test behavior, not implementation
981
- - Keep tests focused and isolated
982
- - Use test.each for multiple similar cases
983
- - Mock external dependencies
984
- - Write tests alongside code
985
- - Use appropriate matchers
986
- - Group related tests with describe
987
- - Run tests in CI/CD pipeline
988
- - Maintain high coverage thresholds
989
-
990
- ### Don'ts
991
-
992
- - Don't test framework internals
993
- - Don't share state between tests
994
- - Don't use arbitrary timeouts
995
- - Don't over-mock
996
- - Don't ignore flaky tests
997
- - Don't test private methods directly
998
- - Don't duplicate coverage
999
- - Don't write tests after the fact only
1000
- - Don't skip snapshot updates without review
1001
- - Don't test third-party libraries
135
+ | Do | Avoid |
136
+ |----|-------|
137
+ | Use descriptive test names explaining behavior | Testing implementation details |
138
+ | Test behavior, not internal state | Sharing state between tests |
139
+ | Use test.each for multiple similar cases | Using arbitrary timeouts |
140
+ | Mock external dependencies | Over-mocking internal modules |
141
+ | Keep tests focused and isolated | Duplicating test coverage |
142
+ | Write tests alongside code | Ignoring flaky tests |
1002
143
 
1003
144
  ## References
1004
145
 
1005
146
  - [Vitest Documentation](https://vitest.dev/)
1006
147
  - [Vue Test Utils](https://test-utils.vuejs.org/)
1007
148
  - [Testing Library](https://testing-library.com/)
1008
- - [Vitest UI](https://vitest.dev/guide/ui.html)
1009
- - [Coverage Documentation](https://vitest.dev/guide/coverage.html)
149
+ - [Vitest Coverage](https://vitest.dev/guide/coverage.html)