tribunal-kit 3.0.0 → 3.1.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 (226) hide show
  1. package/.agent/ARCHITECTURE.md +99 -99
  2. package/.agent/GEMINI.md +52 -52
  3. package/.agent/agents/accessibility-reviewer.md +187 -220
  4. package/.agent/agents/ai-code-reviewer.md +199 -233
  5. package/.agent/agents/backend-specialist.md +215 -238
  6. package/.agent/agents/code-archaeologist.md +161 -181
  7. package/.agent/agents/database-architect.md +184 -207
  8. package/.agent/agents/debugger.md +191 -218
  9. package/.agent/agents/dependency-reviewer.md +103 -136
  10. package/.agent/agents/devops-engineer.md +218 -238
  11. package/.agent/agents/documentation-writer.md +201 -221
  12. package/.agent/agents/explorer-agent.md +160 -180
  13. package/.agent/agents/frontend-reviewer.md +160 -194
  14. package/.agent/agents/frontend-specialist.md +248 -237
  15. package/.agent/agents/game-developer.md +48 -52
  16. package/.agent/agents/logic-reviewer.md +116 -149
  17. package/.agent/agents/mobile-developer.md +200 -223
  18. package/.agent/agents/mobile-reviewer.md +162 -195
  19. package/.agent/agents/orchestrator.md +181 -211
  20. package/.agent/agents/penetration-tester.md +157 -174
  21. package/.agent/agents/performance-optimizer.md +183 -203
  22. package/.agent/agents/performance-reviewer.md +178 -211
  23. package/.agent/agents/product-manager.md +142 -162
  24. package/.agent/agents/product-owner.md +6 -25
  25. package/.agent/agents/project-planner.md +142 -162
  26. package/.agent/agents/qa-automation-engineer.md +225 -242
  27. package/.agent/agents/security-auditor.md +174 -194
  28. package/.agent/agents/seo-specialist.md +193 -213
  29. package/.agent/agents/sql-reviewer.md +161 -194
  30. package/.agent/agents/supervisor-agent.md +184 -203
  31. package/.agent/agents/swarm-worker-contracts.md +17 -17
  32. package/.agent/agents/swarm-worker-registry.md +46 -46
  33. package/.agent/agents/test-coverage-reviewer.md +160 -193
  34. package/.agent/agents/test-engineer.md +0 -21
  35. package/.agent/agents/type-safety-reviewer.md +175 -208
  36. package/.agent/patterns/generator.md +9 -9
  37. package/.agent/patterns/inversion.md +12 -12
  38. package/.agent/patterns/pipeline.md +9 -9
  39. package/.agent/patterns/reviewer.md +13 -13
  40. package/.agent/patterns/tool-wrapper.md +9 -9
  41. package/.agent/rules/GEMINI.md +63 -63
  42. package/.agent/scripts/compress_skills.py +167 -0
  43. package/.agent/scripts/consolidate_skills.py +173 -0
  44. package/.agent/scripts/deep_compress.py +202 -0
  45. package/.agent/scripts/minify_context.py +80 -0
  46. package/.agent/scripts/security_scan.py +1 -1
  47. package/.agent/scripts/strip_tribunal.py +41 -0
  48. package/.agent/skills/agent-organizer/SKILL.md +92 -126
  49. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  50. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +126 -160
  51. package/.agent/skills/api-patterns/SKILL.md +123 -215
  52. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  53. package/.agent/skills/app-builder/SKILL.md +326 -50
  54. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  55. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  56. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  57. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  58. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  59. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  60. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  61. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  62. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  63. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  64. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  65. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  66. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  67. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  68. package/.agent/skills/appflow-wireframe/SKILL.md +87 -121
  69. package/.agent/skills/architecture/SKILL.md +82 -252
  70. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  71. package/.agent/skills/bash-linux/SKILL.md +120 -154
  72. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  73. package/.agent/skills/brainstorming/SKILL.md +428 -104
  74. package/.agent/skills/building-native-ui/SKILL.md +143 -174
  75. package/.agent/skills/clean-code/SKILL.md +323 -360
  76. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  77. package/.agent/skills/config-validator/SKILL.md +107 -141
  78. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  79. package/.agent/skills/database-design/SKILL.md +104 -369
  80. package/.agent/skills/deployment-procedures/SKILL.md +111 -145
  81. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  82. package/.agent/skills/devops-incident-responder/SKILL.md +79 -113
  83. package/.agent/skills/doc.md +5 -5
  84. package/.agent/skills/documentation-templates/SKILL.md +19 -63
  85. package/.agent/skills/edge-computing/SKILL.md +123 -157
  86. package/.agent/skills/extract-design-system/SKILL.md +100 -134
  87. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  88. package/.agent/skills/frontend-design/SKILL.md +151 -499
  89. package/.agent/skills/game-design-expert/SKILL.md +71 -105
  90. package/.agent/skills/game-engineering-expert/SKILL.md +88 -122
  91. package/.agent/skills/geo-fundamentals/SKILL.md +89 -124
  92. package/.agent/skills/github-operations/SKILL.md +279 -314
  93. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  94. package/.agent/skills/i18n-localization/SKILL.md +104 -138
  95. package/.agent/skills/intelligent-routing/SKILL.md +159 -127
  96. package/.agent/skills/lint-and-validate/SKILL.md +8 -52
  97. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  98. package/.agent/skills/local-first/SKILL.md +120 -154
  99. package/.agent/skills/mcp-builder/SKILL.md +84 -118
  100. package/.agent/skills/mobile-design/SKILL.md +213 -219
  101. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  102. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  103. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  104. package/.agent/skills/observability/SKILL.md +293 -330
  105. package/.agent/skills/parallel-agents/SKILL.md +88 -122
  106. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  107. package/.agent/skills/plan-writing/SKILL.md +84 -118
  108. package/.agent/skills/platform-engineer/SKILL.md +89 -123
  109. package/.agent/skills/playwright-best-practices/SKILL.md +128 -162
  110. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  111. package/.agent/skills/python-patterns/SKILL.md +7 -35
  112. package/.agent/skills/python-pro/SKILL.md +148 -754
  113. package/.agent/skills/react-specialist/SKILL.md +123 -827
  114. package/.agent/skills/readme-builder/SKILL.md +15 -85
  115. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  116. package/.agent/skills/red-team-tactics/SKILL.md +10 -51
  117. package/.agent/skills/rust-pro/SKILL.md +623 -701
  118. package/.agent/skills/seo-fundamentals/SKILL.md +120 -154
  119. package/.agent/skills/server-management/SKILL.md +156 -190
  120. package/.agent/skills/shadcn-ui-expert/SKILL.md +172 -206
  121. package/.agent/skills/skill-creator/SKILL.md +18 -58
  122. package/.agent/skills/sql-pro/SKILL.md +579 -633
  123. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +28 -68
  124. package/.agent/skills/swiftui-expert/SKILL.md +142 -176
  125. package/.agent/skills/systematic-debugging/SKILL.md +84 -118
  126. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  127. package/.agent/skills/tdd-workflow/SKILL.md +103 -137
  128. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  129. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  130. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  131. package/.agent/skills/ui-ux-pro-max/SKILL.md +0 -41
  132. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  133. package/.agent/skills/vue-expert/SKILL.md +127 -866
  134. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  135. package/.agent/skills/web-accessibility-auditor/SKILL.md +159 -193
  136. package/.agent/skills/web-design-guidelines/SKILL.md +17 -61
  137. package/.agent/skills/webapp-testing/SKILL.md +111 -145
  138. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  139. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  140. package/.agent/workflows/api-tester.md +151 -151
  141. package/.agent/workflows/audit.md +127 -138
  142. package/.agent/workflows/brainstorm.md +110 -110
  143. package/.agent/workflows/changelog.md +112 -112
  144. package/.agent/workflows/create.md +124 -124
  145. package/.agent/workflows/debug.md +165 -189
  146. package/.agent/workflows/deploy.md +180 -189
  147. package/.agent/workflows/enhance.md +128 -151
  148. package/.agent/workflows/fix.md +114 -135
  149. package/.agent/workflows/generate.md +12 -4
  150. package/.agent/workflows/migrate.md +160 -160
  151. package/.agent/workflows/orchestrate.md +168 -168
  152. package/.agent/workflows/performance-benchmarker.md +114 -123
  153. package/.agent/workflows/plan.md +173 -173
  154. package/.agent/workflows/preview.md +80 -80
  155. package/.agent/workflows/refactor.md +161 -183
  156. package/.agent/workflows/review-ai.md +101 -129
  157. package/.agent/workflows/review.md +116 -116
  158. package/.agent/workflows/session.md +94 -94
  159. package/.agent/workflows/status.md +79 -79
  160. package/.agent/workflows/strengthen-skills.md +138 -139
  161. package/.agent/workflows/swarm.md +179 -179
  162. package/.agent/workflows/test.md +189 -211
  163. package/.agent/workflows/tribunal-backend.md +93 -113
  164. package/.agent/workflows/tribunal-database.md +94 -115
  165. package/.agent/workflows/tribunal-frontend.md +95 -118
  166. package/.agent/workflows/tribunal-full.md +92 -133
  167. package/.agent/workflows/tribunal-mobile.md +94 -119
  168. package/.agent/workflows/tribunal-performance.md +109 -133
  169. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  170. package/package.json +1 -1
  171. package/.agent/skills/api-patterns/api-style.md +0 -42
  172. package/.agent/skills/api-patterns/auth.md +0 -24
  173. package/.agent/skills/api-patterns/documentation.md +0 -26
  174. package/.agent/skills/api-patterns/graphql.md +0 -41
  175. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  176. package/.agent/skills/api-patterns/response.md +0 -37
  177. package/.agent/skills/api-patterns/rest.md +0 -40
  178. package/.agent/skills/api-patterns/security-testing.md +0 -122
  179. package/.agent/skills/api-patterns/trpc.md +0 -41
  180. package/.agent/skills/api-patterns/versioning.md +0 -22
  181. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  182. package/.agent/skills/app-builder/feature-building.md +0 -53
  183. package/.agent/skills/app-builder/project-detection.md +0 -34
  184. package/.agent/skills/app-builder/scaffolding.md +0 -118
  185. package/.agent/skills/app-builder/tech-stack.md +0 -40
  186. package/.agent/skills/architecture/context-discovery.md +0 -43
  187. package/.agent/skills/architecture/examples.md +0 -94
  188. package/.agent/skills/architecture/pattern-selection.md +0 -68
  189. package/.agent/skills/architecture/patterns-reference.md +0 -50
  190. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  191. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  192. package/.agent/skills/database-design/database-selection.md +0 -43
  193. package/.agent/skills/database-design/indexing.md +0 -39
  194. package/.agent/skills/database-design/migrations.md +0 -48
  195. package/.agent/skills/database-design/optimization.md +0 -36
  196. package/.agent/skills/database-design/orm-selection.md +0 -30
  197. package/.agent/skills/database-design/schema-design.md +0 -56
  198. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  199. package/.agent/skills/frontend-design/color-system.md +0 -329
  200. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  201. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  202. package/.agent/skills/frontend-design/typography-system.md +0 -363
  203. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  204. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  205. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  206. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  207. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  208. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  209. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  210. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  211. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  212. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  213. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  214. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  215. package/.agent/skills/mobile-design/platform-android.md +0 -666
  216. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  217. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  218. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  219. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  220. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  221. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  222. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  223. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  224. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  225. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  226. package/.agent/skills/vulnerability-scanner/checklists.md +0 -121
@@ -1,573 +1,512 @@
1
- ---
2
- name: testing-patterns
3
- description: Testing mastery across stacks. Unit testing with Jest/Vitest/pytest, integration testing, E2E with Playwright, mocking strategies, test architecture (AAA, Given-When-Then), code coverage, snapshot testing, API testing, component testing with Testing Library, and TDD workflow. Use when writing tests, designing test architecture, or improving test coverage.
4
- allowed-tools: Read, Write, Edit, Glob, Grep
5
- version: 2.0.0
6
- last-updated: 2026-04-01
7
- applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
8
- ---
9
-
10
- # Testing Patterns — Cross-Stack Testing Mastery
11
-
12
- > A test that doesn't fail when the code is wrong is worse than no test at all.
13
- > Every test must answer: "What behavior am I proving? What would break if I deleted this test?"
14
-
15
- ---
16
-
17
- ## Test Architecture
18
-
19
- ### The Testing Pyramid
20
-
21
- ```
22
- / E2E \ ← Few: critical user flows (Playwright/Cypress)
23
- /──────────\
24
- / Integration \ ← Moderate: API routes, DB queries, component integration
25
- /──────────────\
26
- / Unit Tests \ ← Many: pure functions, hooks, utilities, business logic
27
- /──────────────────\
28
-
29
- Rules:
30
- - 70% unit, 20% integration, 10% E2E
31
- - Unit tests: < 50ms each
32
- - Integration tests: < 2s each
33
- - E2E tests: < 30s each
34
- - If a test takes > 5s, it's a design problem
35
- ```
36
-
37
- ### AAA Pattern (Arrange-Act-Assert)
38
-
39
- ```typescript
40
- // Every test follows the same structure
41
- it("calculates total with tax", () => {
42
- // Arrange — set up the scenario
43
- const cart = new Cart();
44
- cart.addItem({ name: "Widget", price: 100 });
45
- cart.setTaxRate(0.08);
46
-
47
- // Actperform the action being tested
48
- const total = cart.calculateTotal();
49
-
50
- // Assert — verify the result
51
- expect(total).toBe(108);
52
- });
53
-
54
- // ❌ BAD: Multiple acts in one test
55
- it("does too many things", () => {
56
- cart.addItem({ name: "A", price: 10 });
57
- expect(cart.total).toBe(10); // assert
58
- cart.addItem({ name: "B", price: 20 });
59
- expect(cart.total).toBe(30); // another assert after another act
60
- cart.removeItem("A");
61
- expect(cart.total).toBe(20); // yet another — split into 3 tests
62
- });
63
- ```
64
-
65
- ### Test Naming Convention
66
-
67
- ```typescript
68
- // Format: [unit] + [scenario] + [expected result]
69
-
70
- // GOOD: Descriptive, reads like a specification
71
- describe("calculateDiscount", () => {
72
- it("returns 0% when cart total is under $50", () => {});
73
- it("returns 10% when cart total is $50-$99", () => {});
74
- it("returns 20% when cart total is $100+", () => {});
75
- it("throws when cart is empty", () => {});
76
- });
77
-
78
- // BAD: Vague, implementation-focused
79
- describe("calculateDiscount", () => {
80
- it("works", () => {});
81
- it("test1", () => {});
82
- it("should return correct value", () => {});
83
- });
84
- ```
85
-
86
- ---
87
-
88
- ## Unit Testing (Vitest / Jest)
89
-
90
- ### Pure Function Testing
91
-
92
- ```typescript
93
- // utils/math.ts
94
- export function clamp(value: number, min: number, max: number): number {
95
- return Math.min(Math.max(value, min), max);
96
- }
97
-
98
- // utils/math.test.ts
99
- import { describe, it, expect } from "vitest";
100
- import { clamp } from "./math";
101
-
102
- describe("clamp", () => {
103
- it("returns the value when within range", () => {
104
- expect(clamp(5, 0, 10)).toBe(5);
105
- });
106
-
107
- it("clamps to min when value is below range", () => {
108
- expect(clamp(-5, 0, 10)).toBe(0);
109
- });
110
-
111
- it("clamps to max when value is above range", () => {
112
- expect(clamp(15, 0, 10)).toBe(10);
113
- });
114
-
115
- it("handles equal min and max", () => {
116
- expect(clamp(5, 3, 3)).toBe(3);
117
- });
118
-
119
- it("handles floating point values", () => {
120
- expect(clamp(0.5, 0, 1)).toBeCloseTo(0.5);
121
- });
122
- });
123
- ```
124
-
125
- ### Async Testing
126
-
127
- ```typescript
128
- import { describe, it, expect, vi } from "vitest";
129
-
130
- // Async function under test
131
- async function fetchUser(id: string): Promise<User> {
132
- const response = await fetch(`/api/users/${id}`);
133
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
134
- return response.json();
135
- }
136
-
137
- describe("fetchUser", () => {
138
- it("returns user data on success", async () => {
139
- const mockUser = { id: "1", name: "Alice" };
140
- global.fetch = vi.fn().mockResolvedValue({
141
- ok: true,
142
- json: () => Promise.resolve(mockUser),
143
- });
144
-
145
- const user = await fetchUser("1");
146
- expect(user).toEqual(mockUser);
147
- expect(fetch).toHaveBeenCalledWith("/api/users/1");
148
- });
149
-
150
- it("throws on HTTP error", async () => {
151
- global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 404 });
152
-
153
- await expect(fetchUser("999")).rejects.toThrow("HTTP 404");
154
- });
155
- });
156
- ```
157
-
158
- ### Timer & Date Mocking
159
-
160
- ```typescript
161
- describe("debounce", () => {
162
- beforeEach(() => {
163
- vi.useFakeTimers();
164
- });
165
-
166
- afterEach(() => {
167
- vi.useRealTimers();
168
- });
169
-
170
- it("delays execution by specified ms", () => {
171
- const fn = vi.fn();
172
- const debounced = debounce(fn, 300);
173
-
174
- debounced();
175
- expect(fn).not.toHaveBeenCalled(); // not yet
176
-
177
- vi.advanceTimersByTime(200);
178
- expect(fn).not.toHaveBeenCalled(); // still not
179
-
180
- vi.advanceTimersByTime(100);
181
- expect(fn).toHaveBeenCalledOnce(); // now
182
- });
183
-
184
- it("resets timer on subsequent calls", () => {
185
- const fn = vi.fn();
186
- const debounced = debounce(fn, 300);
187
-
188
- debounced();
189
- vi.advanceTimersByTime(200);
190
- debounced(); // reset timer
191
- vi.advanceTimersByTime(200);
192
- expect(fn).not.toHaveBeenCalled(); // timer was reset
193
-
194
- vi.advanceTimersByTime(100);
195
- expect(fn).toHaveBeenCalledOnce();
196
- });
197
- });
198
-
199
- // Date mocking
200
- it("formats today's date", () => {
201
- vi.setSystemTime(new Date("2024-06-15T12:00:00Z"));
202
- expect(getFormattedDate()).toBe("June 15, 2024");
203
- vi.useRealTimers();
204
- });
205
- ```
206
-
207
- ---
208
-
209
- ## Mocking Strategies
210
-
211
- ### Module Mocks
212
-
213
- ```typescript
214
- import { vi, describe, it, expect, beforeEach } from "vitest";
215
- import { sendEmail } from "./email-service";
216
- import { createUser } from "./user-service";
217
-
218
- // Mock an entire module
219
- vi.mock("./email-service", () => ({
220
- sendEmail: vi.fn().mockResolvedValue({ sent: true }),
221
- }));
222
-
223
- describe("createUser", () => {
224
- beforeEach(() => {
225
- vi.clearAllMocks(); // reset call counts between tests
226
- });
227
-
228
- it("sends welcome email after creating user", async () => {
229
- await createUser({ name: "Alice", email: "alice@test.com" });
230
-
231
- expect(sendEmail).toHaveBeenCalledWith({
232
- to: "alice@test.com",
233
- subject: "Welcome!",
234
- body: expect.stringContaining("Alice"),
235
- });
236
- });
237
-
238
- it("does not send email on validation failure", async () => {
239
- await expect(createUser({ name: "", email: "" })).rejects.toThrow();
240
- expect(sendEmail).not.toHaveBeenCalled();
241
- });
242
- });
243
- ```
244
-
245
- ### Spy Pattern
246
-
247
- ```typescript
248
- // Spy on an existing method (don't replace it — observe it)
249
- const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
250
-
251
- await riskyOperation();
252
-
253
- expect(consoleSpy).toHaveBeenCalledWith(
254
- expect.stringContaining("failed"),
255
- expect.any(Error)
256
- );
257
-
258
- consoleSpy.mockRestore(); // restore original
259
- ```
260
-
261
- ### Dependency Injection Pattern (Testable by Design)
262
-
263
- ```typescript
264
- // BAD: Hard-coded dependency — untestable without module mocking
265
- class UserService {
266
- async getUser(id: string) {
267
- return await fetch(`/api/users/${id}`).then((r) => r.json());
268
- }
269
- }
270
-
271
- // ✅ GOOD: Injected dependency — naturally testable
272
- interface HttpClient {
273
- get<T>(url: string): Promise<T>;
274
- }
275
-
276
- class UserService {
277
- constructor(private http: HttpClient) {}
278
-
279
- async getUser(id: string): Promise<User> {
280
- return this.http.get<User>(`/api/users/${id}`);
281
- }
282
- }
283
-
284
- // In test:
285
- const mockHttp: HttpClient = {
286
- get: vi.fn().mockResolvedValue({ id: "1", name: "Alice" }),
287
- };
288
- const service = new UserService(mockHttp);
289
-
290
- // ❌ HALLUCINATION TRAP: Prefer dependency injection over vi.mock()
291
- // vi.mock() is global and can leak between tests
292
- // DI makes tests isolated and explicit
293
- ```
294
-
295
- ---
296
-
297
- ## React Component Testing (Testing Library)
298
-
299
- ```tsx
300
- import { render, screen, waitFor } from "@testing-library/react";
301
- import userEvent from "@testing-library/user-event";
302
- import { describe, it, expect, vi } from "vitest";
303
- import { LoginForm } from "./LoginForm";
304
-
305
- describe("LoginForm", () => {
306
- it("renders email and password fields", () => {
307
- render(<LoginForm onSubmit={vi.fn()} />);
308
-
309
- expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
310
- expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
311
- expect(screen.getByRole("button", { name: /sign in/i })).toBeInTheDocument();
312
- });
313
-
314
- it("calls onSubmit with credentials", async () => {
315
- const user = userEvent.setup();
316
- const onSubmit = vi.fn();
317
- render(<LoginForm onSubmit={onSubmit} />);
318
-
319
- await user.type(screen.getByLabelText(/email/i), "alice@test.com");
320
- await user.type(screen.getByLabelText(/password/i), "secret123");
321
- await user.click(screen.getByRole("button", { name: /sign in/i }));
322
-
323
- expect(onSubmit).toHaveBeenCalledWith({
324
- email: "alice@test.com",
325
- password: "secret123",
326
- });
327
- });
328
-
329
- it("shows validation error for invalid email", async () => {
330
- const user = userEvent.setup();
331
- render(<LoginForm onSubmit={vi.fn()} />);
332
-
333
- await user.type(screen.getByLabelText(/email/i), "not-an-email");
334
- await user.click(screen.getByRole("button", { name: /sign in/i }));
335
-
336
- expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
337
- });
338
-
339
- it("disables submit button while loading", async () => {
340
- render(<LoginForm onSubmit={vi.fn()} isLoading={true} />);
341
-
342
- expect(screen.getByRole("button", { name: /sign in/i })).toBeDisabled();
343
- });
344
- });
345
-
346
- // HALLUCINATION TRAP: Query priorities (use in this order):
347
- // 1. getByRoleaccessible role ("button", "textbox", etc.)
348
- // 2. getByLabelTextform inputs with labels
349
- // 3. getByPlaceholderTextwhen no label exists
350
- // 4. getByText — non-interactive elements
351
- // 5. getByTestId — LAST RESORT only
352
- // ❌ Never default to getByTestId — it tests implementation, not behavior
353
- ```
354
-
355
- ---
356
-
357
- ## E2E Testing (Playwright)
358
-
359
- ```typescript
360
- import { test, expect } from "@playwright/test";
361
-
362
- test.describe("Login Flow", () => {
363
- test("successful login redirects to dashboard", async ({ page }) => {
364
- await page.goto("/login");
365
-
366
- await page.getByLabel("Email").fill("admin@test.com");
367
- await page.getByLabel("Password").fill("password123");
368
- await page.getByRole("button", { name: "Sign In" }).click();
369
-
370
- // Wait for navigation
371
- await expect(page).toHaveURL("/dashboard");
372
- await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
373
- });
374
-
375
- test("shows error for invalid credentials", async ({ page }) => {
376
- await page.goto("/login");
377
-
378
- await page.getByLabel("Email").fill("wrong@test.com");
379
- await page.getByLabel("Password").fill("wrongpassword");
380
- await page.getByRole("button", { name: "Sign In" }).click();
381
-
382
- await expect(page.getByText("Invalid credentials")).toBeVisible();
383
- await expect(page).toHaveURL("/login"); // no redirect
384
- });
385
-
386
- test("responsive: mobile menu toggles", async ({ page, isMobile }) => {
387
- test.skip(!isMobile, "Mobile only");
388
-
389
- await page.goto("/");
390
- await page.getByRole("button", { name: "Menu" }).click();
391
- await expect(page.getByRole("navigation")).toBeVisible();
392
- });
393
- });
394
-
395
- // API testing with Playwright
396
- test("API: create user returns 201", async ({ request }) => {
397
- const response = await request.post("/api/users", {
398
- data: { name: "Alice", email: "alice@test.com" },
399
- });
400
-
401
- expect(response.status()).toBe(201);
402
- const body = await response.json();
403
- expect(body).toMatchObject({ name: "Alice", email: "alice@test.com" });
404
- });
405
- ```
406
-
407
- ### Playwright Config
408
-
409
- ```typescript
410
- // playwright.config.ts
411
- import { defineConfig } from "@playwright/test";
412
-
413
- export default defineConfig({
414
- testDir: "./e2e",
415
- timeout: 30000,
416
- retries: process.env.CI ? 2 : 0, // retry in CI only
417
- use: {
418
- baseURL: "http://localhost:3000",
419
- trace: "on-first-retry", // save trace on failures
420
- screenshot: "only-on-failure",
421
- },
422
- webServer: {
423
- command: "npm run dev",
424
- port: 3000,
425
- reuseExistingServer: !process.env.CI,
426
- },
427
- projects: [
428
- { name: "chrome", use: { browserName: "chromium" } },
429
- { name: "firefox", use: { browserName: "firefox" } },
430
- { name: "mobile", use: { ...devices["iPhone 14"] } },
431
- ],
432
- });
433
- ```
434
-
435
- ---
436
-
437
- ## API Testing
438
-
439
- ```typescript
440
- // Testing REST APIs with supertest (Express/Fastify)
441
- import request from "supertest";
442
- import { app } from "./app";
443
-
444
- describe("POST /api/users", () => {
445
- it("creates a user and returns 201", async () => {
446
- const response = await request(app)
447
- .post("/api/users")
448
- .send({ name: "Alice", email: "alice@test.com" })
449
- .expect(201)
450
- .expect("Content-Type", /json/);
451
-
452
- expect(response.body).toMatchObject({
453
- id: expect.any(Number),
454
- name: "Alice",
455
- email: "alice@test.com",
456
- });
457
- });
458
-
459
- it("returns 400 for missing required fields", async () => {
460
- await request(app)
461
- .post("/api/users")
462
- .send({ name: "" })
463
- .expect(400);
464
- });
465
-
466
- it("returns 409 for duplicate email", async () => {
467
- await request(app)
468
- .post("/api/users")
469
- .send({ name: "Alice", email: "existing@test.com" })
470
- .expect(409);
471
- });
472
- });
473
- ```
474
-
475
- ---
476
-
477
- ## Code Coverage
478
-
479
- ```jsonc
480
- // vitest.config.ts
481
- export default defineConfig({
482
- test: {
483
- coverage: {
484
- provider: "v8",
485
- reporter: ["text", "lcov", "html"],
486
- thresholds: {
487
- lines: 80,
488
- functions: 80,
489
- branches: 75,
490
- statements: 80,
491
- },
492
- exclude: [
493
- "**/*.test.ts",
494
- "**/*.spec.ts",
495
- "**/types/**",
496
- "**/mocks/**",
497
- ],
498
- },
499
- },
500
- });
501
-
502
- // Run: npx vitest --coverage
503
- ```
504
-
505
- ```
506
- Coverage rules:
507
- - 80% is the practical threshold (not 100%)
508
- - 100% coverage 100% confidence
509
- - Cover edge cases and error paths, not just happy paths
510
- - Avoid testing implementation details (private methods, internal state)
511
- - Focus coverage on: business logic, data transformations, auth/security
512
- - Skip coverage on: config files, types-only files, generated code
513
- ```
514
-
515
- ---
516
-
517
- ## Output Format
518
-
519
- ```
520
- ━━━ Testing Report ━━━━━━━━━━━━━━━━━━━━━━━━
521
- Skill: Testing Patterns
522
- Framework: [Vitest/Jest/pytest/Playwright]
523
- Scope: [N test files · N test cases]
524
- ─────────────────────────────────────────────────
525
- ✅ Passed: [N/N tests passing]
526
- ⚠️ Warnings: [flaky tests, slow tests]
527
- ❌ Blocked: [failing tests]
528
- ─────────────────────────────────────────────────
529
- Coverage: [lines/branches/functions %]
530
- VBC status: PENDING → VERIFIED
531
- Evidence: [test runner output]
532
- ```
533
-
534
- ---
535
-
536
- ## 🤖 LLM-Specific Traps
537
-
538
- 1. **`getByTestId` as Default:** Testing Library queries should use roles and labels first. `getByTestId` is a last resort.
539
- 2. **Testing Implementation, Not Behavior:** Don't test internal state, private methods, or CSS classes. Test what the user sees and does.
540
- 3. **Missing `userEvent.setup()`:** Always call `userEvent.setup()` before interactions. Direct `userEvent.click()` is deprecated.
541
- 4. **`vi.mock()` Without `vi.clearAllMocks()`:** Mock state leaks between tests without clearing. Always call in `beforeEach`.
542
- 5. **Async Tests Without `await`:** Forgetting `await` in async test assertions silently passes. Always `await expect(fn()).rejects.toThrow()`.
543
- 6. **Snapshot Test Overuse:** Snapshot tests are brittle and low-value for UI. Use them for serialized data structures, not rendered components.
544
- 7. **Missing Error Path Tests:** Only testing the happy path. Every function that can fail needs at least one error test.
545
- 8. **Hardcoded IDs/Dates:** Tests relying on specific database IDs or timestamps are flaky. Use factories and relative assertions.
546
- 9. **`--coverage` Without Thresholds:** Coverage reports without enforced thresholds are useless. Set minimum thresholds in config.
547
- 10. **E2E Tests That Test Implementation:** E2E tests should simulate real user flows, not click through internal implementation details.
548
-
549
- ---
550
-
551
- ## 🏛️ Tribunal Integration
552
-
553
- **Slash command: `/test`**
554
-
555
- ### ✅ Pre-Flight Self-Audit
556
-
557
- ```
558
- ✅ Does every test follow AAA (Arrange-Act-Assert)?
559
- ✅ Are test names descriptive (unit + scenario + expected)?
560
- ✅ Did I test both happy path AND error cases?
561
- ✅ Am I using Testing Library query priorities correctly?
562
- ✅ Did I use userEvent.setup() for interactions?
563
- ✅ Did I clear mocks in beforeEach?
564
- ✅ Are async assertions properly awaited?
565
- ✅ Is coverage threshold enforced in config?
566
- ✅ Are E2E tests testing user behavior (not implementation)?
567
- ✅ Do all tests pass independently (no shared state)?
568
- ```
569
-
570
- ### 🛑 VBC Protocol
571
-
572
- - ❌ **Forbidden:** Writing tests that aren't run.
573
- - ✅ **Required:** Provide test runner output showing pass/fail counts.
1
+ ---
2
+ name: testing-patterns
3
+ description: Testing mastery across stacks. Unit testing with Jest/Vitest/pytest, integration testing, E2E with Playwright, mocking strategies, test architecture (AAA, Given-When-Then), code coverage, snapshot testing, API testing, component testing with Testing Library, and TDD workflow. Use when writing tests, designing test architecture, or improving test coverage.
4
+ allowed-tools: Read, Write, Edit, Glob, Grep
5
+ version: 2.0.0
6
+ last-updated: 2026-04-01
7
+ applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
8
+ ---
9
+
10
+ # Testing Patterns — Cross-Stack Testing Mastery
11
+
12
+ ---
13
+
14
+ ## Test Architecture
15
+
16
+ ### The Testing Pyramid
17
+
18
+ ```
19
+ / E2E \ ← Few: critical user flows (Playwright/Cypress)
20
+ /──────────\
21
+ / Integration \ ← Moderate: API routes, DB queries, component integration
22
+ /──────────────\
23
+ / Unit Tests \ ← Many: pure functions, hooks, utilities, business logic
24
+ /──────────────────\
25
+
26
+ Rules:
27
+ - 70% unit, 20% integration, 10% E2E
28
+ - Unit tests: < 50ms each
29
+ - Integration tests: < 2s each
30
+ - E2E tests: < 30s each
31
+ - If a test takes > 5s, it's a design problem
32
+ ```
33
+
34
+ ### AAA Pattern (Arrange-Act-Assert)
35
+
36
+ ```typescript
37
+ // Every test follows the same structure
38
+ it("calculates total with tax", () => {
39
+ // Arrange — set up the scenario
40
+ const cart = new Cart();
41
+ cart.addItem({ name: "Widget", price: 100 });
42
+ cart.setTaxRate(0.08);
43
+
44
+ // Act perform the action being tested
45
+ const total = cart.calculateTotal();
46
+
47
+ // Assertverify the result
48
+ expect(total).toBe(108);
49
+ });
50
+
51
+ // ❌ BAD: Multiple acts in one test
52
+ it("does too many things", () => {
53
+ cart.addItem({ name: "A", price: 10 });
54
+ expect(cart.total).toBe(10); // assert
55
+ cart.addItem({ name: "B", price: 20 });
56
+ expect(cart.total).toBe(30); // another assert after another act
57
+ cart.removeItem("A");
58
+ expect(cart.total).toBe(20); // yet another split into 3 tests
59
+ });
60
+ ```
61
+
62
+ ### Test Naming Convention
63
+
64
+ ```typescript
65
+ // Format: [unit] + [scenario] + [expected result]
66
+
67
+ // ✅ GOOD: Descriptive, reads like a specification
68
+ describe("calculateDiscount", () => {
69
+ it("returns 0% when cart total is under $50", () => {});
70
+ it("returns 10% when cart total is $50-$99", () => {});
71
+ it("returns 20% when cart total is $100+", () => {});
72
+ it("throws when cart is empty", () => {});
73
+ });
74
+
75
+ // BAD: Vague, implementation-focused
76
+ describe("calculateDiscount", () => {
77
+ it("works", () => {});
78
+ it("test1", () => {});
79
+ it("should return correct value", () => {});
80
+ });
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Unit Testing (Vitest / Jest)
86
+
87
+ ### Pure Function Testing
88
+
89
+ ```typescript
90
+ // utils/math.ts
91
+ export function clamp(value: number, min: number, max: number): number {
92
+ return Math.min(Math.max(value, min), max);
93
+ }
94
+
95
+ // utils/math.test.ts
96
+ import { describe, it, expect } from "vitest";
97
+ import { clamp } from "./math";
98
+
99
+ describe("clamp", () => {
100
+ it("returns the value when within range", () => {
101
+ expect(clamp(5, 0, 10)).toBe(5);
102
+ });
103
+
104
+ it("clamps to min when value is below range", () => {
105
+ expect(clamp(-5, 0, 10)).toBe(0);
106
+ });
107
+
108
+ it("clamps to max when value is above range", () => {
109
+ expect(clamp(15, 0, 10)).toBe(10);
110
+ });
111
+
112
+ it("handles equal min and max", () => {
113
+ expect(clamp(5, 3, 3)).toBe(3);
114
+ });
115
+
116
+ it("handles floating point values", () => {
117
+ expect(clamp(0.5, 0, 1)).toBeCloseTo(0.5);
118
+ });
119
+ });
120
+ ```
121
+
122
+ ### Async Testing
123
+
124
+ ```typescript
125
+ import { describe, it, expect, vi } from "vitest";
126
+
127
+ // Async function under test
128
+ async function fetchUser(id: string): Promise<User> {
129
+ const response = await fetch(`/api/users/${id}`);
130
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
131
+ return response.json();
132
+ }
133
+
134
+ describe("fetchUser", () => {
135
+ it("returns user data on success", async () => {
136
+ const mockUser = { id: "1", name: "Alice" };
137
+ global.fetch = vi.fn().mockResolvedValue({
138
+ ok: true,
139
+ json: () => Promise.resolve(mockUser),
140
+ });
141
+
142
+ const user = await fetchUser("1");
143
+ expect(user).toEqual(mockUser);
144
+ expect(fetch).toHaveBeenCalledWith("/api/users/1");
145
+ });
146
+
147
+ it("throws on HTTP error", async () => {
148
+ global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 404 });
149
+
150
+ await expect(fetchUser("999")).rejects.toThrow("HTTP 404");
151
+ });
152
+ });
153
+ ```
154
+
155
+ ### Timer & Date Mocking
156
+
157
+ ```typescript
158
+ describe("debounce", () => {
159
+ beforeEach(() => {
160
+ vi.useFakeTimers();
161
+ });
162
+
163
+ afterEach(() => {
164
+ vi.useRealTimers();
165
+ });
166
+
167
+ it("delays execution by specified ms", () => {
168
+ const fn = vi.fn();
169
+ const debounced = debounce(fn, 300);
170
+
171
+ debounced();
172
+ expect(fn).not.toHaveBeenCalled(); // not yet
173
+
174
+ vi.advanceTimersByTime(200);
175
+ expect(fn).not.toHaveBeenCalled(); // still not
176
+
177
+ vi.advanceTimersByTime(100);
178
+ expect(fn).toHaveBeenCalledOnce(); // now
179
+ });
180
+
181
+ it("resets timer on subsequent calls", () => {
182
+ const fn = vi.fn();
183
+ const debounced = debounce(fn, 300);
184
+
185
+ debounced();
186
+ vi.advanceTimersByTime(200);
187
+ debounced(); // reset timer
188
+ vi.advanceTimersByTime(200);
189
+ expect(fn).not.toHaveBeenCalled(); // timer was reset
190
+
191
+ vi.advanceTimersByTime(100);
192
+ expect(fn).toHaveBeenCalledOnce();
193
+ });
194
+ });
195
+
196
+ // Date mocking
197
+ it("formats today's date", () => {
198
+ vi.setSystemTime(new Date("2024-06-15T12:00:00Z"));
199
+ expect(getFormattedDate()).toBe("June 15, 2024");
200
+ vi.useRealTimers();
201
+ });
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Mocking Strategies
207
+
208
+ ### Module Mocks
209
+
210
+ ```typescript
211
+ import { vi, describe, it, expect, beforeEach } from "vitest";
212
+ import { sendEmail } from "./email-service";
213
+ import { createUser } from "./user-service";
214
+
215
+ // Mock an entire module
216
+ vi.mock("./email-service", () => ({
217
+ sendEmail: vi.fn().mockResolvedValue({ sent: true }),
218
+ }));
219
+
220
+ describe("createUser", () => {
221
+ beforeEach(() => {
222
+ vi.clearAllMocks(); // reset call counts between tests
223
+ });
224
+
225
+ it("sends welcome email after creating user", async () => {
226
+ await createUser({ name: "Alice", email: "alice@test.com" });
227
+
228
+ expect(sendEmail).toHaveBeenCalledWith({
229
+ to: "alice@test.com",
230
+ subject: "Welcome!",
231
+ body: expect.stringContaining("Alice"),
232
+ });
233
+ });
234
+
235
+ it("does not send email on validation failure", async () => {
236
+ await expect(createUser({ name: "", email: "" })).rejects.toThrow();
237
+ expect(sendEmail).not.toHaveBeenCalled();
238
+ });
239
+ });
240
+ ```
241
+
242
+ ### Spy Pattern
243
+
244
+ ```typescript
245
+ // Spy on an existing method (don't replace it — observe it)
246
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
247
+
248
+ await riskyOperation();
249
+
250
+ expect(consoleSpy).toHaveBeenCalledWith(
251
+ expect.stringContaining("failed"),
252
+ expect.any(Error)
253
+ );
254
+
255
+ consoleSpy.mockRestore(); // restore original
256
+ ```
257
+
258
+ ### Dependency Injection Pattern (Testable by Design)
259
+
260
+ ```typescript
261
+ // BAD: Hard-coded dependency untestable without module mocking
262
+ class UserService {
263
+ async getUser(id: string) {
264
+ return await fetch(`/api/users/${id}`).then((r) => r.json());
265
+ }
266
+ }
267
+
268
+ // ✅ GOOD: Injected dependency — naturally testable
269
+ interface HttpClient {
270
+ get<T>(url: string): Promise<T>;
271
+ }
272
+
273
+ class UserService {
274
+ constructor(private http: HttpClient) {}
275
+
276
+ async getUser(id: string): Promise<User> {
277
+ return this.http.get<User>(`/api/users/${id}`);
278
+ }
279
+ }
280
+
281
+ // In test:
282
+ const mockHttp: HttpClient = {
283
+ get: vi.fn().mockResolvedValue({ id: "1", name: "Alice" }),
284
+ };
285
+ const service = new UserService(mockHttp);
286
+
287
+ // ❌ HALLUCINATION TRAP: Prefer dependency injection over vi.mock()
288
+ // vi.mock() is global and can leak between tests
289
+ // DI makes tests isolated and explicit
290
+ ```
291
+
292
+ ---
293
+
294
+ ## React Component Testing (Testing Library)
295
+
296
+ ```tsx
297
+ import { render, screen, waitFor } from "@testing-library/react";
298
+ import userEvent from "@testing-library/user-event";
299
+ import { describe, it, expect, vi } from "vitest";
300
+ import { LoginForm } from "./LoginForm";
301
+
302
+ describe("LoginForm", () => {
303
+ it("renders email and password fields", () => {
304
+ render(<LoginForm onSubmit={vi.fn()} />);
305
+
306
+ expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
307
+ expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
308
+ expect(screen.getByRole("button", { name: /sign in/i })).toBeInTheDocument();
309
+ });
310
+
311
+ it("calls onSubmit with credentials", async () => {
312
+ const user = userEvent.setup();
313
+ const onSubmit = vi.fn();
314
+ render(<LoginForm onSubmit={onSubmit} />);
315
+
316
+ await user.type(screen.getByLabelText(/email/i), "alice@test.com");
317
+ await user.type(screen.getByLabelText(/password/i), "secret123");
318
+ await user.click(screen.getByRole("button", { name: /sign in/i }));
319
+
320
+ expect(onSubmit).toHaveBeenCalledWith({
321
+ email: "alice@test.com",
322
+ password: "secret123",
323
+ });
324
+ });
325
+
326
+ it("shows validation error for invalid email", async () => {
327
+ const user = userEvent.setup();
328
+ render(<LoginForm onSubmit={vi.fn()} />);
329
+
330
+ await user.type(screen.getByLabelText(/email/i), "not-an-email");
331
+ await user.click(screen.getByRole("button", { name: /sign in/i }));
332
+
333
+ expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
334
+ });
335
+
336
+ it("disables submit button while loading", async () => {
337
+ render(<LoginForm onSubmit={vi.fn()} isLoading={true} />);
338
+
339
+ expect(screen.getByRole("button", { name: /sign in/i })).toBeDisabled();
340
+ });
341
+ });
342
+
343
+ // ❌ HALLUCINATION TRAP: Query priorities (use in this order):
344
+ // 1. getByRole — accessible role ("button", "textbox", etc.)
345
+ // 2. getByLabelText — form inputs with labels
346
+ // 3. getByPlaceholderText when no label exists
347
+ // 4. getByTextnon-interactive elements
348
+ // 5. getByTestIdLAST RESORT only
349
+ // Never default to getByTestId it tests implementation, not behavior
350
+ ```
351
+
352
+ ---
353
+
354
+ ## E2E Testing (Playwright)
355
+
356
+ ```typescript
357
+ import { test, expect } from "@playwright/test";
358
+
359
+ test.describe("Login Flow", () => {
360
+ test("successful login redirects to dashboard", async ({ page }) => {
361
+ await page.goto("/login");
362
+
363
+ await page.getByLabel("Email").fill("admin@test.com");
364
+ await page.getByLabel("Password").fill("password123");
365
+ await page.getByRole("button", { name: "Sign In" }).click();
366
+
367
+ // Wait for navigation
368
+ await expect(page).toHaveURL("/dashboard");
369
+ await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
370
+ });
371
+
372
+ test("shows error for invalid credentials", async ({ page }) => {
373
+ await page.goto("/login");
374
+
375
+ await page.getByLabel("Email").fill("wrong@test.com");
376
+ await page.getByLabel("Password").fill("wrongpassword");
377
+ await page.getByRole("button", { name: "Sign In" }).click();
378
+
379
+ await expect(page.getByText("Invalid credentials")).toBeVisible();
380
+ await expect(page).toHaveURL("/login"); // no redirect
381
+ });
382
+
383
+ test("responsive: mobile menu toggles", async ({ page, isMobile }) => {
384
+ test.skip(!isMobile, "Mobile only");
385
+
386
+ await page.goto("/");
387
+ await page.getByRole("button", { name: "Menu" }).click();
388
+ await expect(page.getByRole("navigation")).toBeVisible();
389
+ });
390
+ });
391
+
392
+ // API testing with Playwright
393
+ test("API: create user returns 201", async ({ request }) => {
394
+ const response = await request.post("/api/users", {
395
+ data: { name: "Alice", email: "alice@test.com" },
396
+ });
397
+
398
+ expect(response.status()).toBe(201);
399
+ const body = await response.json();
400
+ expect(body).toMatchObject({ name: "Alice", email: "alice@test.com" });
401
+ });
402
+ ```
403
+
404
+ ### Playwright Config
405
+
406
+ ```typescript
407
+ // playwright.config.ts
408
+ import { defineConfig } from "@playwright/test";
409
+
410
+ export default defineConfig({
411
+ testDir: "./e2e",
412
+ timeout: 30000,
413
+ retries: process.env.CI ? 2 : 0, // retry in CI only
414
+ use: {
415
+ baseURL: "http://localhost:3000",
416
+ trace: "on-first-retry", // save trace on failures
417
+ screenshot: "only-on-failure",
418
+ },
419
+ webServer: {
420
+ command: "npm run dev",
421
+ port: 3000,
422
+ reuseExistingServer: !process.env.CI,
423
+ },
424
+ projects: [
425
+ { name: "chrome", use: { browserName: "chromium" } },
426
+ { name: "firefox", use: { browserName: "firefox" } },
427
+ { name: "mobile", use: { ...devices["iPhone 14"] } },
428
+ ],
429
+ });
430
+ ```
431
+
432
+ ---
433
+
434
+ ## API Testing
435
+
436
+ ```typescript
437
+ // Testing REST APIs with supertest (Express/Fastify)
438
+ import request from "supertest";
439
+ import { app } from "./app";
440
+
441
+ describe("POST /api/users", () => {
442
+ it("creates a user and returns 201", async () => {
443
+ const response = await request(app)
444
+ .post("/api/users")
445
+ .send({ name: "Alice", email: "alice@test.com" })
446
+ .expect(201)
447
+ .expect("Content-Type", /json/);
448
+
449
+ expect(response.body).toMatchObject({
450
+ id: expect.any(Number),
451
+ name: "Alice",
452
+ email: "alice@test.com",
453
+ });
454
+ });
455
+
456
+ it("returns 400 for missing required fields", async () => {
457
+ await request(app)
458
+ .post("/api/users")
459
+ .send({ name: "" })
460
+ .expect(400);
461
+ });
462
+
463
+ it("returns 409 for duplicate email", async () => {
464
+ await request(app)
465
+ .post("/api/users")
466
+ .send({ name: "Alice", email: "existing@test.com" })
467
+ .expect(409);
468
+ });
469
+ });
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Code Coverage
475
+
476
+ ```jsonc
477
+ // vitest.config.ts
478
+ export default defineConfig({
479
+ test: {
480
+ coverage: {
481
+ provider: "v8",
482
+ reporter: ["text", "lcov", "html"],
483
+ thresholds: {
484
+ lines: 80,
485
+ functions: 80,
486
+ branches: 75,
487
+ statements: 80,
488
+ },
489
+ exclude: [
490
+ "**/*.test.ts",
491
+ "**/*.spec.ts",
492
+ "**/types/**",
493
+ "**/mocks/**",
494
+ ],
495
+ },
496
+ },
497
+ });
498
+
499
+ // Run: npx vitest --coverage
500
+ ```
501
+
502
+ ```
503
+ Coverage rules:
504
+ - 80% is the practical threshold (not 100%)
505
+ - 100% coverage ≠ 100% confidence
506
+ - Cover edge cases and error paths, not just happy paths
507
+ - Avoid testing implementation details (private methods, internal state)
508
+ - Focus coverage on: business logic, data transformations, auth/security
509
+ - Skip coverage on: config files, types-only files, generated code
510
+ ```
511
+
512
+ ---