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,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: describe-api
|
|
3
|
+
description: describe/suite for grouping tests into logical blocks
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Describe API
|
|
7
|
+
|
|
8
|
+
Group related tests into suites for organization and shared setup.
|
|
9
|
+
|
|
10
|
+
## Basic Usage
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { describe, expect, test } from 'vitest'
|
|
14
|
+
|
|
15
|
+
describe('Math', () => {
|
|
16
|
+
test('adds numbers', () => {
|
|
17
|
+
expect(1 + 1).toBe(2)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('subtracts numbers', () => {
|
|
21
|
+
expect(3 - 1).toBe(2)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Alias: suite
|
|
26
|
+
import { suite } from 'vitest'
|
|
27
|
+
suite('equivalent to describe', () => {})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Nested Suites
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
describe('User', () => {
|
|
34
|
+
describe('when logged in', () => {
|
|
35
|
+
test('shows dashboard', () => {})
|
|
36
|
+
test('can update profile', () => {})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('when logged out', () => {
|
|
40
|
+
test('shows login page', () => {})
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Suite Options
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// All tests inherit options
|
|
49
|
+
describe('slow tests', { timeout: 30_000 }, () => {
|
|
50
|
+
test('test 1', () => {}) // 30s timeout
|
|
51
|
+
test('test 2', () => {}) // 30s timeout
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Suite Modifiers
|
|
56
|
+
|
|
57
|
+
### Skip Suites
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
describe.skip('skipped suite', () => {
|
|
61
|
+
test('wont run', () => {})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Conditional
|
|
65
|
+
describe.skipIf(process.env.CI)('not in CI', () => {})
|
|
66
|
+
describe.runIf(!process.env.CI)('only local', () => {})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Focus Suites
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
describe.only('only this suite runs', () => {
|
|
73
|
+
test('runs', () => {})
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Todo Suites
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
describe.todo('implement later')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Concurrent Suites
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// All tests run in parallel
|
|
87
|
+
describe.concurrent('parallel tests', () => {
|
|
88
|
+
test('test 1', async ({ expect }) => {})
|
|
89
|
+
test('test 2', async ({ expect }) => {})
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Sequential in Concurrent
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
describe.concurrent('parallel', () => {
|
|
97
|
+
test('concurrent 1', async () => {})
|
|
98
|
+
|
|
99
|
+
describe.sequential('must be sequential', () => {
|
|
100
|
+
test('step 1', async () => {})
|
|
101
|
+
test('step 2', async () => {})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Shuffle Tests
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
describe.shuffle('random order', () => {
|
|
110
|
+
test('test 1', () => {})
|
|
111
|
+
test('test 2', () => {})
|
|
112
|
+
test('test 3', () => {})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Or with option
|
|
116
|
+
describe('random', { shuffle: true }, () => {})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Parameterized Suites
|
|
120
|
+
|
|
121
|
+
### describe.each
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
describe.each([
|
|
125
|
+
{ name: 'Chrome', version: 100 },
|
|
126
|
+
{ name: 'Firefox', version: 90 },
|
|
127
|
+
])('$name browser', ({ name, version }) => {
|
|
128
|
+
test('has version', () => {
|
|
129
|
+
expect(version).toBeGreaterThan(0)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### describe.for
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
describe.for([
|
|
138
|
+
['Chrome', 100],
|
|
139
|
+
['Firefox', 90],
|
|
140
|
+
])('%s browser', ([name, version]) => {
|
|
141
|
+
test('has version', () => {
|
|
142
|
+
expect(version).toBeGreaterThan(0)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Hooks in Suites
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
describe('Database', () => {
|
|
151
|
+
let db
|
|
152
|
+
|
|
153
|
+
beforeAll(async () => {
|
|
154
|
+
db = await createDb()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
afterAll(async () => {
|
|
158
|
+
await db.close()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
beforeEach(async () => {
|
|
162
|
+
await db.clear()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('insert works', async () => {
|
|
166
|
+
await db.insert({ name: 'test' })
|
|
167
|
+
expect(await db.count()).toBe(1)
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Modifier Combinations
|
|
173
|
+
|
|
174
|
+
All modifiers can be chained:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
describe.skip.concurrent('skipped concurrent', () => {})
|
|
178
|
+
describe.only.shuffle('only and shuffled', () => {})
|
|
179
|
+
describe.concurrent.skip('equivalent', () => {})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Key Points
|
|
183
|
+
|
|
184
|
+
- Top-level tests belong to an implicit file suite
|
|
185
|
+
- Nested suites inherit parent's options (timeout, retry, etc.)
|
|
186
|
+
- Hooks are scoped to their suite and nested suites
|
|
187
|
+
- Use `describe.concurrent` with context's `expect` for snapshots
|
|
188
|
+
- Shuffle order depends on `sequence.seed` config
|
|
189
|
+
|
|
190
|
+
<!--
|
|
191
|
+
Source references:
|
|
192
|
+
- https://vitest.dev/api/describe.html
|
|
193
|
+
-->
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: expect-api
|
|
3
|
+
description: Assertions with matchers, asymmetric matchers, and custom matchers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Expect API
|
|
7
|
+
|
|
8
|
+
Vitest uses Chai assertions with Jest-compatible API.
|
|
9
|
+
|
|
10
|
+
## Basic Assertions
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { expect, test } from 'vitest'
|
|
14
|
+
|
|
15
|
+
test('assertions', () => {
|
|
16
|
+
// Equality
|
|
17
|
+
expect(1 + 1).toBe(2) // Strict equality (===)
|
|
18
|
+
expect({ a: 1 }).toEqual({ a: 1 }) // Deep equality
|
|
19
|
+
|
|
20
|
+
// Truthiness
|
|
21
|
+
expect(true).toBeTruthy()
|
|
22
|
+
expect(false).toBeFalsy()
|
|
23
|
+
expect(null).toBeNull()
|
|
24
|
+
expect(undefined).toBeUndefined()
|
|
25
|
+
expect('value').toBeDefined()
|
|
26
|
+
|
|
27
|
+
// Numbers
|
|
28
|
+
expect(10).toBeGreaterThan(5)
|
|
29
|
+
expect(10).toBeGreaterThanOrEqual(10)
|
|
30
|
+
expect(5).toBeLessThan(10)
|
|
31
|
+
expect(0.1 + 0.2).toBeCloseTo(0.3, 5)
|
|
32
|
+
|
|
33
|
+
// Strings
|
|
34
|
+
expect('hello world').toMatch(/world/)
|
|
35
|
+
expect('hello').toContain('ell')
|
|
36
|
+
|
|
37
|
+
// Arrays
|
|
38
|
+
expect([1, 2, 3]).toContain(2)
|
|
39
|
+
expect([{ a: 1 }]).toContainEqual({ a: 1 })
|
|
40
|
+
expect([1, 2, 3]).toHaveLength(3)
|
|
41
|
+
|
|
42
|
+
// Objects
|
|
43
|
+
expect({ a: 1, b: 2 }).toHaveProperty('a')
|
|
44
|
+
expect({ a: 1, b: 2 }).toHaveProperty('a', 1)
|
|
45
|
+
expect({ a: { b: 1 } }).toHaveProperty('a.b', 1)
|
|
46
|
+
expect({ a: 1 }).toMatchObject({ a: 1 })
|
|
47
|
+
|
|
48
|
+
// Types
|
|
49
|
+
expect('string').toBeTypeOf('string')
|
|
50
|
+
expect(new Date()).toBeInstanceOf(Date)
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Negation
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
expect(1).not.toBe(2)
|
|
58
|
+
expect({ a: 1 }).not.toEqual({ a: 2 })
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Error Assertions
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Sync errors - wrap in function
|
|
65
|
+
expect(() => throwError()).toThrow()
|
|
66
|
+
expect(() => throwError()).toThrow('message')
|
|
67
|
+
expect(() => throwError()).toThrow(/pattern/)
|
|
68
|
+
expect(() => throwError()).toThrow(CustomError)
|
|
69
|
+
|
|
70
|
+
// Async errors - use rejects
|
|
71
|
+
await expect(asyncThrow()).rejects.toThrow('error')
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Promise Assertions
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// Resolves
|
|
78
|
+
await expect(Promise.resolve(1)).resolves.toBe(1)
|
|
79
|
+
await expect(fetchData()).resolves.toEqual({ data: true })
|
|
80
|
+
|
|
81
|
+
// Rejects
|
|
82
|
+
await expect(Promise.reject('error')).rejects.toBe('error')
|
|
83
|
+
await expect(failingFetch()).rejects.toThrow()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Spy/Mock Assertions
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const fn = vi.fn()
|
|
90
|
+
fn('arg1', 'arg2')
|
|
91
|
+
fn('arg3')
|
|
92
|
+
|
|
93
|
+
expect(fn).toHaveBeenCalled()
|
|
94
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
|
95
|
+
expect(fn).toHaveBeenCalledWith('arg1', 'arg2')
|
|
96
|
+
expect(fn).toHaveBeenLastCalledWith('arg3')
|
|
97
|
+
expect(fn).toHaveBeenNthCalledWith(1, 'arg1', 'arg2')
|
|
98
|
+
|
|
99
|
+
expect(fn).toHaveReturned()
|
|
100
|
+
expect(fn).toHaveReturnedWith(value)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Asymmetric Matchers
|
|
104
|
+
|
|
105
|
+
Use inside `toEqual`, `toHaveBeenCalledWith`, etc:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
expect({ id: 1, name: 'test' }).toEqual({
|
|
109
|
+
id: expect.any(Number),
|
|
110
|
+
name: expect.any(String),
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
expect({ a: 1, b: 2, c: 3 }).toEqual(
|
|
114
|
+
expect.objectContaining({ a: 1 })
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
expect([1, 2, 3, 4]).toEqual(
|
|
118
|
+
expect.arrayContaining([1, 3])
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
expect('hello world').toEqual(
|
|
122
|
+
expect.stringContaining('world')
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect('hello world').toEqual(
|
|
126
|
+
expect.stringMatching(/world$/)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
expect({ value: null }).toEqual({
|
|
130
|
+
value: expect.anything() // Matches anything except null/undefined
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Negate with expect.not
|
|
134
|
+
expect([1, 2]).toEqual(
|
|
135
|
+
expect.not.arrayContaining([3])
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Soft Assertions
|
|
140
|
+
|
|
141
|
+
Continue test after failure:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
expect.soft(1).toBe(2) // Marks test failed but continues
|
|
145
|
+
expect.soft(2).toBe(3) // Also runs
|
|
146
|
+
// All failures reported at end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Poll Assertions
|
|
150
|
+
|
|
151
|
+
Retry until passes:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
await expect.poll(() => fetchStatus()).toBe('ready')
|
|
155
|
+
|
|
156
|
+
await expect.poll(
|
|
157
|
+
() => document.querySelector('.element'),
|
|
158
|
+
{ interval: 100, timeout: 5000 }
|
|
159
|
+
).toBeTruthy()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Assertion Count
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
test('async assertions', async () => {
|
|
166
|
+
expect.assertions(2) // Exactly 2 assertions must run
|
|
167
|
+
|
|
168
|
+
await doAsync((data) => {
|
|
169
|
+
expect(data).toBeDefined()
|
|
170
|
+
expect(data.id).toBe(1)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('at least one', () => {
|
|
175
|
+
expect.hasAssertions() // At least 1 assertion must run
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Extending Matchers
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
expect.extend({
|
|
183
|
+
toBeWithinRange(received, floor, ceiling) {
|
|
184
|
+
const pass = received >= floor && received <= ceiling
|
|
185
|
+
return {
|
|
186
|
+
pass,
|
|
187
|
+
message: () =>
|
|
188
|
+
`expected ${received} to be within range ${floor} - ${ceiling}`,
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('custom matcher', () => {
|
|
194
|
+
expect(100).toBeWithinRange(90, 110)
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Snapshot Assertions
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
expect(data).toMatchSnapshot()
|
|
202
|
+
expect(data).toMatchInlineSnapshot(`{ "id": 1 }`)
|
|
203
|
+
await expect(result).toMatchFileSnapshot('./expected.json')
|
|
204
|
+
|
|
205
|
+
expect(() => throw new Error('fail')).toThrowErrorMatchingSnapshot()
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Key Points
|
|
209
|
+
|
|
210
|
+
- Use `toBe` for primitives, `toEqual` for objects/arrays
|
|
211
|
+
- `toStrictEqual` checks undefined properties and array sparseness
|
|
212
|
+
- Always `await` async assertions (`resolves`, `rejects`, `poll`)
|
|
213
|
+
- Use context's `expect` in concurrent tests for correct tracking
|
|
214
|
+
- `toThrow` requires wrapping sync code in a function
|
|
215
|
+
|
|
216
|
+
<!--
|
|
217
|
+
Source references:
|
|
218
|
+
- https://vitest.dev/api/expect.html
|
|
219
|
+
-->
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lifecycle-hooks
|
|
3
|
+
description: beforeEach, afterEach, beforeAll, afterAll, and around hooks
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lifecycle Hooks
|
|
7
|
+
|
|
8
|
+
## Basic Hooks
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { afterAll, afterEach, beforeAll, beforeEach, test } from 'vitest'
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
// Runs once before all tests in file/suite
|
|
15
|
+
await setupDatabase()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
// Runs once after all tests in file/suite
|
|
20
|
+
await teardownDatabase()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
// Runs before each test
|
|
25
|
+
await clearTestData()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
// Runs after each test
|
|
30
|
+
await cleanupMocks()
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Cleanup Return Pattern
|
|
35
|
+
|
|
36
|
+
Return cleanup function from `before*` hooks:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
const server = await startServer()
|
|
41
|
+
|
|
42
|
+
// Returned function runs as afterAll
|
|
43
|
+
return async () => {
|
|
44
|
+
await server.close()
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
beforeEach(async () => {
|
|
49
|
+
const connection = await connect()
|
|
50
|
+
|
|
51
|
+
// Runs as afterEach
|
|
52
|
+
return () => connection.close()
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Scoped Hooks
|
|
57
|
+
|
|
58
|
+
Hooks apply to current suite and nested suites:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
describe('outer', () => {
|
|
62
|
+
beforeEach(() => console.log('outer before'))
|
|
63
|
+
|
|
64
|
+
test('test 1', () => {}) // outer before → test
|
|
65
|
+
|
|
66
|
+
describe('inner', () => {
|
|
67
|
+
beforeEach(() => console.log('inner before'))
|
|
68
|
+
|
|
69
|
+
test('test 2', () => {}) // outer before → inner before → test
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Hook Timeout
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
beforeAll(async () => {
|
|
78
|
+
await slowSetup()
|
|
79
|
+
}, 30_000) // 30 second timeout
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Around Hooks
|
|
83
|
+
|
|
84
|
+
Wrap tests with setup/teardown context:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { aroundEach, test } from 'vitest'
|
|
88
|
+
|
|
89
|
+
// Wrap each test in database transaction
|
|
90
|
+
aroundEach(async (runTest) => {
|
|
91
|
+
await db.beginTransaction()
|
|
92
|
+
await runTest() // Must be called!
|
|
93
|
+
await db.rollback()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('insert user', async () => {
|
|
97
|
+
await db.insert({ name: 'Alice' })
|
|
98
|
+
// Automatically rolled back after test
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### aroundAll
|
|
103
|
+
|
|
104
|
+
Wrap entire suite:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { aroundAll, test } from 'vitest'
|
|
108
|
+
|
|
109
|
+
aroundAll(async (runSuite) => {
|
|
110
|
+
console.log('before all tests')
|
|
111
|
+
await runSuite() // Must be called!
|
|
112
|
+
console.log('after all tests')
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Multiple Around Hooks
|
|
117
|
+
|
|
118
|
+
Nested like onion layers:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
aroundEach(async (runTest) => {
|
|
122
|
+
console.log('outer before')
|
|
123
|
+
await runTest()
|
|
124
|
+
console.log('outer after')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
aroundEach(async (runTest) => {
|
|
128
|
+
console.log('inner before')
|
|
129
|
+
await runTest()
|
|
130
|
+
console.log('inner after')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Order: outer before → inner before → test → inner after → outer after
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Test Hooks
|
|
137
|
+
|
|
138
|
+
Inside test body:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { onTestFailed, onTestFinished, test } from 'vitest'
|
|
142
|
+
|
|
143
|
+
test('with cleanup', () => {
|
|
144
|
+
const db = connect()
|
|
145
|
+
|
|
146
|
+
// Runs after test finishes (pass or fail)
|
|
147
|
+
onTestFinished(() => db.close())
|
|
148
|
+
|
|
149
|
+
// Only runs if test fails
|
|
150
|
+
onTestFailed(({ task }) => {
|
|
151
|
+
console.log('Failed:', task.result?.errors)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
db.query('SELECT * FROM users')
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Reusable Cleanup Pattern
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
function useTestDb() {
|
|
162
|
+
const db = connect()
|
|
163
|
+
onTestFinished(() => db.close())
|
|
164
|
+
return db
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
test('query users', () => {
|
|
168
|
+
const db = useTestDb()
|
|
169
|
+
expect(db.query('SELECT * FROM users')).toBeDefined()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('query orders', () => {
|
|
173
|
+
const db = useTestDb() // Fresh connection, auto-closed
|
|
174
|
+
expect(db.query('SELECT * FROM orders')).toBeDefined()
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Concurrent Test Hooks
|
|
179
|
+
|
|
180
|
+
For concurrent tests, use context's hooks:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
test.concurrent('concurrent', ({ onTestFinished }) => {
|
|
184
|
+
const resource = allocate()
|
|
185
|
+
onTestFinished(() => resource.release())
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Extended Test Hooks
|
|
190
|
+
|
|
191
|
+
With `test.extend`, hooks are type-aware:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const test = base.extend<{ db: Database }>({
|
|
195
|
+
db: async ({}, use) => {
|
|
196
|
+
const db = await createDb()
|
|
197
|
+
await use(db)
|
|
198
|
+
await db.close()
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// These hooks know about `db` fixture
|
|
203
|
+
test.beforeEach(({ db }) => {
|
|
204
|
+
db.seed()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test.afterEach(({ db }) => {
|
|
208
|
+
db.clear()
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Hook Execution Order
|
|
213
|
+
|
|
214
|
+
Default order (stack):
|
|
215
|
+
1. `beforeAll` (in order)
|
|
216
|
+
2. `beforeEach` (in order)
|
|
217
|
+
3. Test
|
|
218
|
+
4. `afterEach` (reverse order)
|
|
219
|
+
5. `afterAll` (reverse order)
|
|
220
|
+
|
|
221
|
+
Configure with `sequence.hooks`:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
defineConfig({
|
|
225
|
+
test: {
|
|
226
|
+
sequence: {
|
|
227
|
+
hooks: 'list', // 'stack' (default), 'list', 'parallel'
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Key Points
|
|
234
|
+
|
|
235
|
+
- Hooks are not called during type checking
|
|
236
|
+
- Return cleanup function from `before*` to avoid `after*` duplication
|
|
237
|
+
- `aroundEach`/`aroundAll` must call `runTest()`/`runSuite()`
|
|
238
|
+
- `onTestFinished` always runs, even if test fails
|
|
239
|
+
- Use context hooks for concurrent tests
|
|
240
|
+
|
|
241
|
+
<!--
|
|
242
|
+
Source references:
|
|
243
|
+
- https://vitest.dev/api/hooks.html
|
|
244
|
+
-->
|