skillshop 2.0.0 → 2.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/README.md +217 -104
- package/agents-content/000_communication-writing.block.md +11 -0
- package/agents-content/001_external-actions.block.md +9 -0
- package/agents-content/AGENTS.md +11 -0
- package/package.json +6 -3
- package/skills-content/architecture-scanner/SKILL.md +274 -0
- package/skills-content/architecture-scanner/files.json +10 -0
- package/skills-content/architecture-scanner/scan.sh +823 -0
- package/skills-content/change-review/SKILL.md +76 -0
- package/skills-content/change-review/agents/openai.yaml +4 -0
- package/skills-content/changelog/SKILL.md +99 -0
- package/skills-content/changeset/changeset.md +80 -0
- package/skills-content/code-buddy/SKILL.md +136 -0
- package/skills-content/design-system-catalog/SKILL.md +106 -0
- package/skills-content/design-system-catalog/extract-components.mjs +441 -0
- package/skills-content/design-system-catalog/generate-catalog.sh +255 -0
- package/skills-content/flare/SKILL.md +96 -0
- package/skills-content/grilling/SKILL.md +31 -0
- package/skills-content/install-sigmap/SKILL.md +97 -0
- package/skills-content/one-shot/SKILL.md +51 -0
- package/skills-content/release/SKILL.md +128 -0
- package/skills-content/shadow-clone/SKILL.md +228 -0
- package/skills-content/shadow-clone/extract-signatures.mjs +202 -0
- package/skills-content/shadow-clone/scan-repo.mjs +343 -0
- package/skills-content/ship/SKILL.md +95 -0
- package/skills-content/shots/SKILL.md +89 -0
- package/skills-content/shots/agents/openai.yaml +4 -0
- package/skills-content/skillshop/SKILL.md +104 -0
- package/skills-content/skillz/SKILL.md +15 -0
- package/skills-content/transcript-clean/SKILL.md +55 -0
- package/skills-content/transcript-edit/SKILL.md +80 -0
- package/skills-content/vitest/GENERATION.md +5 -0
- package/skills-content/vitest/SKILL.md +52 -0
- package/skills-content/vitest/references/advanced-environments.md +264 -0
- package/skills-content/vitest/references/advanced-projects.md +300 -0
- package/skills-content/vitest/references/advanced-type-testing.md +237 -0
- package/skills-content/vitest/references/advanced-vi.md +249 -0
- package/skills-content/vitest/references/core-cli.md +166 -0
- package/skills-content/vitest/references/core-config.md +174 -0
- package/skills-content/vitest/references/core-describe.md +193 -0
- package/skills-content/vitest/references/core-expect.md +219 -0
- package/skills-content/vitest/references/core-hooks.md +244 -0
- package/skills-content/vitest/references/core-test-api.md +233 -0
- package/skills-content/vitest/references/features-concurrency.md +250 -0
- package/skills-content/vitest/references/features-context.md +238 -0
- package/skills-content/vitest/references/features-coverage.md +207 -0
- package/skills-content/vitest/references/features-filtering.md +211 -0
- package/skills-content/vitest/references/features-mocking.md +265 -0
- package/skills-content/vitest/references/features-snapshots.md +207 -0
- package/skills-content/workleaf/SKILL.md +77 -0
- package/skills-content/worklog/SKILL.md +145 -0
- package/skills-content/worklog/references/summary-format.md +114 -0
- package/skills-content/worktree/SKILL.md +71 -0
- package/src/agents-instructions.js +219 -0
- package/src/cli.js +87 -23
- package/src/import.js +12 -7
- package/src/index.js +1 -0
- package/src/legacy-agents-content.js +36 -0
- package/src/legacy-cli.js +1234 -0
- package/src/library.js +9 -0
- package/src/specifier.js +11 -0
- package/src/user-install.js +48 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-api
|
|
3
|
+
description: test/it function for defining tests with modifiers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test API
|
|
7
|
+
|
|
8
|
+
## Basic Test
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { expect, test } from 'vitest'
|
|
12
|
+
|
|
13
|
+
test('adds numbers', () => {
|
|
14
|
+
expect(1 + 1).toBe(2)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Alias: it
|
|
18
|
+
import { it } from 'vitest'
|
|
19
|
+
|
|
20
|
+
it('works the same', () => {
|
|
21
|
+
expect(true).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Async Tests
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
test('async test', async () => {
|
|
29
|
+
const result = await fetchData()
|
|
30
|
+
expect(result).toBeDefined()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Promises are automatically awaited
|
|
34
|
+
test('returns promise', () => {
|
|
35
|
+
return fetchData().then(result => {
|
|
36
|
+
expect(result).toBeDefined()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Test Options
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// Timeout (default: 5000ms)
|
|
45
|
+
test('slow test', async () => {
|
|
46
|
+
// ...
|
|
47
|
+
}, 10_000)
|
|
48
|
+
|
|
49
|
+
// Or with options object
|
|
50
|
+
test('with options', { timeout: 10_000, retry: 2 }, async () => {
|
|
51
|
+
// ...
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Test Modifiers
|
|
56
|
+
|
|
57
|
+
### Skip Tests
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
test.skip('skipped test', () => {
|
|
61
|
+
// Won't run
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Conditional skip
|
|
65
|
+
test.skipIf(process.env.CI)('not in CI', () => {})
|
|
66
|
+
test.runIf(process.env.CI)('only in CI', () => {})
|
|
67
|
+
|
|
68
|
+
// Dynamic skip via context
|
|
69
|
+
test('dynamic skip', ({ skip }) => {
|
|
70
|
+
skip(someCondition, 'reason')
|
|
71
|
+
// ...
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Focus Tests
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
test.only('only this runs', () => {
|
|
79
|
+
// Other tests in file are skipped
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Todo Tests
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
test.todo('implement later')
|
|
87
|
+
|
|
88
|
+
test.todo('with body', () => {
|
|
89
|
+
// Not run, shows in report
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Failing Tests
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
test.fails('expected to fail', () => {
|
|
97
|
+
expect(1).toBe(2) // Test passes because assertion fails
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Concurrent Tests
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// Run tests in parallel
|
|
105
|
+
test.concurrent('test 1', async ({ expect }) => {
|
|
106
|
+
// Use context.expect for concurrent tests
|
|
107
|
+
expect(await fetch1()).toBe('result')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test.concurrent('test 2', async ({ expect }) => {
|
|
111
|
+
expect(await fetch2()).toBe('result')
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Sequential Tests
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// Force sequential in concurrent context
|
|
119
|
+
test.sequential('must run alone', async () => {})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Parameterized Tests
|
|
123
|
+
|
|
124
|
+
### test.each
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
test.each([
|
|
128
|
+
[1, 1, 2],
|
|
129
|
+
[1, 2, 3],
|
|
130
|
+
[2, 1, 3],
|
|
131
|
+
])('add(%i, %i) = %i', (a, b, expected) => {
|
|
132
|
+
expect(a + b).toBe(expected)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// With objects
|
|
136
|
+
test.each([
|
|
137
|
+
{ a: 1, b: 1, expected: 2 },
|
|
138
|
+
{ a: 1, b: 2, expected: 3 },
|
|
139
|
+
])('add($a, $b) = $expected', ({ a, b, expected }) => {
|
|
140
|
+
expect(a + b).toBe(expected)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Template literal
|
|
144
|
+
test.each`
|
|
145
|
+
a | b | expected
|
|
146
|
+
${1} | ${1} | ${2}
|
|
147
|
+
${1} | ${2} | ${3}
|
|
148
|
+
`('add($a, $b) = $expected', ({ a, b, expected }) => {
|
|
149
|
+
expect(a + b).toBe(expected)
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### test.for
|
|
154
|
+
|
|
155
|
+
Preferred over `.each` - doesn't spread arrays:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
test.for([
|
|
159
|
+
[1, 1, 2],
|
|
160
|
+
[1, 2, 3],
|
|
161
|
+
])('add(%i, %i) = %i', ([a, b, expected], { expect }) => {
|
|
162
|
+
// Second arg is TestContext
|
|
163
|
+
expect(a + b).toBe(expected)
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Test Context
|
|
168
|
+
|
|
169
|
+
First argument provides context utilities:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
test('with context', ({ expect, skip, task }) => {
|
|
173
|
+
console.log(task.name) // Test name
|
|
174
|
+
skip(someCondition) // Skip dynamically
|
|
175
|
+
expect(1).toBe(1) // Context-bound expect
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Custom Test with Fixtures
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { test as base } from 'vitest'
|
|
183
|
+
|
|
184
|
+
const test = base.extend({
|
|
185
|
+
db: async ({}, use) => {
|
|
186
|
+
const db = await createDb()
|
|
187
|
+
await use(db)
|
|
188
|
+
await db.close()
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('query', async ({ db }) => {
|
|
193
|
+
const users = await db.query('SELECT * FROM users')
|
|
194
|
+
expect(users).toBeDefined()
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Retry Configuration
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
test('flaky test', { retry: 3 }, async () => {
|
|
202
|
+
// Retries up to 3 times on failure
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// Advanced retry options
|
|
206
|
+
test('with delay', {
|
|
207
|
+
retry: {
|
|
208
|
+
count: 3,
|
|
209
|
+
delay: 1000,
|
|
210
|
+
condition: /timeout/i, // Only retry on timeout errors
|
|
211
|
+
},
|
|
212
|
+
}, async () => {})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Tags
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
test('database test', { tags: ['db', 'slow'] }, async () => {})
|
|
219
|
+
|
|
220
|
+
// Run with: vitest --tags db
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Key Points
|
|
224
|
+
|
|
225
|
+
- Tests with no body are marked as `todo`
|
|
226
|
+
- `test.only` throws in CI unless `allowOnly: true`
|
|
227
|
+
- Use context's `expect` for concurrent tests and snapshots
|
|
228
|
+
- Function name is used as test name if passed as first arg
|
|
229
|
+
|
|
230
|
+
<!--
|
|
231
|
+
Source references:
|
|
232
|
+
- https://vitest.dev/api/test.html
|
|
233
|
+
-->
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: concurrency-parallelism
|
|
3
|
+
description: Concurrent tests, parallel execution, and sharding
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Concurrency & Parallelism
|
|
7
|
+
|
|
8
|
+
## File Parallelism
|
|
9
|
+
|
|
10
|
+
By default, Vitest runs test files in parallel across workers:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
defineConfig({
|
|
14
|
+
test: {
|
|
15
|
+
// Run files in parallel (default: true)
|
|
16
|
+
fileParallelism: true,
|
|
17
|
+
|
|
18
|
+
// Number of worker threads
|
|
19
|
+
maxWorkers: 4,
|
|
20
|
+
minWorkers: 1,
|
|
21
|
+
|
|
22
|
+
// Pool type: 'threads', 'forks', 'vmThreads'
|
|
23
|
+
pool: 'threads',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Concurrent Tests
|
|
29
|
+
|
|
30
|
+
Run tests within a file in parallel:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// Individual concurrent tests
|
|
34
|
+
test.concurrent('test 1', async ({ expect }) => {
|
|
35
|
+
expect(await fetch1()).toBe('result')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test.concurrent('test 2', async ({ expect }) => {
|
|
39
|
+
expect(await fetch2()).toBe('result')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// All tests in suite concurrent
|
|
43
|
+
describe.concurrent('parallel suite', () => {
|
|
44
|
+
test('test 1', async ({ expect }) => {})
|
|
45
|
+
test('test 2', async ({ expect }) => {})
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Important:** Use `{ expect }` from context for concurrent tests.
|
|
50
|
+
|
|
51
|
+
## Sequential in Concurrent Context
|
|
52
|
+
|
|
53
|
+
Force sequential execution:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
describe.concurrent('mostly parallel', () => {
|
|
57
|
+
test('parallel 1', async () => {})
|
|
58
|
+
test('parallel 2', async () => {})
|
|
59
|
+
|
|
60
|
+
test.sequential('must run alone 1', async () => {})
|
|
61
|
+
test.sequential('must run alone 2', async () => {})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Or entire suite
|
|
65
|
+
describe.sequential('sequential suite', () => {
|
|
66
|
+
test('first', () => {})
|
|
67
|
+
test('second', () => {})
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Max Concurrency
|
|
72
|
+
|
|
73
|
+
Limit concurrent tests:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
defineConfig({
|
|
77
|
+
test: {
|
|
78
|
+
maxConcurrency: 5, // Max concurrent tests per file
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Isolation
|
|
84
|
+
|
|
85
|
+
Each file runs in isolated environment by default:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
defineConfig({
|
|
89
|
+
test: {
|
|
90
|
+
// Disable isolation for faster runs (less safe)
|
|
91
|
+
isolate: false,
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Sharding
|
|
97
|
+
|
|
98
|
+
Split tests across machines:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Machine 1
|
|
102
|
+
vitest run --shard=1/3
|
|
103
|
+
|
|
104
|
+
# Machine 2
|
|
105
|
+
vitest run --shard=2/3
|
|
106
|
+
|
|
107
|
+
# Machine 3
|
|
108
|
+
vitest run --shard=3/3
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### CI Example (GitHub Actions)
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
jobs:
|
|
115
|
+
test:
|
|
116
|
+
strategy:
|
|
117
|
+
matrix:
|
|
118
|
+
shard: [1, 2, 3]
|
|
119
|
+
steps:
|
|
120
|
+
- run: vitest run --shard=${{ matrix.shard }}/3 --reporter=blob
|
|
121
|
+
|
|
122
|
+
merge:
|
|
123
|
+
needs: test
|
|
124
|
+
steps:
|
|
125
|
+
- run: vitest --merge-reports --reporter=junit
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Merge Reports
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Each shard outputs blob
|
|
132
|
+
vitest run --shard=1/3 --reporter=blob --coverage
|
|
133
|
+
vitest run --shard=2/3 --reporter=blob --coverage
|
|
134
|
+
|
|
135
|
+
# Merge all blobs
|
|
136
|
+
vitest --merge-reports --reporter=json --coverage
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Test Sequence
|
|
140
|
+
|
|
141
|
+
Control test order:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
defineConfig({
|
|
145
|
+
test: {
|
|
146
|
+
sequence: {
|
|
147
|
+
// Run tests in random order
|
|
148
|
+
shuffle: true,
|
|
149
|
+
|
|
150
|
+
// Seed for reproducible shuffle
|
|
151
|
+
seed: 12345,
|
|
152
|
+
|
|
153
|
+
// Hook execution order
|
|
154
|
+
hooks: 'stack', // 'stack', 'list', 'parallel'
|
|
155
|
+
|
|
156
|
+
// All tests concurrent by default
|
|
157
|
+
concurrent: true,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Shuffle Tests
|
|
164
|
+
|
|
165
|
+
Randomize to catch hidden dependencies:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// Via CLI
|
|
169
|
+
vitest --sequence.shuffle
|
|
170
|
+
|
|
171
|
+
// Per suite
|
|
172
|
+
describe.shuffle('random order', () => {
|
|
173
|
+
test('test 1', () => {})
|
|
174
|
+
test('test 2', () => {})
|
|
175
|
+
test('test 3', () => {})
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Pool Options
|
|
180
|
+
|
|
181
|
+
### Threads (Default)
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
defineConfig({
|
|
185
|
+
test: {
|
|
186
|
+
pool: 'threads',
|
|
187
|
+
poolOptions: {
|
|
188
|
+
threads: {
|
|
189
|
+
maxThreads: 8,
|
|
190
|
+
minThreads: 2,
|
|
191
|
+
isolate: true,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Forks
|
|
199
|
+
|
|
200
|
+
Better isolation, slower:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
defineConfig({
|
|
204
|
+
test: {
|
|
205
|
+
pool: 'forks',
|
|
206
|
+
poolOptions: {
|
|
207
|
+
forks: {
|
|
208
|
+
maxForks: 4,
|
|
209
|
+
isolate: true,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### VM Threads
|
|
217
|
+
|
|
218
|
+
Full VM isolation per file:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
defineConfig({
|
|
222
|
+
test: {
|
|
223
|
+
pool: 'vmThreads',
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Bail on Failure
|
|
229
|
+
|
|
230
|
+
Stop after first failure:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
vitest --bail 1 # Stop after 1 failure
|
|
234
|
+
vitest --bail # Stop on first failure (same as --bail 1)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Key Points
|
|
238
|
+
|
|
239
|
+
- Files run in parallel by default
|
|
240
|
+
- Use `.concurrent` for parallel tests within file
|
|
241
|
+
- Always use context's `expect` in concurrent tests
|
|
242
|
+
- Sharding splits tests across CI machines
|
|
243
|
+
- Use `--merge-reports` to combine sharded results
|
|
244
|
+
- Shuffle tests to find hidden dependencies
|
|
245
|
+
|
|
246
|
+
<!--
|
|
247
|
+
Source references:
|
|
248
|
+
- https://vitest.dev/guide/features.html#running-tests-concurrently
|
|
249
|
+
- https://vitest.dev/guide/improving-performance.html
|
|
250
|
+
-->
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-context-fixtures
|
|
3
|
+
description: Test context, custom fixtures with test.extend
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test Context & Fixtures
|
|
7
|
+
|
|
8
|
+
## Built-in Context
|
|
9
|
+
|
|
10
|
+
Every test receives context as first argument:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
test('context', ({ task, expect, skip }) => {
|
|
14
|
+
console.log(task.name) // Test name
|
|
15
|
+
expect(1).toBe(1) // Context-bound expect
|
|
16
|
+
skip() // Skip test dynamically
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Context Properties
|
|
21
|
+
|
|
22
|
+
- `task` - Test metadata (name, file, etc.)
|
|
23
|
+
- `expect` - Expect bound to this test (important for concurrent tests)
|
|
24
|
+
- `skip(condition?, message?)` - Skip the test
|
|
25
|
+
- `onTestFinished(fn)` - Cleanup after test
|
|
26
|
+
- `onTestFailed(fn)` - Run on failure only
|
|
27
|
+
|
|
28
|
+
## Custom Fixtures with test.extend
|
|
29
|
+
|
|
30
|
+
Create reusable test utilities:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { test as base } from 'vitest'
|
|
34
|
+
|
|
35
|
+
// Define fixture types
|
|
36
|
+
interface Fixtures {
|
|
37
|
+
db: Database
|
|
38
|
+
user: User
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create extended test
|
|
42
|
+
export const test = base.extend<Fixtures>({
|
|
43
|
+
// Fixture with setup/teardown
|
|
44
|
+
db: async ({}, use) => {
|
|
45
|
+
const db = await createDatabase()
|
|
46
|
+
await use(db) // Provide to test
|
|
47
|
+
await db.close() // Cleanup
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Fixture depending on another fixture
|
|
51
|
+
user: async ({ db }, use) => {
|
|
52
|
+
const user = await db.createUser({ name: 'Test' })
|
|
53
|
+
await use(user)
|
|
54
|
+
await db.deleteUser(user.id)
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Using fixtures:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
test('query user', async ({ db, user }) => {
|
|
63
|
+
const found = await db.findUser(user.id)
|
|
64
|
+
expect(found).toEqual(user)
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Fixture Initialization
|
|
69
|
+
|
|
70
|
+
Fixtures only initialize when accessed:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const test = base.extend({
|
|
74
|
+
expensive: async ({}, use) => {
|
|
75
|
+
console.log('initializing') // Only runs if test uses it
|
|
76
|
+
await use('value')
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('no fixture', () => {}) // expensive not called
|
|
81
|
+
test('uses fixture', ({ expensive }) => {}) // expensive called
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Auto Fixtures
|
|
85
|
+
|
|
86
|
+
Run fixture for every test:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const test = base.extend({
|
|
90
|
+
setup: [
|
|
91
|
+
async ({}, use) => {
|
|
92
|
+
await globalSetup()
|
|
93
|
+
await use()
|
|
94
|
+
await globalTeardown()
|
|
95
|
+
},
|
|
96
|
+
{ auto: true } // Always run
|
|
97
|
+
],
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Scoped Fixtures
|
|
102
|
+
|
|
103
|
+
### File Scope
|
|
104
|
+
|
|
105
|
+
Initialize once per file:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const test = base.extend({
|
|
109
|
+
connection: [
|
|
110
|
+
async ({}, use) => {
|
|
111
|
+
const conn = await connect()
|
|
112
|
+
await use(conn)
|
|
113
|
+
await conn.close()
|
|
114
|
+
},
|
|
115
|
+
{ scope: 'file' }
|
|
116
|
+
],
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Worker Scope
|
|
121
|
+
|
|
122
|
+
Initialize once per worker:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const test = base.extend({
|
|
126
|
+
sharedResource: [
|
|
127
|
+
async ({}, use) => {
|
|
128
|
+
await use(globalResource)
|
|
129
|
+
},
|
|
130
|
+
{ scope: 'worker' }
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Injected Fixtures (from Config)
|
|
136
|
+
|
|
137
|
+
Override fixtures per project:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// test file
|
|
141
|
+
const test = base.extend({
|
|
142
|
+
apiUrl: ['/default', { injected: true }],
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// vitest.config.ts
|
|
146
|
+
defineConfig({
|
|
147
|
+
test: {
|
|
148
|
+
projects: [
|
|
149
|
+
{
|
|
150
|
+
test: {
|
|
151
|
+
name: 'prod',
|
|
152
|
+
provide: { apiUrl: 'https://api.prod.com' },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Scoped Values per Suite
|
|
161
|
+
|
|
162
|
+
Override fixture for specific suite:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const test = base.extend({
|
|
166
|
+
environment: 'development',
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('production tests', () => {
|
|
170
|
+
test.scoped({ environment: 'production' })
|
|
171
|
+
|
|
172
|
+
test('uses production', ({ environment }) => {
|
|
173
|
+
expect(environment).toBe('production')
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('uses default', ({ environment }) => {
|
|
178
|
+
expect(environment).toBe('development')
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Extended Test Hooks
|
|
183
|
+
|
|
184
|
+
Type-aware hooks with fixtures:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const test = base.extend<{ db: Database }>({
|
|
188
|
+
db: async ({}, use) => {
|
|
189
|
+
const db = await createDb()
|
|
190
|
+
await use(db)
|
|
191
|
+
await db.close()
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// Hooks know about fixtures
|
|
196
|
+
test.beforeEach(({ db }) => {
|
|
197
|
+
db.seed()
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test.afterEach(({ db }) => {
|
|
201
|
+
db.clear()
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Composing Fixtures
|
|
206
|
+
|
|
207
|
+
Extend from another extended test:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
// base-test.ts
|
|
211
|
+
export const test = base.extend<{ db: Database }>({
|
|
212
|
+
db: async ({}, use) => { /* ... */ },
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// admin-test.ts
|
|
216
|
+
import { test as dbTest } from './base-test'
|
|
217
|
+
|
|
218
|
+
export const test = dbTest.extend<{ admin: User }>({
|
|
219
|
+
admin: async ({ db }, use) => {
|
|
220
|
+
const admin = await db.createAdmin()
|
|
221
|
+
await use(admin)
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Key Points
|
|
227
|
+
|
|
228
|
+
- Use `{ }` destructuring to access fixtures
|
|
229
|
+
- Fixtures are lazy - only initialize when accessed
|
|
230
|
+
- Return cleanup function from fixtures
|
|
231
|
+
- Use `{ auto: true }` for setup fixtures
|
|
232
|
+
- Use `{ scope: 'file' }` for expensive shared resources
|
|
233
|
+
- Fixtures compose - extend from extended tests
|
|
234
|
+
|
|
235
|
+
<!--
|
|
236
|
+
Source references:
|
|
237
|
+
- https://vitest.dev/guide/test-context.html
|
|
238
|
+
-->
|