@dfosco/storyboard-core 3.2.0 → 3.3.1
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/dist/storyboard-ui.css +1 -0
- package/dist/storyboard-ui.js +26304 -0
- package/dist/storyboard-ui.js.map +1 -0
- package/dist/tailwind.css +1 -1
- package/package.json +24 -18
- package/scaffold/manifest.json +35 -0
- package/scaffold/scripts/link.sh +26 -0
- package/scaffold/scripts/unlink.sh +10 -0
- package/scaffold/skills/create/SKILL.md +501 -0
- package/scaffold/skills/storyboard/SKILL.md +360 -0
- package/scaffold/skills/update-storyboard/SKILL.md +16 -0
- package/scaffold/skills/update-storyboard/update-storyboard-packages.sh +26 -0
- package/scaffold/skills/vitest/GENERATION.md +5 -0
- package/scaffold/skills/vitest/SKILL.md +52 -0
- package/scaffold/skills/vitest/references/advanced-environments.md +264 -0
- package/scaffold/skills/vitest/references/advanced-projects.md +300 -0
- package/scaffold/skills/vitest/references/advanced-type-testing.md +237 -0
- package/scaffold/skills/vitest/references/advanced-vi.md +249 -0
- package/scaffold/skills/vitest/references/core-cli.md +166 -0
- package/scaffold/skills/vitest/references/core-config.md +174 -0
- package/scaffold/skills/vitest/references/core-describe.md +193 -0
- package/scaffold/skills/vitest/references/core-expect.md +219 -0
- package/scaffold/skills/vitest/references/core-hooks.md +244 -0
- package/scaffold/skills/vitest/references/core-test-api.md +233 -0
- package/scaffold/skills/vitest/references/features-concurrency.md +250 -0
- package/scaffold/skills/vitest/references/features-context.md +238 -0
- package/scaffold/skills/vitest/references/features-coverage.md +207 -0
- package/scaffold/skills/vitest/references/features-filtering.md +211 -0
- package/scaffold/skills/vitest/references/features-mocking.md +265 -0
- package/scaffold/skills/vitest/references/features-snapshots.md +207 -0
- package/scaffold/skills/worktree/SKILL.md +51 -0
- package/scaffold/storyboard.config.json +26 -0
- package/scaffold/svelte.config.js +1 -0
- package/scaffold/toolbar.config.json +4 -0
- package/src/ActionMenuButton.svelte +1 -1
- package/src/CanvasCreateMenu.svelte +1 -1
- package/src/CoreUIBar.svelte +23 -12
- package/src/CreateMenuButton.svelte +1 -1
- package/src/InspectorPanel.svelte +144 -49
- package/src/SidePanel.svelte +10 -10
- package/src/commandActions.js +1 -1
- package/src/comments/index.js +0 -3
- package/src/devtools-consumer.js +28 -0
- package/src/devtools.js +4 -1
- package/src/index.js +8 -3
- package/src/inspector/highlighter.js +28 -17
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +8 -4
- package/src/mountStoryboardCore.js +223 -0
- package/src/scaffold.js +100 -0
- package/src/stores/themeStore.ts +29 -8
- package/src/styles/tailwind.css +16 -0
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +18 -0
- package/src/ui-entry.js +30 -0
- package/src/vite/server-plugin.js +8 -24
- package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +24 -6
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +1 -1
- package/src/workshop/features/createFlow/index.js +0 -1
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +1 -1
- package/src/workshop/features/createPrototype/index.js +0 -1
- /package/{core-ui.config.json → toolbar.config.json} +0 -0
|
@@ -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
|
+
-->
|
|
@@ -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
|
+
-->
|