tribunal-kit 3.0.0 → 4.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 (233) 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/precedence-reviewer.md +213 -0
  24. package/.agent/agents/product-manager.md +142 -162
  25. package/.agent/agents/product-owner.md +6 -25
  26. package/.agent/agents/project-planner.md +142 -162
  27. package/.agent/agents/qa-automation-engineer.md +225 -242
  28. package/.agent/agents/security-auditor.md +174 -194
  29. package/.agent/agents/seo-specialist.md +193 -213
  30. package/.agent/agents/sql-reviewer.md +161 -194
  31. package/.agent/agents/supervisor-agent.md +184 -203
  32. package/.agent/agents/swarm-worker-contracts.md +17 -17
  33. package/.agent/agents/swarm-worker-registry.md +46 -46
  34. package/.agent/agents/test-coverage-reviewer.md +160 -193
  35. package/.agent/agents/test-engineer.md +0 -21
  36. package/.agent/agents/type-safety-reviewer.md +175 -208
  37. package/.agent/patterns/generator.md +9 -9
  38. package/.agent/patterns/inversion.md +12 -12
  39. package/.agent/patterns/pipeline.md +9 -9
  40. package/.agent/patterns/reviewer.md +13 -13
  41. package/.agent/patterns/tool-wrapper.md +9 -9
  42. package/.agent/rules/GEMINI.md +63 -63
  43. package/.agent/scripts/append_flow.js +72 -0
  44. package/.agent/scripts/case_law_manager.py +525 -0
  45. package/.agent/scripts/compress_skills.py +167 -0
  46. package/.agent/scripts/consolidate_skills.py +173 -0
  47. package/.agent/scripts/deep_compress.py +202 -0
  48. package/.agent/scripts/minify_context.py +80 -0
  49. package/.agent/scripts/security_scan.py +1 -1
  50. package/.agent/scripts/skill_evolution.py +563 -0
  51. package/.agent/scripts/strip_tribunal.py +41 -0
  52. package/.agent/skills/agent-organizer/SKILL.md +100 -126
  53. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  54. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +134 -160
  55. package/.agent/skills/api-patterns/SKILL.md +123 -215
  56. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  57. package/.agent/skills/app-builder/SKILL.md +334 -50
  58. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  59. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  60. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  61. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  62. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  63. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  64. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  65. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  66. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  67. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  68. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  69. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  70. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  71. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  72. package/.agent/skills/appflow-wireframe/SKILL.md +95 -121
  73. package/.agent/skills/architecture/SKILL.md +169 -331
  74. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  75. package/.agent/skills/bash-linux/SKILL.md +129 -154
  76. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  77. package/.agent/skills/brainstorming/SKILL.md +436 -104
  78. package/.agent/skills/building-native-ui/SKILL.md +152 -174
  79. package/.agent/skills/clean-code/SKILL.md +331 -360
  80. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  81. package/.agent/skills/config-validator/SKILL.md +115 -141
  82. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  83. package/.agent/skills/database-design/SKILL.md +104 -369
  84. package/.agent/skills/deployment-procedures/SKILL.md +119 -145
  85. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  86. package/.agent/skills/devops-incident-responder/SKILL.md +87 -113
  87. package/.agent/skills/doc.md +5 -5
  88. package/.agent/skills/documentation-templates/SKILL.md +27 -63
  89. package/.agent/skills/edge-computing/SKILL.md +131 -157
  90. package/.agent/skills/extract-design-system/SKILL.md +108 -134
  91. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  92. package/.agent/skills/frontend-design/SKILL.md +151 -499
  93. package/.agent/skills/game-design-expert/SKILL.md +79 -105
  94. package/.agent/skills/game-engineering-expert/SKILL.md +96 -122
  95. package/.agent/skills/geo-fundamentals/SKILL.md +97 -124
  96. package/.agent/skills/github-operations/SKILL.md +279 -314
  97. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  98. package/.agent/skills/i18n-localization/SKILL.md +113 -138
  99. package/.agent/skills/intelligent-routing/SKILL.md +167 -127
  100. package/.agent/skills/lint-and-validate/SKILL.md +16 -52
  101. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  102. package/.agent/skills/local-first/SKILL.md +128 -154
  103. package/.agent/skills/mcp-builder/SKILL.md +92 -118
  104. package/.agent/skills/mobile-design/SKILL.md +213 -219
  105. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  106. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  107. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  108. package/.agent/skills/observability/SKILL.md +293 -330
  109. package/.agent/skills/parallel-agents/SKILL.md +96 -122
  110. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  111. package/.agent/skills/plan-writing/SKILL.md +92 -118
  112. package/.agent/skills/platform-engineer/SKILL.md +97 -123
  113. package/.agent/skills/playwright-best-practices/SKILL.md +137 -162
  114. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  115. package/.agent/skills/project-idioms/SKILL.md +87 -0
  116. package/.agent/skills/python-patterns/SKILL.md +15 -35
  117. package/.agent/skills/python-pro/SKILL.md +148 -754
  118. package/.agent/skills/react-specialist/SKILL.md +123 -827
  119. package/.agent/skills/readme-builder/SKILL.md +23 -85
  120. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  121. package/.agent/skills/red-team-tactics/SKILL.md +18 -51
  122. package/.agent/skills/rust-pro/SKILL.md +623 -701
  123. package/.agent/skills/seo-fundamentals/SKILL.md +129 -154
  124. package/.agent/skills/server-management/SKILL.md +164 -190
  125. package/.agent/skills/shadcn-ui-expert/SKILL.md +181 -206
  126. package/.agent/skills/skill-creator/SKILL.md +24 -56
  127. package/.agent/skills/sql-pro/SKILL.md +579 -633
  128. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +35 -66
  129. package/.agent/skills/swiftui-expert/SKILL.md +151 -176
  130. package/.agent/skills/systematic-debugging/SKILL.md +92 -118
  131. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  132. package/.agent/skills/tdd-workflow/SKILL.md +111 -137
  133. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  134. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  135. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  136. package/.agent/skills/ui-ux-pro-max/SKILL.md +8 -41
  137. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  138. package/.agent/skills/vue-expert/SKILL.md +127 -866
  139. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  140. package/.agent/skills/web-accessibility-auditor/SKILL.md +168 -193
  141. package/.agent/skills/web-design-guidelines/SKILL.md +25 -61
  142. package/.agent/skills/webapp-testing/SKILL.md +119 -145
  143. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  144. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  145. package/.agent/workflows/api-tester.md +151 -151
  146. package/.agent/workflows/audit.md +127 -138
  147. package/.agent/workflows/brainstorm.md +110 -110
  148. package/.agent/workflows/changelog.md +112 -112
  149. package/.agent/workflows/create.md +124 -124
  150. package/.agent/workflows/debug.md +165 -189
  151. package/.agent/workflows/deploy.md +180 -189
  152. package/.agent/workflows/enhance.md +128 -151
  153. package/.agent/workflows/fix.md +114 -135
  154. package/.agent/workflows/generate.md +13 -4
  155. package/.agent/workflows/migrate.md +160 -160
  156. package/.agent/workflows/orchestrate.md +168 -168
  157. package/.agent/workflows/performance-benchmarker.md +114 -123
  158. package/.agent/workflows/plan.md +173 -173
  159. package/.agent/workflows/preview.md +80 -80
  160. package/.agent/workflows/refactor.md +161 -183
  161. package/.agent/workflows/review-ai.md +101 -129
  162. package/.agent/workflows/review.md +116 -116
  163. package/.agent/workflows/session.md +94 -94
  164. package/.agent/workflows/status.md +79 -79
  165. package/.agent/workflows/strengthen-skills.md +138 -139
  166. package/.agent/workflows/swarm.md +179 -179
  167. package/.agent/workflows/test.md +189 -211
  168. package/.agent/workflows/tribunal-backend.md +94 -113
  169. package/.agent/workflows/tribunal-database.md +95 -115
  170. package/.agent/workflows/tribunal-frontend.md +96 -118
  171. package/.agent/workflows/tribunal-full.md +93 -133
  172. package/.agent/workflows/tribunal-mobile.md +95 -119
  173. package/.agent/workflows/tribunal-performance.md +110 -133
  174. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  175. package/README.md +30 -1
  176. package/bin/tribunal-kit.js +175 -12
  177. package/package.json +25 -4
  178. package/.agent/skills/api-patterns/api-style.md +0 -42
  179. package/.agent/skills/api-patterns/auth.md +0 -24
  180. package/.agent/skills/api-patterns/documentation.md +0 -26
  181. package/.agent/skills/api-patterns/graphql.md +0 -41
  182. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  183. package/.agent/skills/api-patterns/response.md +0 -37
  184. package/.agent/skills/api-patterns/rest.md +0 -40
  185. package/.agent/skills/api-patterns/security-testing.md +0 -122
  186. package/.agent/skills/api-patterns/trpc.md +0 -41
  187. package/.agent/skills/api-patterns/versioning.md +0 -22
  188. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  189. package/.agent/skills/app-builder/feature-building.md +0 -53
  190. package/.agent/skills/app-builder/project-detection.md +0 -34
  191. package/.agent/skills/app-builder/scaffolding.md +0 -118
  192. package/.agent/skills/app-builder/tech-stack.md +0 -40
  193. package/.agent/skills/architecture/context-discovery.md +0 -43
  194. package/.agent/skills/architecture/examples.md +0 -94
  195. package/.agent/skills/architecture/pattern-selection.md +0 -68
  196. package/.agent/skills/architecture/patterns-reference.md +0 -50
  197. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  198. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  199. package/.agent/skills/database-design/database-selection.md +0 -43
  200. package/.agent/skills/database-design/indexing.md +0 -39
  201. package/.agent/skills/database-design/migrations.md +0 -48
  202. package/.agent/skills/database-design/optimization.md +0 -36
  203. package/.agent/skills/database-design/orm-selection.md +0 -30
  204. package/.agent/skills/database-design/schema-design.md +0 -56
  205. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  206. package/.agent/skills/frontend-design/color-system.md +0 -329
  207. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  208. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  209. package/.agent/skills/frontend-design/typography-system.md +0 -363
  210. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  211. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  212. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  213. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  214. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  215. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  216. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  217. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  218. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  219. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  220. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  221. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  222. package/.agent/skills/mobile-design/platform-android.md +0 -666
  223. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  224. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  225. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  226. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  227. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  228. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  229. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  230. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  231. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  232. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  233. 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
+ ---