@qazuor/claude-code-config 0.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.
- package/LICENSE +21 -0
- package/README.md +1248 -0
- package/dist/bin.cjs +11886 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +11869 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.cjs +3887 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1325 -0
- package/dist/index.d.ts +1325 -0
- package/dist/index.js +3835 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
- package/templates/.log/notifications.log +1775 -0
- package/templates/agents/README.md +164 -0
- package/templates/agents/_registry.json +443 -0
- package/templates/agents/design/content-writer.md +353 -0
- package/templates/agents/design/ux-ui-designer.md +382 -0
- package/templates/agents/engineering/astro-engineer.md +293 -0
- package/templates/agents/engineering/db-drizzle-engineer.md +360 -0
- package/templates/agents/engineering/express-engineer.md +316 -0
- package/templates/agents/engineering/fastify-engineer.md +399 -0
- package/templates/agents/engineering/hono-engineer.md +263 -0
- package/templates/agents/engineering/mongoose-engineer.md +473 -0
- package/templates/agents/engineering/nestjs-engineer.md +429 -0
- package/templates/agents/engineering/nextjs-engineer.md +451 -0
- package/templates/agents/engineering/node-typescript-engineer.md +347 -0
- package/templates/agents/engineering/prisma-engineer.md +432 -0
- package/templates/agents/engineering/react-senior-dev.md +394 -0
- package/templates/agents/engineering/tanstack-start-engineer.md +447 -0
- package/templates/agents/engineering/tech-lead.md +269 -0
- package/templates/agents/product/product-functional.md +329 -0
- package/templates/agents/product/product-technical.md +578 -0
- package/templates/agents/quality/debugger.md +514 -0
- package/templates/agents/quality/qa-engineer.md +390 -0
- package/templates/agents/specialized/enrichment-agent.md +277 -0
- package/templates/agents/specialized/i18n-specialist.md +322 -0
- package/templates/agents/specialized/seo-ai-specialist.md +387 -0
- package/templates/agents/specialized/tech-writer.md +300 -0
- package/templates/code-style/.editorconfig +27 -0
- package/templates/code-style/.prettierignore +25 -0
- package/templates/code-style/.prettierrc +12 -0
- package/templates/code-style/biome.json +78 -0
- package/templates/code-style/commitlint.config.js +44 -0
- package/templates/commands/README.md +175 -0
- package/templates/commands/_registry.json +420 -0
- package/templates/commands/add-new-entity.md +211 -0
- package/templates/commands/audit/accessibility-audit.md +360 -0
- package/templates/commands/audit/performance-audit.md +290 -0
- package/templates/commands/audit/security-audit.md +231 -0
- package/templates/commands/code-check.md +127 -0
- package/templates/commands/five-why.md +225 -0
- package/templates/commands/formatting/format-markdown.md +197 -0
- package/templates/commands/git/commit.md +247 -0
- package/templates/commands/meta/create-agent.md +257 -0
- package/templates/commands/meta/create-command.md +312 -0
- package/templates/commands/meta/create-skill.md +321 -0
- package/templates/commands/meta/help.md +318 -0
- package/templates/commands/planning/check-completed-tasks.md +224 -0
- package/templates/commands/planning/cleanup-issues.md +248 -0
- package/templates/commands/planning/planning-cleanup.md +251 -0
- package/templates/commands/planning/sync-planning-github.md +133 -0
- package/templates/commands/planning/sync-todos-github.md +203 -0
- package/templates/commands/quality-check.md +211 -0
- package/templates/commands/run-tests.md +159 -0
- package/templates/commands/start-feature-plan.md +232 -0
- package/templates/commands/start-refactor-plan.md +244 -0
- package/templates/commands/sync-planning.md +176 -0
- package/templates/commands/update-docs.md +242 -0
- package/templates/docs/CHECKPOINT-SYSTEM.md +504 -0
- package/templates/docs/INDEX.md +677 -0
- package/templates/docs/RECOMMENDED-HOOKS.md +415 -0
- package/templates/docs/_registry.json +329 -0
- package/templates/docs/diagrams/README.md +220 -0
- package/templates/docs/diagrams/agent-hierarchy.mmd +55 -0
- package/templates/docs/diagrams/documentation-map.mmd +61 -0
- package/templates/docs/diagrams/tools-relationship.mmd +55 -0
- package/templates/docs/diagrams/workflow-decision-tree.mmd +38 -0
- package/templates/docs/doc-sync.md +533 -0
- package/templates/docs/examples/end-to-end-workflow.md +1505 -0
- package/templates/docs/glossary.md +495 -0
- package/templates/docs/guides/mockup-prompt-engineering.md +644 -0
- package/templates/docs/guides/mockup-setup.md +737 -0
- package/templates/docs/learnings/README.md +250 -0
- package/templates/docs/learnings/common-architectural-patterns.md +123 -0
- package/templates/docs/learnings/common-mistakes-to-avoid.md +149 -0
- package/templates/docs/learnings/markdown-formatting-standards.md +104 -0
- package/templates/docs/learnings/monorepo-command-execution.md +64 -0
- package/templates/docs/learnings/optimization-tips.md +146 -0
- package/templates/docs/learnings/planning-linear-sync-workflow.md +70 -0
- package/templates/docs/learnings/shell-compatibility-fish.md +46 -0
- package/templates/docs/learnings/test-organization-structure.md +68 -0
- package/templates/docs/mcp-installation.md +613 -0
- package/templates/docs/mcp-servers.md +989 -0
- package/templates/docs/notification-installation.md +570 -0
- package/templates/docs/quick-start.md +354 -0
- package/templates/docs/standards/architecture-patterns.md +1064 -0
- package/templates/docs/standards/atomic-commits.md +513 -0
- package/templates/docs/standards/code-standards.md +993 -0
- package/templates/docs/standards/design-standards.md +656 -0
- package/templates/docs/standards/documentation-standards.md +1160 -0
- package/templates/docs/standards/testing-standards.md +969 -0
- package/templates/docs/system-maintenance.md +604 -0
- package/templates/docs/templates/PDR-template.md +561 -0
- package/templates/docs/templates/TODOs-template.md +534 -0
- package/templates/docs/templates/tech-analysis-template.md +800 -0
- package/templates/docs/workflows/README.md +519 -0
- package/templates/docs/workflows/atomic-task-protocol.md +955 -0
- package/templates/docs/workflows/decision-tree.md +482 -0
- package/templates/docs/workflows/edge-cases.md +856 -0
- package/templates/docs/workflows/phase-1-planning.md +957 -0
- package/templates/docs/workflows/phase-2-implementation.md +896 -0
- package/templates/docs/workflows/phase-3-validation.md +792 -0
- package/templates/docs/workflows/phase-4-finalization.md +927 -0
- package/templates/docs/workflows/quick-fix-protocol.md +505 -0
- package/templates/docs/workflows/task-atomization.md +537 -0
- package/templates/docs/workflows/task-completion-protocol.md +448 -0
- package/templates/hooks/on-notification.sh +28 -0
- package/templates/schemas/checkpoint.schema.json +97 -0
- package/templates/schemas/code-registry.schema.json +84 -0
- package/templates/schemas/pdr.schema.json +314 -0
- package/templates/schemas/problems.schema.json +55 -0
- package/templates/schemas/tech-analysis.schema.json +404 -0
- package/templates/schemas/telemetry.schema.json +298 -0
- package/templates/schemas/todos.schema.json +234 -0
- package/templates/schemas/workflows.schema.json +69 -0
- package/templates/scripts/add-changelogs.sh +105 -0
- package/templates/scripts/generate-code-registry.ts +270 -0
- package/templates/scripts/health-check.sh +343 -0
- package/templates/scripts/sync-registry.sh +40 -0
- package/templates/scripts/telemetry-report.ts +36 -0
- package/templates/scripts/validate-docs.sh +224 -0
- package/templates/scripts/validate-registry.sh +225 -0
- package/templates/scripts/validate-schemas.ts +283 -0
- package/templates/scripts/validate-structure.sh +165 -0
- package/templates/scripts/worktree-cleanup.sh +81 -0
- package/templates/scripts/worktree-create.sh +63 -0
- package/templates/sessions/planning/.gitkeep +0 -0
- package/templates/sessions/planning/archived/.gitkeep +0 -0
- package/templates/settings.json +202 -0
- package/templates/settings.local.json +138 -0
- package/templates/skills/README.md +197 -0
- package/templates/skills/_registry.json +473 -0
- package/templates/skills/audit/accessibility-audit.md +309 -0
- package/templates/skills/audit/performance-audit.md +257 -0
- package/templates/skills/audit/security-audit.md +217 -0
- package/templates/skills/auth/nextauth-patterns.md +308 -0
- package/templates/skills/brand-guidelines.md +240 -0
- package/templates/skills/documentation/markdown-formatter.md +302 -0
- package/templates/skills/git/git-commit-helper.md +321 -0
- package/templates/skills/i18n/i18n-patterns.md +251 -0
- package/templates/skills/patterns/error-handling-patterns.md +242 -0
- package/templates/skills/patterns/tdd-methodology.md +342 -0
- package/templates/skills/qa/qa-criteria-validator.md +383 -0
- package/templates/skills/qa/web-app-testing.md +398 -0
- package/templates/skills/react/react-hook-form-patterns.md +359 -0
- package/templates/skills/state/redux-toolkit-patterns.md +272 -0
- package/templates/skills/state/tanstack-query-patterns.md +299 -0
- package/templates/skills/state/zustand-patterns.md +301 -0
- package/templates/skills/tech/mermaid-diagram-specialist.md +195 -0
- package/templates/skills/tech/shadcn-specialist.md +252 -0
- package/templates/skills/tech/vercel-specialist.md +297 -0
- package/templates/skills/testing/api-app-testing.md +254 -0
- package/templates/skills/testing/performance-testing.md +275 -0
- package/templates/skills/testing/security-testing.md +348 -0
- package/templates/skills/utils/add-memory.md +295 -0
- package/templates/skills/utils/json-data-auditor.md +283 -0
- package/templates/skills/utils/pdf-creator-editor.md +342 -0
- package/templates/tools/format-markdown.sh +185 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
# Testing Standards
|
|
2
|
+
|
|
3
|
+
This document defines the testing standards and practices.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
<!-- markdownlint-disable MD051 -->
|
|
10
|
+
|
|
11
|
+
1. [Testing Philosophy](#testing-philosophy)
|
|
12
|
+
2. [TDD Workflow](#tdd-workflow)
|
|
13
|
+
3. [Test Types](#test-types)
|
|
14
|
+
4. [Coverage Requirements](#coverage-requirements)
|
|
15
|
+
5. [Test Structure](#test-structure)
|
|
16
|
+
6. [Naming Conventions](#naming-conventions)
|
|
17
|
+
7. [Test Patterns](#test-patterns)
|
|
18
|
+
8. [Mocking & Fixtures](#mocking--fixtures)
|
|
19
|
+
9. [Database Testing](#database-testing)
|
|
20
|
+
10. [API Testing](#api-testing)
|
|
21
|
+
11. [Frontend Testing](#frontend-testing)
|
|
22
|
+
12. [Common Pitfalls](#common-pitfalls)
|
|
23
|
+
|
|
24
|
+
<!-- markdownlint-enable MD051 -->
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Testing Philosophy
|
|
29
|
+
|
|
30
|
+
### Core Principles
|
|
31
|
+
|
|
32
|
+
**Test-Driven Development (TDD):**
|
|
33
|
+
|
|
34
|
+
- Write tests FIRST, before implementation
|
|
35
|
+
- Follow Red → Green → Refactor cycle
|
|
36
|
+
- Tests define the interface and behavior
|
|
37
|
+
|
|
38
|
+
**90% Minimum Coverage:**
|
|
39
|
+
|
|
40
|
+
- Unit tests: All public functions
|
|
41
|
+
- Integration tests: All API endpoints
|
|
42
|
+
- E2E tests: Critical user flows
|
|
43
|
+
|
|
44
|
+
**Test Quality Over Quantity:**
|
|
45
|
+
|
|
46
|
+
- Tests should be maintainable
|
|
47
|
+
- Tests should be readable
|
|
48
|
+
- Tests should test behavior, not implementation
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## TDD Workflow
|
|
53
|
+
|
|
54
|
+
### Red → Green → Refactor
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
|
|
58
|
+
1. RED: Write a failing test
|
|
59
|
+
- Define what you want to build
|
|
60
|
+
- Test should fail (no implementation yet)
|
|
61
|
+
|
|
62
|
+
2. GREEN: Write minimum code to pass
|
|
63
|
+
- Implement just enough to make test pass
|
|
64
|
+
- Don't worry about perfection yet
|
|
65
|
+
|
|
66
|
+
3. REFACTOR: Improve the code
|
|
67
|
+
- Clean up implementation
|
|
68
|
+
- Tests stay green
|
|
69
|
+
- Improve design
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
|
|
73
|
+
### Example TDD Cycle
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// 1. RED: Write failing test
|
|
77
|
+
describe('UserService', () => {
|
|
78
|
+
it('should create user with valid data', async () => {
|
|
79
|
+
const input = {
|
|
80
|
+
name: 'John Doe',
|
|
81
|
+
email: 'john@example.com',
|
|
82
|
+
role: 'host' as const,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = await userService.create({ input, user: adminUser });
|
|
86
|
+
|
|
87
|
+
expect(result.user).toBeDefined();
|
|
88
|
+
expect(result.user.email).toBe('john@example.com');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// 2. GREEN: Implement minimum code
|
|
93
|
+
async create({ input, user }) {
|
|
94
|
+
const newUser = await this.model.create(input);
|
|
95
|
+
return { user: newUser };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. REFACTOR: Add validation, error handling, etc.
|
|
99
|
+
async create({ input, user }) {
|
|
100
|
+
// Validate input
|
|
101
|
+
if (!input.email.includes('@')) {
|
|
102
|
+
throw new ServiceError('Invalid email', ServiceErrorCode.VALIDATION_ERROR);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check permissions
|
|
106
|
+
if (user.role !== 'admin') {
|
|
107
|
+
throw new ServiceError('Forbidden', ServiceErrorCode.FORBIDDEN);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create user
|
|
111
|
+
const newUser = await this.model.create(input);
|
|
112
|
+
|
|
113
|
+
return { user: newUser };
|
|
114
|
+
}
|
|
115
|
+
```text
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Test Types
|
|
120
|
+
|
|
121
|
+
### Unit Tests
|
|
122
|
+
|
|
123
|
+
**Purpose:** Test individual functions/methods in isolation
|
|
124
|
+
|
|
125
|
+
**Characteristics:**
|
|
126
|
+
|
|
127
|
+
- Fast (< 1ms per test)
|
|
128
|
+
- No external dependencies
|
|
129
|
+
- Mock all dependencies
|
|
130
|
+
- Test single responsibility
|
|
131
|
+
|
|
132
|
+
**Example:**
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
describe('calculateBookingPrice', () => {
|
|
136
|
+
it('should calculate price for 3 nights', () => {
|
|
137
|
+
const input = {
|
|
138
|
+
pricePerNight: 100,
|
|
139
|
+
nights: 3,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = calculateBookingPrice(input);
|
|
143
|
+
|
|
144
|
+
expect(result.totalPrice).toBe(300);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should apply discount for 7+ nights', () => {
|
|
148
|
+
const input = {
|
|
149
|
+
pricePerNight: 100,
|
|
150
|
+
nights: 7,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const result = calculateBookingPrice(input);
|
|
154
|
+
|
|
155
|
+
expect(result.totalPrice).toBe(630); // 10% discount
|
|
156
|
+
expect(result.discount).toBe(70);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
```text
|
|
160
|
+
|
|
161
|
+
### Integration Tests
|
|
162
|
+
|
|
163
|
+
**Purpose:** Test interaction between components
|
|
164
|
+
|
|
165
|
+
**Characteristics:**
|
|
166
|
+
|
|
167
|
+
- Moderate speed (< 100ms per test)
|
|
168
|
+
- Real database (test database)
|
|
169
|
+
- Real services
|
|
170
|
+
- Mock only external APIs
|
|
171
|
+
|
|
172
|
+
**Example:**
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
describe('EntityService Integration', () => {
|
|
176
|
+
let service: EntityService;
|
|
177
|
+
let testDb: Database;
|
|
178
|
+
|
|
179
|
+
beforeEach(async () => {
|
|
180
|
+
testDb = await createTestDatabase();
|
|
181
|
+
service = new EntityService({ db: testDb });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
afterEach(async () => {
|
|
185
|
+
await testDb.destroy();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should create entity and retrieve it', async () => {
|
|
189
|
+
// Arrange
|
|
190
|
+
const input = {
|
|
191
|
+
name: 'Beach House',
|
|
192
|
+
type: 'house' as const,
|
|
193
|
+
capacity: 6,
|
|
194
|
+
pricePerNight: 150,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Act - Create
|
|
198
|
+
const created = await service.create({ input, user: hostUser });
|
|
199
|
+
|
|
200
|
+
// Act - Retrieve
|
|
201
|
+
const retrieved = await service.findById({ id: created.entity.id });
|
|
202
|
+
|
|
203
|
+
// Assert
|
|
204
|
+
expect(retrieved.entity.name).toBe('Beach House');
|
|
205
|
+
expect(retrieved.entity.capacity).toBe(6);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
```text
|
|
209
|
+
|
|
210
|
+
### E2E Tests
|
|
211
|
+
|
|
212
|
+
**Purpose:** Test complete user flows
|
|
213
|
+
|
|
214
|
+
**Characteristics:**
|
|
215
|
+
|
|
216
|
+
- Slow (1-5 seconds per test)
|
|
217
|
+
- Real browser (Playwright/Cypress)
|
|
218
|
+
- Real API calls
|
|
219
|
+
- Test from user perspective
|
|
220
|
+
|
|
221
|
+
**Example:**
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
describe('Booking Flow', () => {
|
|
225
|
+
it('should allow user to search, view, and book entity', async () => {
|
|
226
|
+
// 1. Navigate to search
|
|
227
|
+
await page.goto('/entitys');
|
|
228
|
+
|
|
229
|
+
// 2. Search for entity
|
|
230
|
+
await page.fill('[name="search"]', 'Beach House');
|
|
231
|
+
await page.click('button[type="submit"]');
|
|
232
|
+
|
|
233
|
+
// 3. View details
|
|
234
|
+
await page.click('text=Beach House');
|
|
235
|
+
expect(page.url()).toContain('/entitys/');
|
|
236
|
+
|
|
237
|
+
// 4. Select dates
|
|
238
|
+
await page.fill('[name="checkIn"]', '2024-07-01');
|
|
239
|
+
await page.fill('[name="checkOut"]', '2024-07-05');
|
|
240
|
+
|
|
241
|
+
// 5. Book
|
|
242
|
+
await page.click('text=Book Now');
|
|
243
|
+
|
|
244
|
+
// 6. Verify confirmation
|
|
245
|
+
await expect(page.locator('text=Booking Confirmed')).toBeVisible();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
```text
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Coverage Requirements
|
|
253
|
+
|
|
254
|
+
### Minimum Coverage: 90%
|
|
255
|
+
|
|
256
|
+
**What to test:**
|
|
257
|
+
|
|
258
|
+
- All public functions/methods
|
|
259
|
+
- All API endpoints
|
|
260
|
+
- All business logic paths
|
|
261
|
+
- All error cases
|
|
262
|
+
- All edge cases
|
|
263
|
+
|
|
264
|
+
**What NOT to test:**
|
|
265
|
+
|
|
266
|
+
- Private functions (test through public API)
|
|
267
|
+
- Type definitions
|
|
268
|
+
- Third-party libraries
|
|
269
|
+
- Constants
|
|
270
|
+
|
|
271
|
+
### Coverage by Layer
|
|
272
|
+
|
|
273
|
+
| Layer | Unit | Integration | E2E |
|
|
274
|
+
|-------|------|-------------|-----|
|
|
275
|
+
| Models | ✅ All methods | ✅ Database operations | ❌ |
|
|
276
|
+
| Services | ✅ All methods | ✅ With database | ❌ |
|
|
277
|
+
| API Routes | ✅ Logic | ✅ Endpoints | ✅ Critical flows |
|
|
278
|
+
| Components | ✅ Logic | ✅ User interactions | ✅ Main flows |
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Test Structure
|
|
283
|
+
|
|
284
|
+
### AAA Pattern (Arrange, Act, Assert)
|
|
285
|
+
|
|
286
|
+
**Always use AAA structure:**
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
it('should create user with valid data', async () => {
|
|
290
|
+
// ARRANGE: Setup test data and dependencies
|
|
291
|
+
const input = {
|
|
292
|
+
name: 'John Doe',
|
|
293
|
+
email: 'john@example.com',
|
|
294
|
+
role: 'host' as const,
|
|
295
|
+
};
|
|
296
|
+
const mockUser = createTestUser({ role: 'admin' });
|
|
297
|
+
|
|
298
|
+
// ACT: Execute the function being tested
|
|
299
|
+
const result = await userService.create({ input, user: mockUser });
|
|
300
|
+
|
|
301
|
+
// ASSERT: Verify the result
|
|
302
|
+
expect(result.user).toBeDefined();
|
|
303
|
+
expect(result.user.name).toBe('John Doe');
|
|
304
|
+
expect(result.user.email).toBe('john@example.com');
|
|
305
|
+
});
|
|
306
|
+
```text
|
|
307
|
+
|
|
308
|
+
### Given-When-Then (for Acceptance Tests)
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
it('Given user is admin, When creating user, Then user is created', async () => {
|
|
312
|
+
// GIVEN: Setup preconditions
|
|
313
|
+
const adminUser = createTestUser({ role: 'admin' });
|
|
314
|
+
const inputData = createTestUserInput();
|
|
315
|
+
|
|
316
|
+
// WHEN: Perform action
|
|
317
|
+
const result = await userService.create({ input: inputData, user: adminUser });
|
|
318
|
+
|
|
319
|
+
// THEN: Verify outcome
|
|
320
|
+
expect(result.user.id).toBeDefined();
|
|
321
|
+
expect(result.user.role).toBe('host');
|
|
322
|
+
});
|
|
323
|
+
```text
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Test File Organization
|
|
328
|
+
|
|
329
|
+
### Test Folder Structure
|
|
330
|
+
|
|
331
|
+
**Rule:** All tests MUST be placed in a `test/` folder at the root of each package/app, mirroring the source folder structure.
|
|
332
|
+
|
|
333
|
+
**Structure:**
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
packages/my-package/
|
|
337
|
+
├── src/
|
|
338
|
+
│ ├── services/
|
|
339
|
+
│ │ └── user-service.ts
|
|
340
|
+
│ ├── utils/
|
|
341
|
+
│ │ └── validators.ts
|
|
342
|
+
│ └── index.ts
|
|
343
|
+
└── test/
|
|
344
|
+
├── services/
|
|
345
|
+
│ └── user-service.test.ts
|
|
346
|
+
├── utils/
|
|
347
|
+
│ └── validators.test.ts
|
|
348
|
+
└── index.test.ts
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Examples:**
|
|
352
|
+
|
|
353
|
+
```text
|
|
354
|
+
✅ CORRECT:
|
|
355
|
+
Source: packages/db/src/models/user.model.ts
|
|
356
|
+
Test: packages/db/test/models/user.model.test.ts
|
|
357
|
+
|
|
358
|
+
Source: packages/service-core/src/services/booking/booking-service.ts
|
|
359
|
+
Test: packages/service-core/test/services/booking/booking-service.test.ts
|
|
360
|
+
|
|
361
|
+
Source: apps/api/src/routes/entitys.ts
|
|
362
|
+
Test: apps/api/test/routes/entitys.test.ts
|
|
363
|
+
|
|
364
|
+
❌ INCORRECT:
|
|
365
|
+
Source: packages/db/src/models/user.model.ts
|
|
366
|
+
Test: packages/db/src/models/user.model.test.ts (tests should NOT be in src/)
|
|
367
|
+
|
|
368
|
+
Source: packages/db/src/models/user.model.ts
|
|
369
|
+
Test: packages/db/test/user.model.test.ts (missing models/ folder)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Import Paths in Tests
|
|
373
|
+
|
|
374
|
+
When importing from source files, use relative paths from test/ to src/:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// ✅ CORRECT: test/services/user-service.test.ts
|
|
378
|
+
import { UserService } from '../src/services/user-service.js';
|
|
379
|
+
|
|
380
|
+
// ✅ CORRECT: test/models/user.model.test.ts
|
|
381
|
+
import { UserModel } from '../src/models/user.model.js';
|
|
382
|
+
|
|
383
|
+
// ❌ INCORRECT: Don't use same-folder imports
|
|
384
|
+
import { UserService } from './user-service.js';
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Vitest Configuration
|
|
388
|
+
|
|
389
|
+
Vitest automatically discovers tests in `test/` folders. No special configuration needed:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// vitest.config.ts
|
|
393
|
+
export default defineConfig({
|
|
394
|
+
test: {
|
|
395
|
+
globals: true,
|
|
396
|
+
environment: 'node',
|
|
397
|
+
coverage: {
|
|
398
|
+
provider: 'v8',
|
|
399
|
+
exclude: [
|
|
400
|
+
'node_modules/',
|
|
401
|
+
'dist/',
|
|
402
|
+
'**/*.test.ts', // Excludes tests wherever they are
|
|
403
|
+
'**/*.config.ts',
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Naming Conventions
|
|
413
|
+
|
|
414
|
+
### Test File Names
|
|
415
|
+
|
|
416
|
+
```text
|
|
417
|
+
{module-name}.test.ts
|
|
418
|
+
{module-name}.integration.test.ts
|
|
419
|
+
{module-name}.e2e.test.ts
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Examples:**
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
user-service.test.ts
|
|
426
|
+
user-service.integration.test.ts
|
|
427
|
+
booking-flow.e2e.test.ts
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Test Description
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// ✅ GOOD: Descriptive, behavior-focused
|
|
434
|
+
describe('UserService', () => {
|
|
435
|
+
describe('create', () => {
|
|
436
|
+
it('should create user with valid data', async () => {});
|
|
437
|
+
it('should throw error when email already exists', async () => {});
|
|
438
|
+
it('should throw error when user lacks permissions', async () => {});
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ❌ BAD: Implementation-focused
|
|
443
|
+
describe('UserService', () => {
|
|
444
|
+
it('test create', async () => {});
|
|
445
|
+
it('test validation', async () => {});
|
|
446
|
+
});
|
|
447
|
+
```text
|
|
448
|
+
|
|
449
|
+
### Variable Names
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// ✅ GOOD: Clear, descriptive names
|
|
453
|
+
const inputValid = { name: 'John', email: 'john@example.com' };
|
|
454
|
+
const inputInvalid = { name: '', email: 'invalid' };
|
|
455
|
+
const mockUser = createTestUser();
|
|
456
|
+
const expectedResult = { id: '123', name: 'John' };
|
|
457
|
+
const actualResult = await service.create({ input: inputValid, user: mockUser });
|
|
458
|
+
|
|
459
|
+
// ❌ BAD: Unclear names
|
|
460
|
+
const data = { name: 'John' };
|
|
461
|
+
const result = await service.create({ input: data });
|
|
462
|
+
```text
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Test Patterns
|
|
467
|
+
|
|
468
|
+
### Setup and Teardown
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
describe('UserService', () => {
|
|
472
|
+
let service: UserService;
|
|
473
|
+
let db: Database;
|
|
474
|
+
let testUser: User;
|
|
475
|
+
|
|
476
|
+
// Run before all tests in this suite
|
|
477
|
+
beforeAll(async () => {
|
|
478
|
+
db = await createTestDatabase();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Run before each test
|
|
482
|
+
beforeEach(async () => {
|
|
483
|
+
service = new UserService({ db });
|
|
484
|
+
testUser = await createTestUser();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Run after each test
|
|
488
|
+
afterEach(async () => {
|
|
489
|
+
await cleanupTestData();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Run after all tests
|
|
493
|
+
afterAll(async () => {
|
|
494
|
+
await db.destroy();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('should work', async () => {
|
|
498
|
+
// Test has access to service, db, testUser
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
```text
|
|
502
|
+
|
|
503
|
+
### Parameterized Tests
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
describe('validateEmail', () => {
|
|
507
|
+
const testCases = [
|
|
508
|
+
{ email: 'valid@example.com', expected: true },
|
|
509
|
+
{ email: 'invalid', expected: false },
|
|
510
|
+
{ email: 'missing@domain', expected: false },
|
|
511
|
+
{ email: '@example.com', expected: false },
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
testCases.forEach(({ email, expected }) => {
|
|
515
|
+
it(`should return ${expected} for "${email}"`, () => {
|
|
516
|
+
const result = validateEmail(email);
|
|
517
|
+
expect(result).toBe(expected);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
```text
|
|
522
|
+
|
|
523
|
+
### Testing Errors
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
describe('UserService', () => {
|
|
527
|
+
it('should throw NOT_FOUND when user does not exist', async () => {
|
|
528
|
+
await expect(
|
|
529
|
+
service.findById({ id: 'non-existent-id' })
|
|
530
|
+
).rejects.toThrow(ServiceError);
|
|
531
|
+
|
|
532
|
+
await expect(
|
|
533
|
+
service.findById({ id: 'non-existent-id' })
|
|
534
|
+
).rejects.toMatchObject({
|
|
535
|
+
code: ServiceErrorCode.NOT_FOUND,
|
|
536
|
+
message: expect.stringContaining('not found'),
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should throw FORBIDDEN when user lacks permissions', async () => {
|
|
541
|
+
const guestUser = createTestUser({ role: 'guest' });
|
|
542
|
+
|
|
543
|
+
await expect(
|
|
544
|
+
service.create({ input: validInput, user: guestUser })
|
|
545
|
+
).rejects.toThrow('Forbidden');
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
```text
|
|
549
|
+
|
|
550
|
+
### Testing Async Code
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// ✅ GOOD: Using async/await
|
|
554
|
+
it('should fetch user data', async () => {
|
|
555
|
+
const result = await fetchUser('123');
|
|
556
|
+
expect(result.name).toBe('John');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// ✅ GOOD: Using resolves/rejects
|
|
560
|
+
it('should fetch user data', () => {
|
|
561
|
+
return expect(fetchUser('123')).resolves.toMatchObject({
|
|
562
|
+
name: 'John',
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// ❌ BAD: Not handling async
|
|
567
|
+
it('should fetch user data', () => {
|
|
568
|
+
fetchUser('123'); // Test will pass before promise resolves!
|
|
569
|
+
expect(result.name).toBe('John'); // result is undefined
|
|
570
|
+
});
|
|
571
|
+
```text
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Mocking & Fixtures
|
|
576
|
+
|
|
577
|
+
### Mock External Dependencies
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
import { vi } from 'vitest';
|
|
581
|
+
|
|
582
|
+
describe('BookingService', () => {
|
|
583
|
+
let emailService: EmailService;
|
|
584
|
+
|
|
585
|
+
beforeEach(() => {
|
|
586
|
+
// Mock email service
|
|
587
|
+
emailService = {
|
|
588
|
+
sendConfirmation: vi.fn(),
|
|
589
|
+
} as unknown as EmailService;
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should send confirmation email after booking', async () => {
|
|
593
|
+
const service = new BookingService({ db, emailService });
|
|
594
|
+
|
|
595
|
+
await service.create({ input: bookingData, user });
|
|
596
|
+
|
|
597
|
+
expect(emailService.sendConfirmation).toHaveBeenCalledWith({
|
|
598
|
+
to: user.email,
|
|
599
|
+
booking: expect.objectContaining({ id: expect.any(String) }),
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
```text
|
|
604
|
+
|
|
605
|
+
### Test Fixtures
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
// test/fixtures/user.fixture.ts
|
|
609
|
+
|
|
610
|
+
export const createTestUser = (overrides?: Partial<User>): User => ({
|
|
611
|
+
id: randomUUID(),
|
|
612
|
+
name: 'Test User',
|
|
613
|
+
email: 'test@example.com',
|
|
614
|
+
role: 'host',
|
|
615
|
+
createdAt: new Date(),
|
|
616
|
+
updatedAt: new Date(),
|
|
617
|
+
deletedAt: null,
|
|
618
|
+
...overrides,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
export const createTestAdmin = (): User =>
|
|
622
|
+
createTestUser({ role: 'admin', email: 'admin@example.com' });
|
|
623
|
+
|
|
624
|
+
export const createTestGuest = (): User =>
|
|
625
|
+
createTestUser({ role: 'guest', email: 'guest@example.com' });
|
|
626
|
+
```text
|
|
627
|
+
|
|
628
|
+
### Factory Pattern
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// test/factories/entity.factory.ts
|
|
632
|
+
|
|
633
|
+
let counter = 0;
|
|
634
|
+
|
|
635
|
+
export const buildEntity = (
|
|
636
|
+
overrides?: Partial<Entity>
|
|
637
|
+
): Entity => {
|
|
638
|
+
counter++;
|
|
639
|
+
return {
|
|
640
|
+
id: randomUUID(),
|
|
641
|
+
name: `Test Entity ${counter}`,
|
|
642
|
+
type: 'house',
|
|
643
|
+
capacity: 4,
|
|
644
|
+
pricePerNight: 100,
|
|
645
|
+
hostId: randomUUID(),
|
|
646
|
+
createdAt: new Date(),
|
|
647
|
+
updatedAt: new Date(),
|
|
648
|
+
deletedAt: null,
|
|
649
|
+
...overrides,
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
```text
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## Database Testing
|
|
657
|
+
|
|
658
|
+
### Use Test Database
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
// test/setup.ts
|
|
662
|
+
|
|
663
|
+
let testDb: Database;
|
|
664
|
+
|
|
665
|
+
export const setupTestDatabase = async () => {
|
|
666
|
+
testDb = await createConnection({
|
|
667
|
+
host: 'localhost',
|
|
668
|
+
port: 5433, // Different port for test DB
|
|
669
|
+
database: 'project_test',
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// Run migrations
|
|
673
|
+
await migrate(testDb);
|
|
674
|
+
|
|
675
|
+
return testDb;
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
export const teardownTestDatabase = async () => {
|
|
679
|
+
await testDb.destroy();
|
|
680
|
+
};
|
|
681
|
+
```text
|
|
682
|
+
|
|
683
|
+
### Clean Between Tests
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
beforeEach(async () => {
|
|
687
|
+
// Clean all tables
|
|
688
|
+
await db.delete(bookings);
|
|
689
|
+
await db.delete(entitys);
|
|
690
|
+
await db.delete(users);
|
|
691
|
+
});
|
|
692
|
+
```text
|
|
693
|
+
|
|
694
|
+
### Transaction Rollback Pattern
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
describe('UserService', () => {
|
|
698
|
+
let trx: Transaction;
|
|
699
|
+
|
|
700
|
+
beforeEach(async () => {
|
|
701
|
+
trx = await db.transaction();
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
afterEach(async () => {
|
|
705
|
+
await trx.rollback(); // Rollback all changes
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('should create user', async () => {
|
|
709
|
+
const service = new UserService({ db: trx });
|
|
710
|
+
// Test...
|
|
711
|
+
// Changes are rolled back after test
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
```text
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## API Testing
|
|
719
|
+
|
|
720
|
+
### Test API Endpoints
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
import { app } from '../src/app';
|
|
724
|
+
|
|
725
|
+
describe('POST /api/entitys', () => {
|
|
726
|
+
it('should create entity with valid data', async () => {
|
|
727
|
+
const input = {
|
|
728
|
+
name: 'Beach House',
|
|
729
|
+
type: 'house',
|
|
730
|
+
capacity: 6,
|
|
731
|
+
pricePerNight: 150,
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const response = await app.request('/api/entitys', {
|
|
735
|
+
method: 'POST',
|
|
736
|
+
body: JSON.stringify(input),
|
|
737
|
+
headers: {
|
|
738
|
+
'Content-Type': 'application/json',
|
|
739
|
+
'Authorization': `Bearer ${hostToken}`,
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
expect(response.status).toBe(201);
|
|
744
|
+
|
|
745
|
+
const body = await response.json();
|
|
746
|
+
expect(body.success).toBe(true);
|
|
747
|
+
expect(body.data.entity.name).toBe('Beach House');
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('should return 401 when not authenticated', async () => {
|
|
751
|
+
const response = await app.request('/api/entitys', {
|
|
752
|
+
method: 'POST',
|
|
753
|
+
body: JSON.stringify({}),
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
expect(response.status).toBe(401);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
it('should return 400 when validation fails', async () => {
|
|
760
|
+
const invalidInput = {
|
|
761
|
+
name: '', // Empty name
|
|
762
|
+
type: 'invalid-type',
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const response = await app.request('/api/entitys', {
|
|
766
|
+
method: 'POST',
|
|
767
|
+
body: JSON.stringify(invalidInput),
|
|
768
|
+
headers: {
|
|
769
|
+
'Authorization': `Bearer ${hostToken}`,
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
expect(response.status).toBe(400);
|
|
774
|
+
const body = await response.json();
|
|
775
|
+
expect(body.success).toBe(false);
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
```text
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## Frontend Testing
|
|
783
|
+
|
|
784
|
+
### Component Testing
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
788
|
+
import { EntityCard } from './EntityCard';
|
|
789
|
+
|
|
790
|
+
describe('EntityCard', () => {
|
|
791
|
+
const mockEntity = {
|
|
792
|
+
id: '123',
|
|
793
|
+
name: 'Beach House',
|
|
794
|
+
type: 'house',
|
|
795
|
+
capacity: 6,
|
|
796
|
+
pricePerNight: 150,
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
it('should render entity details', () => {
|
|
800
|
+
render(<EntityCard entity={mockEntity} />);
|
|
801
|
+
|
|
802
|
+
expect(screen.getByText('Beach House')).toBeInTheDocument();
|
|
803
|
+
expect(screen.getByText('$150 / night')).toBeInTheDocument();
|
|
804
|
+
expect(screen.getByText('6 guests')).toBeInTheDocument();
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should call onSelect when clicked', () => {
|
|
808
|
+
const mockOnSelect = vi.fn();
|
|
809
|
+
|
|
810
|
+
render(
|
|
811
|
+
<EntityCard
|
|
812
|
+
entity={mockEntity}
|
|
813
|
+
onSelect={mockOnSelect}
|
|
814
|
+
/>
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
fireEvent.click(screen.getByRole('button'));
|
|
818
|
+
|
|
819
|
+
expect(mockOnSelect).toHaveBeenCalledWith('123');
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
```text
|
|
823
|
+
|
|
824
|
+
### Testing with TanStack Query
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
828
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
829
|
+
import { useEntitys } from './useEntitys';
|
|
830
|
+
|
|
831
|
+
describe('useEntitys', () => {
|
|
832
|
+
let queryClient: QueryClient;
|
|
833
|
+
|
|
834
|
+
beforeEach(() => {
|
|
835
|
+
queryClient = new QueryClient({
|
|
836
|
+
defaultOptions: {
|
|
837
|
+
queries: { retry: false },
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it('should fetch entitys', async () => {
|
|
843
|
+
const wrapper = ({ children }) => (
|
|
844
|
+
<QueryClientProvider client={queryClient}>
|
|
845
|
+
{children}
|
|
846
|
+
</QueryClientProvider>
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
const { result } = renderHook(() => useEntitys(), { wrapper });
|
|
850
|
+
|
|
851
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
852
|
+
|
|
853
|
+
expect(result.current.data).toHaveLength(3);
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
```text
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
## Common Pitfalls
|
|
861
|
+
|
|
862
|
+
### ❌ Testing Implementation Details
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
// ❌ BAD: Testing private methods
|
|
866
|
+
it('should call _validateEmail internally', () => {
|
|
867
|
+
const spy = vi.spyOn(service, '_validateEmail');
|
|
868
|
+
service.create({ input });
|
|
869
|
+
expect(spy).toHaveBeenCalled();
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// ✅ GOOD: Testing behavior
|
|
873
|
+
it('should reject invalid email', async () => {
|
|
874
|
+
await expect(
|
|
875
|
+
service.create({ input: { email: 'invalid' } })
|
|
876
|
+
).rejects.toThrow('Invalid email');
|
|
877
|
+
});
|
|
878
|
+
```text
|
|
879
|
+
|
|
880
|
+
### ❌ Brittle Tests
|
|
881
|
+
|
|
882
|
+
```typescript
|
|
883
|
+
// ❌ BAD: Too specific, will break on small changes
|
|
884
|
+
expect(result).toEqual({
|
|
885
|
+
id: '123',
|
|
886
|
+
name: 'John',
|
|
887
|
+
email: 'john@example.com',
|
|
888
|
+
createdAt: new Date('2024-01-01'),
|
|
889
|
+
updatedAt: new Date('2024-01-01'),
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// ✅ GOOD: Focus on important parts
|
|
893
|
+
expect(result).toMatchObject({
|
|
894
|
+
name: 'John',
|
|
895
|
+
email: 'john@example.com',
|
|
896
|
+
});
|
|
897
|
+
expect(result.id).toBeDefined();
|
|
898
|
+
expect(result.createdAt).toBeInstanceOf(Date);
|
|
899
|
+
```text
|
|
900
|
+
|
|
901
|
+
### ❌ Shared State
|
|
902
|
+
|
|
903
|
+
```typescript
|
|
904
|
+
// ❌ BAD: Tests affect each other
|
|
905
|
+
let user;
|
|
906
|
+
|
|
907
|
+
beforeAll(() => {
|
|
908
|
+
user = createTestUser();
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
it('test 1', () => {
|
|
912
|
+
user.name = 'Changed'; // Affects other tests!
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// ✅ GOOD: Fresh state per test
|
|
916
|
+
beforeEach(() => {
|
|
917
|
+
user = createTestUser();
|
|
918
|
+
});
|
|
919
|
+
```text
|
|
920
|
+
|
|
921
|
+
### ❌ Not Testing Edge Cases
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
// ❌ BAD: Only happy path
|
|
925
|
+
it('should calculate price', () => {
|
|
926
|
+
expect(calculatePrice(100, 3)).toBe(300);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// ✅ GOOD: Test edge cases
|
|
930
|
+
describe('calculatePrice', () => {
|
|
931
|
+
it('should calculate price for positive values', () => {
|
|
932
|
+
expect(calculatePrice(100, 3)).toBe(300);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
it('should throw for zero nights', () => {
|
|
936
|
+
expect(() => calculatePrice(100, 0)).toThrow();
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it('should throw for negative nights', () => {
|
|
940
|
+
expect(() => calculatePrice(100, -1)).toThrow();
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
it('should handle large numbers', () => {
|
|
944
|
+
expect(calculatePrice(1000000, 365)).toBe(365000000);
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
```text
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
## Summary Checklist
|
|
952
|
+
|
|
953
|
+
Before considering testing complete:
|
|
954
|
+
|
|
955
|
+
- [ ] 90%+ code coverage
|
|
956
|
+
- [ ] All public functions tested
|
|
957
|
+
- [ ] All API endpoints tested
|
|
958
|
+
- [ ] All error cases tested
|
|
959
|
+
- [ ] All edge cases tested
|
|
960
|
+
- [ ] Tests follow AAA pattern
|
|
961
|
+
- [ ] Tests are independent
|
|
962
|
+
- [ ] Tests are fast (< 100ms unit, < 1s integration)
|
|
963
|
+
- [ ] No flaky tests
|
|
964
|
+
- [ ] Tests document behavior
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
**TDD is mandatory. Tests must be written BEFORE implementation.**
|
|
969
|
+
|