@girardmedia/bootspring 1.2.0 → 2.0.3
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 +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
# Component Testing Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for testing React components.
|
|
4
|
-
|
|
5
|
-
## Basic Component Test
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
// __tests__/components/Button.test.tsx
|
|
9
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
10
|
-
import { Button } from '@/components/ui/Button'
|
|
11
|
-
|
|
12
|
-
describe('Button', () => {
|
|
13
|
-
it('renders with text', () => {
|
|
14
|
-
render(<Button>Click me</Button>)
|
|
15
|
-
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('calls onClick when clicked', () => {
|
|
19
|
-
const handleClick = vi.fn()
|
|
20
|
-
render(<Button onClick={handleClick}>Click me</Button>)
|
|
21
|
-
|
|
22
|
-
fireEvent.click(screen.getByRole('button'))
|
|
23
|
-
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('is disabled when disabled prop is true', () => {
|
|
27
|
-
render(<Button disabled>Click me</Button>)
|
|
28
|
-
expect(screen.getByRole('button')).toBeDisabled()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('renders with different variants', () => {
|
|
32
|
-
const { rerender } = render(<Button variant="primary">Primary</Button>)
|
|
33
|
-
expect(screen.getByRole('button')).toHaveClass('bg-blue-600')
|
|
34
|
-
|
|
35
|
-
rerender(<Button variant="secondary">Secondary</Button>)
|
|
36
|
-
expect(screen.getByRole('button')).toHaveClass('bg-gray-200')
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Testing with User Events
|
|
42
|
-
|
|
43
|
-
```tsx
|
|
44
|
-
// __tests__/components/Form.test.tsx
|
|
45
|
-
import { render, screen, waitFor } from '@testing-library/react'
|
|
46
|
-
import userEvent from '@testing-library/user-event'
|
|
47
|
-
import { ContactForm } from '@/components/ContactForm'
|
|
48
|
-
|
|
49
|
-
describe('ContactForm', () => {
|
|
50
|
-
it('submits form with valid data', async () => {
|
|
51
|
-
const user = userEvent.setup()
|
|
52
|
-
const onSubmit = vi.fn()
|
|
53
|
-
|
|
54
|
-
render(<ContactForm onSubmit={onSubmit} />)
|
|
55
|
-
|
|
56
|
-
await user.type(screen.getByLabelText('Name'), 'John Doe')
|
|
57
|
-
await user.type(screen.getByLabelText('Email'), 'john@example.com')
|
|
58
|
-
await user.type(screen.getByLabelText('Message'), 'Hello, this is a test message')
|
|
59
|
-
|
|
60
|
-
await user.click(screen.getByRole('button', { name: 'Submit' }))
|
|
61
|
-
|
|
62
|
-
await waitFor(() => {
|
|
63
|
-
expect(onSubmit).toHaveBeenCalledWith({
|
|
64
|
-
name: 'John Doe',
|
|
65
|
-
email: 'john@example.com',
|
|
66
|
-
message: 'Hello, this is a test message'
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('shows validation errors', async () => {
|
|
72
|
-
const user = userEvent.setup()
|
|
73
|
-
render(<ContactForm onSubmit={vi.fn()} />)
|
|
74
|
-
|
|
75
|
-
await user.click(screen.getByRole('button', { name: 'Submit' }))
|
|
76
|
-
|
|
77
|
-
expect(await screen.findByText('Name is required')).toBeInTheDocument()
|
|
78
|
-
expect(screen.getByText('Email is required')).toBeInTheDocument()
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Testing Async Components
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
// __tests__/components/UserList.test.tsx
|
|
87
|
-
import { render, screen, waitFor } from '@testing-library/react'
|
|
88
|
-
import { UserList } from '@/components/UserList'
|
|
89
|
-
import { server } from '@/mocks/server'
|
|
90
|
-
import { http, HttpResponse } from 'msw'
|
|
91
|
-
|
|
92
|
-
describe('UserList', () => {
|
|
93
|
-
it('renders users after loading', async () => {
|
|
94
|
-
server.use(
|
|
95
|
-
http.get('/api/users', () => {
|
|
96
|
-
return HttpResponse.json([
|
|
97
|
-
{ id: '1', name: 'Alice' },
|
|
98
|
-
{ id: '2', name: 'Bob' }
|
|
99
|
-
])
|
|
100
|
-
})
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
render(<UserList />)
|
|
104
|
-
|
|
105
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
106
|
-
|
|
107
|
-
await waitFor(() => {
|
|
108
|
-
expect(screen.getByText('Alice')).toBeInTheDocument()
|
|
109
|
-
expect(screen.getByText('Bob')).toBeInTheDocument()
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('shows error message on failure', async () => {
|
|
114
|
-
server.use(
|
|
115
|
-
http.get('/api/users', () => {
|
|
116
|
-
return HttpResponse.json({ error: 'Server error' }, { status: 500 })
|
|
117
|
-
})
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
render(<UserList />)
|
|
121
|
-
|
|
122
|
-
await waitFor(() => {
|
|
123
|
-
expect(screen.getByText('Failed to load users')).toBeInTheDocument()
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Testing with Context
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
// __tests__/components/ThemeToggle.test.tsx
|
|
133
|
-
import { render, screen } from '@testing-library/react'
|
|
134
|
-
import userEvent from '@testing-library/user-event'
|
|
135
|
-
import { ThemeProvider } from '@/contexts/ThemeContext'
|
|
136
|
-
import { ThemeToggle } from '@/components/ThemeToggle'
|
|
137
|
-
|
|
138
|
-
function renderWithTheme(component: React.ReactNode) {
|
|
139
|
-
return render(
|
|
140
|
-
<ThemeProvider>{component}</ThemeProvider>
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
describe('ThemeToggle', () => {
|
|
145
|
-
it('toggles theme on click', async () => {
|
|
146
|
-
const user = userEvent.setup()
|
|
147
|
-
renderWithTheme(<ThemeToggle />)
|
|
148
|
-
|
|
149
|
-
const button = screen.getByRole('button')
|
|
150
|
-
expect(button).toHaveAttribute('aria-label', 'Switch to dark mode')
|
|
151
|
-
|
|
152
|
-
await user.click(button)
|
|
153
|
-
expect(button).toHaveAttribute('aria-label', 'Switch to light mode')
|
|
154
|
-
})
|
|
155
|
-
})
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Testing Hooks
|
|
159
|
-
|
|
160
|
-
```tsx
|
|
161
|
-
// __tests__/hooks/useCounter.test.ts
|
|
162
|
-
import { renderHook, act } from '@testing-library/react'
|
|
163
|
-
import { useCounter } from '@/hooks/useCounter'
|
|
164
|
-
|
|
165
|
-
describe('useCounter', () => {
|
|
166
|
-
it('initializes with default value', () => {
|
|
167
|
-
const { result } = renderHook(() => useCounter())
|
|
168
|
-
expect(result.current.count).toBe(0)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('initializes with provided value', () => {
|
|
172
|
-
const { result } = renderHook(() => useCounter(10))
|
|
173
|
-
expect(result.current.count).toBe(10)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('increments count', () => {
|
|
177
|
-
const { result } = renderHook(() => useCounter())
|
|
178
|
-
|
|
179
|
-
act(() => {
|
|
180
|
-
result.current.increment()
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
expect(result.current.count).toBe(1)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('decrements count', () => {
|
|
187
|
-
const { result } = renderHook(() => useCounter(5))
|
|
188
|
-
|
|
189
|
-
act(() => {
|
|
190
|
-
result.current.decrement()
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
expect(result.current.count).toBe(4)
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Testing Server Components
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
// __tests__/components/UserProfile.test.tsx
|
|
202
|
-
// For Server Components, test the rendered output
|
|
203
|
-
import { render, screen } from '@testing-library/react'
|
|
204
|
-
|
|
205
|
-
// Mock the data fetching
|
|
206
|
-
vi.mock('@/lib/users', () => ({
|
|
207
|
-
getUser: vi.fn().mockResolvedValue({
|
|
208
|
-
id: '1',
|
|
209
|
-
name: 'John Doe',
|
|
210
|
-
email: 'john@example.com'
|
|
211
|
-
})
|
|
212
|
-
}))
|
|
213
|
-
|
|
214
|
-
// Import after mocking
|
|
215
|
-
import { UserProfile } from '@/components/UserProfile'
|
|
216
|
-
|
|
217
|
-
describe('UserProfile', () => {
|
|
218
|
-
it('renders user information', async () => {
|
|
219
|
-
const Component = await UserProfile({ userId: '1' })
|
|
220
|
-
render(Component)
|
|
221
|
-
|
|
222
|
-
expect(screen.getByText('John Doe')).toBeInTheDocument()
|
|
223
|
-
expect(screen.getByText('john@example.com')).toBeInTheDocument()
|
|
224
|
-
})
|
|
225
|
-
})
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## When to Use
|
|
229
|
-
|
|
230
|
-
- UI components
|
|
231
|
-
- Form validation
|
|
232
|
-
- User interactions
|
|
233
|
-
- State management
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
# Test Coverage Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for measuring and improving test coverage.
|
|
4
|
-
|
|
5
|
-
## Vitest Coverage Setup
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// vitest.config.ts
|
|
9
|
-
import { defineConfig } from 'vitest/config'
|
|
10
|
-
|
|
11
|
-
export default defineConfig({
|
|
12
|
-
test: {
|
|
13
|
-
coverage: {
|
|
14
|
-
provider: 'v8',
|
|
15
|
-
reporter: ['text', 'json', 'html'],
|
|
16
|
-
exclude: [
|
|
17
|
-
'node_modules/',
|
|
18
|
-
'tests/',
|
|
19
|
-
'**/*.d.ts',
|
|
20
|
-
'**/*.config.ts',
|
|
21
|
-
'**/types.ts'
|
|
22
|
-
],
|
|
23
|
-
thresholds: {
|
|
24
|
-
lines: 80,
|
|
25
|
-
functions: 80,
|
|
26
|
-
branches: 80,
|
|
27
|
-
statements: 80
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Run with Coverage
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
# Generate coverage report
|
|
38
|
-
npm run test -- --coverage
|
|
39
|
-
|
|
40
|
-
# Watch mode with coverage
|
|
41
|
-
npm run test -- --coverage --watch
|
|
42
|
-
|
|
43
|
-
# Coverage for specific files
|
|
44
|
-
npm run test -- --coverage src/lib/
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Coverage Thresholds
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
// vitest.config.ts - Per-file thresholds
|
|
51
|
-
export default defineConfig({
|
|
52
|
-
test: {
|
|
53
|
-
coverage: {
|
|
54
|
-
thresholds: {
|
|
55
|
-
// Global thresholds
|
|
56
|
-
lines: 80,
|
|
57
|
-
|
|
58
|
-
// Per-file thresholds
|
|
59
|
-
'src/lib/auth.ts': {
|
|
60
|
-
lines: 100,
|
|
61
|
-
functions: 100
|
|
62
|
-
},
|
|
63
|
-
'src/lib/utils.ts': {
|
|
64
|
-
lines: 90
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Istanbul Ignore Comments
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
// Ignore specific lines
|
|
76
|
-
/* istanbul ignore next */
|
|
77
|
-
if (process.env.NODE_ENV === 'development') {
|
|
78
|
-
console.log('Debug mode')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Ignore entire function
|
|
82
|
-
/* istanbul ignore next */
|
|
83
|
-
function devOnlyHelper() {
|
|
84
|
-
// This won't affect coverage
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Ignore else branch
|
|
88
|
-
if (condition) {
|
|
89
|
-
doSomething()
|
|
90
|
-
} /* istanbul ignore else */ else {
|
|
91
|
-
// Edge case that's hard to test
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Critical Path Coverage
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
// Ensure critical paths are always tested
|
|
99
|
-
// tests/critical-paths.test.ts
|
|
100
|
-
|
|
101
|
-
describe('Critical Paths', () => {
|
|
102
|
-
describe('Authentication', () => {
|
|
103
|
-
it('handles login success')
|
|
104
|
-
it('handles login failure')
|
|
105
|
-
it('handles session expiry')
|
|
106
|
-
it('handles logout')
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('Payment Processing', () => {
|
|
110
|
-
it('handles successful payment')
|
|
111
|
-
it('handles declined card')
|
|
112
|
-
it('handles webhook verification')
|
|
113
|
-
it('handles refunds')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe('Data Integrity', () => {
|
|
117
|
-
it('validates user input')
|
|
118
|
-
it('sanitizes output')
|
|
119
|
-
it('handles concurrent updates')
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Branch Coverage
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// Ensure all branches are tested
|
|
128
|
-
function getDiscount(user: User, order: Order): number {
|
|
129
|
-
// Branch 1: Premium user
|
|
130
|
-
if (user.isPremium) {
|
|
131
|
-
// Branch 1a: Large order
|
|
132
|
-
if (order.total > 100) {
|
|
133
|
-
return 0.2
|
|
134
|
-
}
|
|
135
|
-
// Branch 1b: Small order
|
|
136
|
-
return 0.1
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Branch 2: Regular user
|
|
140
|
-
if (order.total > 200) {
|
|
141
|
-
return 0.05
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Branch 3: No discount
|
|
145
|
-
return 0
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Tests for all branches
|
|
149
|
-
describe('getDiscount', () => {
|
|
150
|
-
it('returns 20% for premium user with large order', () => {
|
|
151
|
-
expect(getDiscount({ isPremium: true }, { total: 150 })).toBe(0.2)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('returns 10% for premium user with small order', () => {
|
|
155
|
-
expect(getDiscount({ isPremium: true }, { total: 50 })).toBe(0.1)
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('returns 5% for regular user with large order', () => {
|
|
159
|
-
expect(getDiscount({ isPremium: false }, { total: 250 })).toBe(0.05)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('returns 0% for regular user with small order', () => {
|
|
163
|
-
expect(getDiscount({ isPremium: false }, { total: 50 })).toBe(0)
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## CI Coverage Gates
|
|
169
|
-
|
|
170
|
-
```yaml
|
|
171
|
-
# .github/workflows/test.yml
|
|
172
|
-
name: Test
|
|
173
|
-
|
|
174
|
-
on: [push, pull_request]
|
|
175
|
-
|
|
176
|
-
jobs:
|
|
177
|
-
test:
|
|
178
|
-
runs-on: ubuntu-latest
|
|
179
|
-
steps:
|
|
180
|
-
- uses: actions/checkout@v4
|
|
181
|
-
- uses: actions/setup-node@v4
|
|
182
|
-
with:
|
|
183
|
-
node-version: '20'
|
|
184
|
-
|
|
185
|
-
- run: npm ci
|
|
186
|
-
- run: npm test -- --coverage
|
|
187
|
-
|
|
188
|
-
- name: Check coverage thresholds
|
|
189
|
-
run: |
|
|
190
|
-
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
|
|
191
|
-
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
|
|
192
|
-
echo "Coverage $COVERAGE% is below 80% threshold"
|
|
193
|
-
exit 1
|
|
194
|
-
fi
|
|
195
|
-
|
|
196
|
-
- name: Upload coverage
|
|
197
|
-
uses: codecov/codecov-action@v3
|
|
198
|
-
with:
|
|
199
|
-
files: ./coverage/lcov.info
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## When to Use
|
|
203
|
-
|
|
204
|
-
- Quality gates
|
|
205
|
-
- CI/CD pipelines
|
|
206
|
-
- Critical code paths
|
|
207
|
-
- Refactoring validation
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
# Test Fixtures Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for creating and managing test data.
|
|
4
|
-
|
|
5
|
-
## Factory Functions
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// tests/factories/user.ts
|
|
9
|
-
import { faker } from '@faker-js/faker'
|
|
10
|
-
|
|
11
|
-
interface UserInput {
|
|
12
|
-
id?: string
|
|
13
|
-
email?: string
|
|
14
|
-
name?: string
|
|
15
|
-
role?: 'USER' | 'ADMIN'
|
|
16
|
-
createdAt?: Date
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function createUser(overrides: UserInput = {}) {
|
|
20
|
-
return {
|
|
21
|
-
id: faker.string.uuid(),
|
|
22
|
-
email: faker.internet.email(),
|
|
23
|
-
name: faker.person.fullName(),
|
|
24
|
-
role: 'USER' as const,
|
|
25
|
-
createdAt: faker.date.past(),
|
|
26
|
-
...overrides
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Usage
|
|
31
|
-
const user = createUser({ role: 'ADMIN' })
|
|
32
|
-
const users = Array.from({ length: 5 }, () => createUser())
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Builder Pattern
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
// tests/factories/post.ts
|
|
39
|
-
class PostBuilder {
|
|
40
|
-
private data = {
|
|
41
|
-
id: faker.string.uuid(),
|
|
42
|
-
title: faker.lorem.sentence(),
|
|
43
|
-
content: faker.lorem.paragraphs(3),
|
|
44
|
-
published: false,
|
|
45
|
-
authorId: faker.string.uuid(),
|
|
46
|
-
createdAt: new Date()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
withTitle(title: string) {
|
|
50
|
-
this.data.title = title
|
|
51
|
-
return this
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
published() {
|
|
55
|
-
this.data.published = true
|
|
56
|
-
return this
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
byAuthor(authorId: string) {
|
|
60
|
-
this.data.authorId = authorId
|
|
61
|
-
return this
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
build() {
|
|
65
|
-
return { ...this.data }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const postBuilder = () => new PostBuilder()
|
|
70
|
-
|
|
71
|
-
// Usage
|
|
72
|
-
const post = postBuilder()
|
|
73
|
-
.withTitle('My Post')
|
|
74
|
-
.published()
|
|
75
|
-
.byAuthor('user-123')
|
|
76
|
-
.build()
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Database Fixtures
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// tests/fixtures/database.ts
|
|
83
|
-
import { prisma } from '@/lib/db'
|
|
84
|
-
import { createUser } from '../factories/user'
|
|
85
|
-
|
|
86
|
-
export async function seedUser(overrides = {}) {
|
|
87
|
-
const data = createUser(overrides)
|
|
88
|
-
return prisma.user.create({ data })
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function seedUsers(count: number) {
|
|
92
|
-
const users = Array.from({ length: count }, () => createUser())
|
|
93
|
-
return prisma.user.createMany({ data: users })
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function cleanDatabase() {
|
|
97
|
-
const tables = ['Post', 'Comment', 'User']
|
|
98
|
-
|
|
99
|
-
for (const table of tables) {
|
|
100
|
-
await prisma.$executeRawUnsafe(`TRUNCATE TABLE "${table}" CASCADE`)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Test setup
|
|
105
|
-
beforeEach(async () => {
|
|
106
|
-
await cleanDatabase()
|
|
107
|
-
})
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Fixture Files
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// tests/fixtures/products.json
|
|
114
|
-
[
|
|
115
|
-
{
|
|
116
|
-
"id": "prod_1",
|
|
117
|
-
"name": "Basic Plan",
|
|
118
|
-
"price": 999,
|
|
119
|
-
"currency": "usd"
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
"id": "prod_2",
|
|
123
|
-
"name": "Pro Plan",
|
|
124
|
-
"price": 2999,
|
|
125
|
-
"currency": "usd"
|
|
126
|
-
}
|
|
127
|
-
]
|
|
128
|
-
|
|
129
|
-
// tests/fixtures/index.ts
|
|
130
|
-
import products from './products.json'
|
|
131
|
-
|
|
132
|
-
export const fixtures = {
|
|
133
|
-
products,
|
|
134
|
-
users: [
|
|
135
|
-
{ id: 'user_1', email: 'admin@test.com', role: 'ADMIN' },
|
|
136
|
-
{ id: 'user_2', email: 'user@test.com', role: 'USER' }
|
|
137
|
-
]
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Scenario Fixtures
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
// tests/fixtures/scenarios.ts
|
|
145
|
-
export async function setupTeamWithMembers() {
|
|
146
|
-
const owner = await seedUser({ role: 'ADMIN' })
|
|
147
|
-
const team = await prisma.team.create({
|
|
148
|
-
data: {
|
|
149
|
-
name: 'Test Team',
|
|
150
|
-
ownerId: owner.id
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
const members = await Promise.all([
|
|
155
|
-
seedUser(),
|
|
156
|
-
seedUser(),
|
|
157
|
-
seedUser()
|
|
158
|
-
])
|
|
159
|
-
|
|
160
|
-
await prisma.teamMember.createMany({
|
|
161
|
-
data: members.map(m => ({
|
|
162
|
-
userId: m.id,
|
|
163
|
-
teamId: team.id,
|
|
164
|
-
role: 'MEMBER'
|
|
165
|
-
}))
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
return { owner, team, members }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Usage in test
|
|
172
|
-
it('lists team members', async () => {
|
|
173
|
-
const { team, members } = await setupTeamWithMembers()
|
|
174
|
-
|
|
175
|
-
const result = await getTeamMembers(team.id)
|
|
176
|
-
expect(result).toHaveLength(members.length)
|
|
177
|
-
})
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Playwright Fixtures
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
// tests/e2e/fixtures.ts
|
|
184
|
-
import { test as base } from '@playwright/test'
|
|
185
|
-
import { LoginPage } from './pages/login.page'
|
|
186
|
-
import { DashboardPage } from './pages/dashboard.page'
|
|
187
|
-
|
|
188
|
-
type Fixtures = {
|
|
189
|
-
loginPage: LoginPage
|
|
190
|
-
dashboardPage: DashboardPage
|
|
191
|
-
authenticatedPage: void
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export const test = base.extend<Fixtures>({
|
|
195
|
-
loginPage: async ({ page }, use) => {
|
|
196
|
-
await use(new LoginPage(page))
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
dashboardPage: async ({ page }, use) => {
|
|
200
|
-
await use(new DashboardPage(page))
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
authenticatedPage: async ({ page }, use) => {
|
|
204
|
-
await page.goto('/login')
|
|
205
|
-
await page.fill('[name="email"]', 'test@example.com')
|
|
206
|
-
await page.fill('[name="password"]', 'password')
|
|
207
|
-
await page.click('button[type="submit"]')
|
|
208
|
-
await page.waitForURL('/dashboard')
|
|
209
|
-
await use()
|
|
210
|
-
}
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
// Usage
|
|
214
|
-
test('authenticated user can access settings', async ({ authenticatedPage, page }) => {
|
|
215
|
-
await page.goto('/settings')
|
|
216
|
-
await expect(page).toHaveURL('/settings')
|
|
217
|
-
})
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## When to Use
|
|
221
|
-
|
|
222
|
-
- Consistent test data
|
|
223
|
-
- Complex object creation
|
|
224
|
-
- Database seeding
|
|
225
|
-
- Reusable scenarios
|