@massu/core 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/agents/massu-architecture-reviewer.md +104 -0
- package/agents/massu-blast-radius-analyzer.md +84 -0
- package/agents/massu-competitive-scorer.md +126 -0
- package/agents/massu-help-sync.md +73 -0
- package/agents/massu-migration-writer.md +94 -0
- package/agents/massu-output-scorer.md +87 -0
- package/agents/massu-pattern-reviewer.md +84 -0
- package/agents/massu-plan-auditor.md +170 -0
- package/agents/massu-schema-sync-verifier.md +70 -0
- package/agents/massu-security-reviewer.md +98 -0
- package/agents/massu-ux-reviewer.md +106 -0
- package/commands/_shared-preamble.md +53 -23
- package/commands/_shared-references/auto-learning-protocol.md +71 -0
- package/commands/_shared-references/blast-radius-protocol.md +76 -0
- package/commands/_shared-references/security-pre-screen.md +64 -0
- package/commands/_shared-references/test-first-protocol.md +87 -0
- package/commands/_shared-references/verification-table.md +52 -0
- package/commands/massu-article-review.md +343 -0
- package/commands/massu-autoresearch/references/eval-runner.md +84 -0
- package/commands/massu-autoresearch/references/safety-rails.md +125 -0
- package/commands/massu-autoresearch/references/scoring-protocol.md +151 -0
- package/commands/massu-autoresearch.md +258 -0
- package/commands/massu-batch.md +44 -12
- package/commands/massu-bearings.md +42 -8
- package/commands/massu-checkpoint.md +588 -0
- package/commands/massu-ci-fix.md +2 -2
- package/commands/massu-command-health.md +132 -0
- package/commands/massu-command-improve.md +232 -0
- package/commands/massu-commit.md +205 -44
- package/commands/massu-create-plan.md +239 -57
- package/commands/massu-data/references/common-queries.md +79 -0
- package/commands/massu-data/references/table-guide.md +50 -0
- package/commands/massu-data.md +66 -0
- package/commands/massu-dead-code.md +29 -34
- package/commands/massu-debug/references/auto-learning.md +61 -0
- package/commands/massu-debug/references/codegraph-tracing.md +80 -0
- package/commands/massu-debug/references/common-shortcuts.md +98 -0
- package/commands/massu-debug/references/investigation-phases.md +294 -0
- package/commands/massu-debug/references/report-format.md +107 -0
- package/commands/massu-debug.md +105 -386
- package/commands/massu-docs.md +1 -1
- package/commands/massu-full-audit.md +61 -0
- package/commands/massu-gap-enhancement-analyzer.md +276 -16
- package/commands/massu-golden-path/references/approval-points.md +216 -0
- package/commands/massu-golden-path/references/competitive-mode.md +273 -0
- package/commands/massu-golden-path/references/error-handling.md +121 -0
- package/commands/massu-golden-path/references/phase-0-requirements.md +53 -0
- package/commands/massu-golden-path/references/phase-1-plan-creation.md +168 -0
- package/commands/massu-golden-path/references/phase-2-implementation.md +397 -0
- package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +156 -0
- package/commands/massu-golden-path/references/phase-3-simplify.md +40 -0
- package/commands/massu-golden-path/references/phase-4-commit.md +94 -0
- package/commands/massu-golden-path/references/phase-5-push.md +116 -0
- package/commands/massu-golden-path/references/phase-5.5-production-verify.md +170 -0
- package/commands/massu-golden-path/references/phase-6-completion.md +113 -0
- package/commands/massu-golden-path/references/qa-evaluator-spec.md +137 -0
- package/commands/massu-golden-path/references/sprint-contract-protocol.md +117 -0
- package/commands/massu-golden-path/references/vr-visual-calibration.md +73 -0
- package/commands/massu-golden-path.md +114 -848
- package/commands/massu-guide.md +72 -69
- package/commands/massu-hooks.md +27 -12
- package/commands/massu-hotfix.md +221 -144
- package/commands/massu-incident.md +49 -20
- package/commands/massu-infra-audit.md +187 -0
- package/commands/massu-learning-audit.md +211 -0
- package/commands/massu-loop/references/auto-learning.md +49 -0
- package/commands/massu-loop/references/checkpoint-audit.md +40 -0
- package/commands/massu-loop/references/guardrails.md +17 -0
- package/commands/massu-loop/references/iteration-structure.md +115 -0
- package/commands/massu-loop/references/loop-controller.md +188 -0
- package/commands/massu-loop/references/plan-extraction.md +78 -0
- package/commands/massu-loop/references/vr-plan-spec.md +140 -0
- package/commands/massu-loop-playwright.md +9 -9
- package/commands/massu-loop.md +115 -670
- package/commands/massu-new-pattern.md +423 -0
- package/commands/massu-perf.md +422 -0
- package/commands/massu-plan-audit.md +1 -1
- package/commands/massu-plan.md +389 -122
- package/commands/massu-production-verify.md +433 -0
- package/commands/massu-push.md +62 -378
- package/commands/massu-recap.md +29 -3
- package/commands/massu-rollback.md +613 -0
- package/commands/massu-scaffold-hook.md +2 -4
- package/commands/massu-scaffold-page.md +2 -3
- package/commands/massu-scaffold-router.md +1 -2
- package/commands/massu-security.md +619 -0
- package/commands/massu-simplify.md +115 -85
- package/commands/massu-squirrels.md +2 -2
- package/commands/massu-tdd.md +38 -22
- package/commands/massu-test.md +3 -3
- package/commands/massu-type-mismatch-audit.md +469 -0
- package/commands/massu-ui-audit.md +587 -0
- package/commands/massu-verify-playwright.md +287 -32
- package/commands/massu-verify.md +150 -46
- package/dist/cli.js +1451 -1047
- package/dist/hooks/post-tool-use.js +75 -6
- package/dist/hooks/user-prompt.js +16 -0
- package/package.json +6 -2
- package/patterns/build-patterns.md +302 -0
- package/patterns/component-patterns.md +246 -0
- package/patterns/display-patterns.md +185 -0
- package/patterns/form-patterns.md +890 -0
- package/patterns/integration-testing-checklist.md +445 -0
- package/patterns/security-patterns.md +219 -0
- package/patterns/testing-patterns.md +569 -0
- package/patterns/tool-routing.md +81 -0
- package/patterns/ui-patterns.md +371 -0
- package/protocols/plan-implementation.md +267 -0
- package/protocols/recovery.md +225 -0
- package/protocols/verification.md +404 -0
- package/reference/command-taxonomy.md +178 -0
- package/reference/cr-rules-reference.md +76 -0
- package/reference/hook-execution-order.md +148 -0
- package/reference/lessons-learned.md +175 -0
- package/reference/patterns-quickref.md +208 -0
- package/reference/standards.md +135 -0
- package/reference/subagents-reference.md +17 -0
- package/reference/vr-verification-reference.md +867 -0
- package/src/commands/init.ts +27 -0
- package/src/commands/install-commands.ts +149 -53
- package/src/hooks/post-tool-use.ts +17 -0
- package/src/hooks/user-prompt.ts +21 -0
- package/src/memory-file-ingest.ts +127 -0
- package/src/memory-tools.ts +34 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
**Purpose**: Testing pyramid, patterns, and best practices for unit, integration, and E2E tests.
|
|
4
|
+
|
|
5
|
+
**When to Read**: Before writing tests, setting up test infrastructure, or debugging test failures.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Testing Pyramid
|
|
10
|
+
|
|
11
|
+
| Level | Coverage | Tools | Speed |
|
|
12
|
+
|-------|----------|-------|-------|
|
|
13
|
+
| Unit | 80% | Vitest + Testing Library | Fast (seconds) |
|
|
14
|
+
| Integration | 15% | Vitest + tRPC test utils | Medium (seconds) |
|
|
15
|
+
| E2E | 5% | Playwright | Slow (minutes) |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Unit Testing (80% of Tests)
|
|
20
|
+
|
|
21
|
+
### Location Convention
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── __tests__/ # Unit tests
|
|
26
|
+
│ ├── lib/ # Utility tests
|
|
27
|
+
│ │ └── calculateTotal.test.ts
|
|
28
|
+
│ ├── components/ # Component tests
|
|
29
|
+
│ │ └── StatusBadge.test.tsx
|
|
30
|
+
│ └── hooks/ # Hook tests
|
|
31
|
+
│ └── useDebounce.test.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Example 1: Business Logic
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// src/__tests__/lib/calculateTotal.test.ts
|
|
38
|
+
import { describe, it, expect } from 'vitest';
|
|
39
|
+
import { calculateOrderTotal } from '@/lib/calculations/order';
|
|
40
|
+
|
|
41
|
+
describe('calculateOrderTotal', () => {
|
|
42
|
+
it('should calculate total with tax', () => {
|
|
43
|
+
const items = [
|
|
44
|
+
{ price: 100, quantity: 2 },
|
|
45
|
+
{ price: 50, quantity: 1 },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const result = calculateOrderTotal(items, { taxRate: 0.08 });
|
|
49
|
+
|
|
50
|
+
expect(result.subtotal).toBe(250);
|
|
51
|
+
expect(result.tax).toBe(20);
|
|
52
|
+
expect(result.total).toBe(270);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle empty items', () => {
|
|
56
|
+
const result = calculateOrderTotal([], { taxRate: 0.08 });
|
|
57
|
+
|
|
58
|
+
expect(result.subtotal).toBe(0);
|
|
59
|
+
expect(result.tax).toBe(0);
|
|
60
|
+
expect(result.total).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should apply discount', () => {
|
|
64
|
+
const items = [{ price: 100, quantity: 1 }];
|
|
65
|
+
|
|
66
|
+
const result = calculateOrderTotal(items, {
|
|
67
|
+
taxRate: 0.08,
|
|
68
|
+
discountPercent: 10,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(result.subtotal).toBe(100);
|
|
72
|
+
expect(result.discount).toBe(10);
|
|
73
|
+
expect(result.tax).toBe(7.2); // Tax on discounted amount
|
|
74
|
+
expect(result.total).toBe(97.2);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Example 2: React Component
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// src/__tests__/components/StatusBadge.test.tsx
|
|
83
|
+
import { describe, it, expect } from 'vitest';
|
|
84
|
+
import { render, screen } from '@testing-library/react';
|
|
85
|
+
import { StatusBadge } from '@/components/common/StatusBadge';
|
|
86
|
+
|
|
87
|
+
describe('StatusBadge', () => {
|
|
88
|
+
it('should render active status with green badge', () => {
|
|
89
|
+
render(<StatusBadge status="active" />);
|
|
90
|
+
|
|
91
|
+
const badge = screen.getByText('Active');
|
|
92
|
+
expect(badge).toBeInTheDocument();
|
|
93
|
+
expect(badge.className).toContain('badge-success');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should handle null status gracefully', () => {
|
|
97
|
+
render(<StatusBadge status={null} />);
|
|
98
|
+
|
|
99
|
+
const badge = screen.getByText('Pending');
|
|
100
|
+
expect(badge).toBeInTheDocument();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should capitalize status text', () => {
|
|
104
|
+
render(<StatusBadge status="in_progress" />);
|
|
105
|
+
|
|
106
|
+
expect(screen.getByText('In Progress')).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Example 3: Custom Hook
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// src/__tests__/hooks/useDebounce.test.ts
|
|
115
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
116
|
+
import { renderHook, act } from '@testing-library/react';
|
|
117
|
+
import { useDebounce } from '@/hooks/useDebounce';
|
|
118
|
+
|
|
119
|
+
describe('useDebounce', () => {
|
|
120
|
+
it('should debounce value changes', async () => {
|
|
121
|
+
vi.useFakeTimers();
|
|
122
|
+
|
|
123
|
+
const { result, rerender } = renderHook(
|
|
124
|
+
({ value }) => useDebounce(value, 300),
|
|
125
|
+
{ initialProps: { value: 'initial' } }
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(result.current).toBe('initial');
|
|
129
|
+
|
|
130
|
+
rerender({ value: 'updated' });
|
|
131
|
+
expect(result.current).toBe('initial'); // Not yet updated
|
|
132
|
+
|
|
133
|
+
act(() => {
|
|
134
|
+
vi.advanceTimersByTime(300);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(result.current).toBe('updated'); // Now updated
|
|
138
|
+
|
|
139
|
+
vi.useRealTimers();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 2. Integration Testing (15% of Tests)
|
|
147
|
+
|
|
148
|
+
### Location Convention
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
tests/
|
|
152
|
+
├── integration/
|
|
153
|
+
│ ├── routers/ # tRPC router tests
|
|
154
|
+
│ │ └── orders.test.ts
|
|
155
|
+
│ ├── database/ # Database transaction tests
|
|
156
|
+
│ │ └── transactions.test.ts
|
|
157
|
+
│ └── helpers/
|
|
158
|
+
│ └── trpc.ts # Test caller setup
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### tRPC Router Testing
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// tests/integration/routers/orders.test.ts
|
|
165
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
166
|
+
import { createCaller } from '../helpers/trpc';
|
|
167
|
+
import { PrismaClient } from '@prisma/client';
|
|
168
|
+
|
|
169
|
+
const prisma = new PrismaClient();
|
|
170
|
+
|
|
171
|
+
describe('Integration: Orders Router', () => {
|
|
172
|
+
let caller: ReturnType<typeof createCaller>;
|
|
173
|
+
const testCustomerId = 'test-customer-123';
|
|
174
|
+
|
|
175
|
+
beforeAll(async () => {
|
|
176
|
+
caller = createCaller({ userId: 'test-user-id' });
|
|
177
|
+
|
|
178
|
+
// Setup test data
|
|
179
|
+
await prisma.customer.create({
|
|
180
|
+
data: { id: testCustomerId, name: 'Test Customer', email: 'test@example.com' }
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await prisma.product.create({
|
|
184
|
+
data: { id: 'test-product-789', name: 'Test Product', price: 100, inventory_quantity: 100 }
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
afterAll(async () => {
|
|
189
|
+
await prisma.orderItem.deleteMany({ where: { order: { customerId: testCustomerId } } });
|
|
190
|
+
await prisma.order.deleteMany({ where: { customerId: testCustomerId } });
|
|
191
|
+
await prisma.product.deleteMany({ where: { id: 'test-product-789' } });
|
|
192
|
+
await prisma.customer.deleteMany({ where: { id: testCustomerId } });
|
|
193
|
+
await prisma.$disconnect();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should create order and update inventory', async () => {
|
|
197
|
+
const result = await caller.orders.create({
|
|
198
|
+
customerId: testCustomerId,
|
|
199
|
+
items: [
|
|
200
|
+
{ productId: 'test-product-789', quantity: 5, price: 100 }
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(result.total).toBe(500);
|
|
205
|
+
expect(result.status).toBe('pending');
|
|
206
|
+
|
|
207
|
+
// Verify inventory updated
|
|
208
|
+
const product = await prisma.product.findUnique({
|
|
209
|
+
where: { id: 'test-product-789' }
|
|
210
|
+
});
|
|
211
|
+
expect(product?.inventory_quantity).toBe(95);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should fail when insufficient inventory', async () => {
|
|
215
|
+
await expect(
|
|
216
|
+
caller.orders.create({
|
|
217
|
+
customerId: testCustomerId,
|
|
218
|
+
items: [
|
|
219
|
+
{ productId: 'test-product-789', quantity: 200, price: 100 }
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
).rejects.toThrow('Insufficient inventory');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Integration Test Best Practices
|
|
228
|
+
|
|
229
|
+
**1. Use Test Database**
|
|
230
|
+
```typescript
|
|
231
|
+
// .env.test
|
|
232
|
+
DATABASE_URL="postgresql://user:pass@localhost:5432/test_db"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**2. Clean Test Data After Each Test**
|
|
236
|
+
```typescript
|
|
237
|
+
afterEach(async () => {
|
|
238
|
+
// Delete in reverse foreign key order
|
|
239
|
+
await prisma.orderItem.deleteMany({ where: { orderId: testOrderId } });
|
|
240
|
+
await prisma.order.deleteMany({ where: { id: testOrderId } });
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**3. Test Error Cases**
|
|
245
|
+
```typescript
|
|
246
|
+
it('should handle database constraint violations', async () => {
|
|
247
|
+
await expect(
|
|
248
|
+
prisma.order.create({ data: { customerId: 'non-existent' } })
|
|
249
|
+
).rejects.toThrow('Foreign key constraint');
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**4. Use Factory Functions for Test Data**
|
|
254
|
+
```typescript
|
|
255
|
+
// tests/factories/order-factory.ts
|
|
256
|
+
export async function createTestOrder(overrides = {}) {
|
|
257
|
+
return await prisma.order.create({
|
|
258
|
+
data: {
|
|
259
|
+
userId: 'test-user',
|
|
260
|
+
customerId: 'test-customer',
|
|
261
|
+
total: 100,
|
|
262
|
+
status: 'pending',
|
|
263
|
+
...overrides
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 2.1 Router Contract Validation (Integration Test Prerequisite)
|
|
272
|
+
|
|
273
|
+
### CRITICAL: Verify Router Methods Exist BEFORE Writing Tests
|
|
274
|
+
|
|
275
|
+
**Why This Matters**: Tests written without verifying router methods exist cause "No procedure found on path" errors. This wastes significant debugging time.
|
|
276
|
+
|
|
277
|
+
### The 4-Step Verification Process
|
|
278
|
+
|
|
279
|
+
Before writing ANY integration test:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
/**
|
|
283
|
+
* STEP 1: Read Actual Router File
|
|
284
|
+
* ================================
|
|
285
|
+
*/
|
|
286
|
+
// Terminal command:
|
|
287
|
+
cat src/server/api/routers/my-router.ts | grep -A2 "export const"
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* STEP 2: Document Available Methods in Test Header
|
|
291
|
+
* ==================================================
|
|
292
|
+
*/
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Integration Tests: My Router
|
|
296
|
+
*
|
|
297
|
+
* ROUTER METHODS VERIFIED (date)
|
|
298
|
+
* Source: src/server/api/routers/my-router.ts
|
|
299
|
+
*
|
|
300
|
+
* Available Methods:
|
|
301
|
+
* - method1(params) - Description
|
|
302
|
+
* - method2(params) - Description
|
|
303
|
+
*
|
|
304
|
+
* Methods NOT Available:
|
|
305
|
+
* - create() - Does NOT exist (use other router instead)
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* STEP 3: Run Automated Validation
|
|
310
|
+
* =================================
|
|
311
|
+
*/
|
|
312
|
+
npm run validate:router-contracts
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* STEP 4: Commit Tests Immediately
|
|
316
|
+
* =================================
|
|
317
|
+
*/
|
|
318
|
+
git add tests/integration/
|
|
319
|
+
git commit -m "test(integration): add router lifecycle tests"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Common API Mismatches
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// WRONG - Assumed method name
|
|
326
|
+
const result = await caller.myRouter.create({ name: 'Test' });
|
|
327
|
+
// Error: No procedure found on path 'myRouter,create'
|
|
328
|
+
|
|
329
|
+
// CORRECT - Verified method name
|
|
330
|
+
const result = await caller.myRouter.executeAction({
|
|
331
|
+
actionId: existingId,
|
|
332
|
+
context: { trigger: 'manual' }
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 3. E2E Testing (5% of Tests)
|
|
339
|
+
|
|
340
|
+
### Critical Test Paths
|
|
341
|
+
|
|
342
|
+
| # | Test | Coverage |
|
|
343
|
+
|---|------|----------|
|
|
344
|
+
| 1 | Authentication | Login, logout, session persistence |
|
|
345
|
+
| 2 | Admin Access | Admin portal access control |
|
|
346
|
+
| 3 | Order Creation | End-to-end order flow |
|
|
347
|
+
| 4 | Payment Processing | Payment integration |
|
|
348
|
+
| 5 | Document Upload | File upload/download |
|
|
349
|
+
| 6 | Search | Global search functionality |
|
|
350
|
+
|
|
351
|
+
### When to Add E2E Tests
|
|
352
|
+
|
|
353
|
+
**DO add for:**
|
|
354
|
+
- Critical revenue-generating flows (checkout, payment)
|
|
355
|
+
- Security-critical paths (authentication, authorization)
|
|
356
|
+
- Cross-system integrations (payment gateways, APIs)
|
|
357
|
+
- Compliance-required workflows (audit trails)
|
|
358
|
+
|
|
359
|
+
**DO NOT add for:**
|
|
360
|
+
- Features covered by unit/integration tests
|
|
361
|
+
- UI styling or layout changes
|
|
362
|
+
- Admin-only features (unless security-critical)
|
|
363
|
+
|
|
364
|
+
### E2E Best Practices
|
|
365
|
+
|
|
366
|
+
**1. Keep Tests Independent**
|
|
367
|
+
```typescript
|
|
368
|
+
// WRONG - Tests depend on each other
|
|
369
|
+
test('create order', async ({ page }) => { /* ... */ });
|
|
370
|
+
test('view order', async ({ page }) => {
|
|
371
|
+
// Uses order from previous test (BRITTLE!)
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// CORRECT - Self-contained
|
|
375
|
+
test('view order', async ({ page }) => {
|
|
376
|
+
const orderId = await createTestOrder();
|
|
377
|
+
await page.goto(`/orders/${orderId}`);
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**2. Use Page Object Pattern**
|
|
382
|
+
```typescript
|
|
383
|
+
export class LoginPage {
|
|
384
|
+
constructor(private page: Page) {}
|
|
385
|
+
|
|
386
|
+
async goto() {
|
|
387
|
+
await this.page.goto('/auth/login');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async login(email: string, password: string) {
|
|
391
|
+
await this.page.fill('[name="email"]', email);
|
|
392
|
+
await this.page.fill('[name="password"]', password);
|
|
393
|
+
await this.page.click('button[type="submit"]');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**3. Wait for Network**
|
|
399
|
+
```typescript
|
|
400
|
+
// WRONG
|
|
401
|
+
await page.click('button');
|
|
402
|
+
await expect(page.locator('.result')).toBeVisible();
|
|
403
|
+
|
|
404
|
+
// CORRECT
|
|
405
|
+
await page.click('button');
|
|
406
|
+
await page.waitForLoadState('networkidle');
|
|
407
|
+
await expect(page.locator('.result')).toBeVisible();
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 4. Test Data Management
|
|
413
|
+
|
|
414
|
+
### Mock Data Strategies
|
|
415
|
+
|
|
416
|
+
| Strategy | Use Case | Example |
|
|
417
|
+
|----------|----------|---------|
|
|
418
|
+
| Inline mocks | Unit tests | `{ name: 'John', email: 'john@example.com' }` |
|
|
419
|
+
| Factory functions | Integration tests | `createTestUser({ email: 'specific@test.com' })` |
|
|
420
|
+
| Database seeding | E2E tests | `seedTestData()` in beforeAll |
|
|
421
|
+
|
|
422
|
+
### Test Isolation
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// Use unique test IDs
|
|
426
|
+
const testId = `test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
427
|
+
|
|
428
|
+
// Clean up after each test
|
|
429
|
+
afterEach(async () => {
|
|
430
|
+
await prisma.order.deleteMany({ where: { userId: testId } });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// WRONG - Shared state
|
|
434
|
+
let sharedOrder: Order;
|
|
435
|
+
beforeEach(() => { sharedOrder = createOrder(); });
|
|
436
|
+
|
|
437
|
+
// CORRECT - Each test creates its own
|
|
438
|
+
it('should update order', () => {
|
|
439
|
+
const order = createOrder();
|
|
440
|
+
// Test with this order
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## 5. Development Workflow
|
|
447
|
+
|
|
448
|
+
### Pre-Commit Checks
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"lint-staged": {
|
|
453
|
+
"*.{ts,tsx}": [
|
|
454
|
+
"npm run test -- --related --run",
|
|
455
|
+
"npm run type-check",
|
|
456
|
+
"npm run lint --fix"
|
|
457
|
+
]
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Local Development
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
# Before starting work
|
|
466
|
+
git pull origin main && npm install && npm run test
|
|
467
|
+
|
|
468
|
+
# During development
|
|
469
|
+
npm run test -- --watch
|
|
470
|
+
|
|
471
|
+
# Before committing
|
|
472
|
+
npm run test && npm run type-check && npm run lint
|
|
473
|
+
|
|
474
|
+
# Before pushing
|
|
475
|
+
npm run build
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 6. Common Testing Patterns
|
|
481
|
+
|
|
482
|
+
### Testing Async Functions
|
|
483
|
+
```typescript
|
|
484
|
+
it('should fetch data', async () => {
|
|
485
|
+
const result = await fetchUser('user-123');
|
|
486
|
+
expect(result.email).toBe('test@example.com');
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Testing Error Handling
|
|
491
|
+
```typescript
|
|
492
|
+
it('should throw for invalid input', async () => {
|
|
493
|
+
await expect(createOrder({ items: [] }))
|
|
494
|
+
.rejects.toThrow('Order must have at least one item');
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Testing with Mocks
|
|
499
|
+
```typescript
|
|
500
|
+
import { vi } from 'vitest';
|
|
501
|
+
|
|
502
|
+
it('should call external API', async () => {
|
|
503
|
+
const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) });
|
|
504
|
+
global.fetch = mockFetch;
|
|
505
|
+
|
|
506
|
+
await sendEmail('test@example.com', 'Hello');
|
|
507
|
+
|
|
508
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
509
|
+
expect.stringContaining('/api/email'),
|
|
510
|
+
expect.objectContaining({ method: 'POST' })
|
|
511
|
+
);
|
|
512
|
+
});
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Testing React Components
|
|
516
|
+
```typescript
|
|
517
|
+
import { render, screen } from '@testing-library/react';
|
|
518
|
+
import userEvent from '@testing-library/user-event';
|
|
519
|
+
|
|
520
|
+
it('should submit form', async () => {
|
|
521
|
+
const handleSubmit = vi.fn();
|
|
522
|
+
render(<LoginForm onSubmit={handleSubmit} />);
|
|
523
|
+
|
|
524
|
+
await userEvent.type(screen.getByLabelText('Email'), 'test@example.com');
|
|
525
|
+
await userEvent.type(screen.getByLabelText('Password'), 'password123');
|
|
526
|
+
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
|
|
527
|
+
|
|
528
|
+
expect(handleSubmit).toHaveBeenCalledWith({
|
|
529
|
+
email: 'test@example.com',
|
|
530
|
+
password: 'password123'
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## 7. Troubleshooting
|
|
538
|
+
|
|
539
|
+
| Issue | Solution |
|
|
540
|
+
|-------|---------|
|
|
541
|
+
| Tests timing out | Increase timeout: `{ timeout: 10000 }` or globally in `vitest.config.ts` |
|
|
542
|
+
| Database tests failing | Clean database: `await prisma.$executeRaw\`TRUNCATE TABLE orders CASCADE\`` |
|
|
543
|
+
| Flaky E2E tests | Use `toPass` with retry: `await expect(async () => { ... }).toPass({ timeout: 5000 })` |
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Quick Reference
|
|
548
|
+
|
|
549
|
+
| Scenario | Test Type | Location |
|
|
550
|
+
|----------|-----------|----------|
|
|
551
|
+
| Business logic function | Unit | `src/__tests__/` |
|
|
552
|
+
| React component behavior | Unit | `src/__tests__/components/` |
|
|
553
|
+
| tRPC endpoint | Integration | `tests/integration/routers/` |
|
|
554
|
+
| Database transaction | Integration | `tests/integration/database/` |
|
|
555
|
+
| Login flow | E2E | `tests/e2e/critical/` |
|
|
556
|
+
| Payment processing | E2E | `tests/e2e/critical/` |
|
|
557
|
+
|
|
558
|
+
```bash
|
|
559
|
+
npm run test # All unit + integration tests
|
|
560
|
+
npm run test -- --watch # Watch mode
|
|
561
|
+
npm run test:e2e # E2E tests
|
|
562
|
+
npm run type-check # TypeScript validation
|
|
563
|
+
npm run build # Verify production build
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
**Document Status**: ACTIVE
|
|
569
|
+
**Compliance**: Mandatory testing pyramid enforcement
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Tool Routing Patterns
|
|
2
|
+
|
|
3
|
+
**Purpose**: Decision trees for selecting the correct tool/MCP for browser automation, Google services, and code search.
|
|
4
|
+
|
|
5
|
+
**When to Read**: Before using any browser automation, Google service, or code search tool.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Browser Automation Routing
|
|
10
|
+
|
|
11
|
+
| Need | Tool | Why |
|
|
12
|
+
|------|------|-----|
|
|
13
|
+
| Navigate + screenshot + verify | Playwright CLI (`browser_navigate`, `browser_snapshot`) | Deterministic, accessibility tree, no vision needed |
|
|
14
|
+
| Fill forms + click buttons | Playwright CLI (`browser_click`, `browser_fill_form`) | Direct element interaction via ref IDs |
|
|
15
|
+
| Complex multi-step flows | Playwright CLI (multiple calls) | Sequential, reliable |
|
|
16
|
+
| Visual design review | Playwright CLI `browser_take_screenshot` + vision analysis | Screenshot for LLM visual evaluation |
|
|
17
|
+
| Natural language interaction | Stagehand MCP | When element selectors are unclear |
|
|
18
|
+
| Generic web scraping | Browser MCP | Non-app browsing tasks |
|
|
19
|
+
|
|
20
|
+
### Decision Tree
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Need browser automation?
|
|
24
|
+
├── Testing YOUR app (localhost/preview)?
|
|
25
|
+
│ ├── Know the element? → Playwright CLI (browser_click, browser_fill_form)
|
|
26
|
+
│ ├── Need page state? → Playwright CLI (browser_snapshot)
|
|
27
|
+
│ ├── Need screenshot? → Playwright CLI (browser_take_screenshot)
|
|
28
|
+
│ └── Complex flow? → Playwright CLI (sequential calls)
|
|
29
|
+
├── Scraping external site?
|
|
30
|
+
│ └── → Browser MCP or WebFetch
|
|
31
|
+
└── Natural language interaction needed?
|
|
32
|
+
└── → Stagehand MCP
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Google Services Routing
|
|
38
|
+
|
|
39
|
+
| Need | Tool | Why |
|
|
40
|
+
|------|------|-----|
|
|
41
|
+
| Read/write spreadsheets | Google Workspace MCP | Direct API access |
|
|
42
|
+
| Send email | Google Workspace MCP (`send_gmail_message`) | Gmail API |
|
|
43
|
+
| Calendar events | Google Workspace MCP (`get_events`, `manage_event`) | Calendar API |
|
|
44
|
+
| Drive files | Google Workspace MCP (`search_drive_files`, `get_drive_file_content`) | Drive API |
|
|
45
|
+
| Apps Script management | Google Workspace MCP (`get_script_content`, `update_script_content`) | Direct script access |
|
|
46
|
+
| Run Apps Script function | Google Workspace MCP (`run_script_function`) | Execute deployed functions |
|
|
47
|
+
| Create Apps Script project | Google Workspace MCP (`create_script_project`) | Project scaffolding |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Code Search Routing
|
|
52
|
+
|
|
53
|
+
| Need | Tool | Why |
|
|
54
|
+
|------|------|-----|
|
|
55
|
+
| Find files by name/pattern | Glob | Fast pattern matching, sorted by modification time |
|
|
56
|
+
| Search file contents | Grep | Regex search with context, line numbers |
|
|
57
|
+
| Understand call graph | Codegraph MCP (`codegraph_callers`, `codegraph_callees`) | Relationship analysis |
|
|
58
|
+
| Impact analysis | Codegraph MCP (`codegraph_impact`) | What would break if X changes |
|
|
59
|
+
| Multi-round exploration | Task agent | Open-ended investigation |
|
|
60
|
+
|
|
61
|
+
### Decision Tree
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Need to find something in code?
|
|
65
|
+
├── Know the filename pattern?
|
|
66
|
+
│ └── → Glob ("**/*.tsx", "src/**/*service*")
|
|
67
|
+
├── Know what the code contains?
|
|
68
|
+
│ ├── Simple pattern? → Grep (pattern, path)
|
|
69
|
+
│ └── Complex regex? → Grep (multiline: true)
|
|
70
|
+
├── Need to understand relationships?
|
|
71
|
+
│ ├── Who calls this? → codegraph_callers
|
|
72
|
+
│ ├── What does this call? → codegraph_callees
|
|
73
|
+
│ └── What breaks if I change this? → codegraph_impact
|
|
74
|
+
└── Open-ended exploration?
|
|
75
|
+
└── → Task agent (multiple search rounds)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
**Document Status**: ACTIVE
|
|
81
|
+
**Compliance**: Use routing tables before selecting tools
|