@dfosco/storyboard-core 3.1.2 → 3.3.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/dist/storyboard-ui.css +1 -0
- package/dist/storyboard-ui.js +26298 -0
- package/dist/storyboard-ui.js.map +1 -0
- package/dist/tailwind.css +1 -1
- package/package.json +24 -19
- 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 +20 -9
- 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.js +4 -1
- package/src/index.js +5 -2
- package/src/inspector/highlighter.js +3 -4
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
- 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,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vitest-configuration
|
|
3
|
+
description: Configure Vitest with vite.config.ts or vitest.config.ts
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configuration
|
|
7
|
+
|
|
8
|
+
Vitest reads configuration from `vitest.config.ts` or `vite.config.ts`. It shares the same config format as Vite.
|
|
9
|
+
|
|
10
|
+
## Basic Setup
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// vitest.config.ts
|
|
14
|
+
import { defineConfig } from 'vitest/config'
|
|
15
|
+
|
|
16
|
+
export default defineConfig({
|
|
17
|
+
test: {
|
|
18
|
+
// test options
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Using with Existing Vite Config
|
|
24
|
+
|
|
25
|
+
Add Vitest types reference and use the `test` property:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// vite.config.ts
|
|
29
|
+
/// <reference types="vitest/config" />
|
|
30
|
+
import { defineConfig } from 'vite'
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
test: {
|
|
34
|
+
globals: true,
|
|
35
|
+
environment: 'jsdom',
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Merging Configs
|
|
41
|
+
|
|
42
|
+
If you have separate config files, use `mergeConfig`:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// vitest.config.ts
|
|
46
|
+
import { defineConfig, mergeConfig } from 'vitest/config'
|
|
47
|
+
import viteConfig from './vite.config'
|
|
48
|
+
|
|
49
|
+
export default mergeConfig(viteConfig, defineConfig({
|
|
50
|
+
test: {
|
|
51
|
+
environment: 'jsdom',
|
|
52
|
+
},
|
|
53
|
+
}))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Common Options
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
defineConfig({
|
|
60
|
+
test: {
|
|
61
|
+
// Enable global APIs (describe, it, expect) without imports
|
|
62
|
+
globals: true,
|
|
63
|
+
|
|
64
|
+
// Test environment: 'node', 'jsdom', 'happy-dom'
|
|
65
|
+
environment: 'node',
|
|
66
|
+
|
|
67
|
+
// Setup files to run before each test file
|
|
68
|
+
setupFiles: ['./tests/setup.ts'],
|
|
69
|
+
|
|
70
|
+
// Include patterns for test files
|
|
71
|
+
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
|
|
72
|
+
|
|
73
|
+
// Exclude patterns
|
|
74
|
+
exclude: ['**/node_modules/**', '**/dist/**'],
|
|
75
|
+
|
|
76
|
+
// Test timeout in ms
|
|
77
|
+
testTimeout: 5000,
|
|
78
|
+
|
|
79
|
+
// Hook timeout in ms
|
|
80
|
+
hookTimeout: 10000,
|
|
81
|
+
|
|
82
|
+
// Enable watch mode by default
|
|
83
|
+
watch: true,
|
|
84
|
+
|
|
85
|
+
// Coverage configuration
|
|
86
|
+
coverage: {
|
|
87
|
+
provider: 'v8', // or 'istanbul'
|
|
88
|
+
reporter: ['text', 'html'],
|
|
89
|
+
include: ['src/**/*.ts'],
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Run tests in isolation (each file in separate process)
|
|
93
|
+
isolate: true,
|
|
94
|
+
|
|
95
|
+
// Pool for running tests: 'threads', 'forks', 'vmThreads'
|
|
96
|
+
pool: 'threads',
|
|
97
|
+
|
|
98
|
+
// Number of threads/processes
|
|
99
|
+
poolOptions: {
|
|
100
|
+
threads: {
|
|
101
|
+
maxThreads: 4,
|
|
102
|
+
minThreads: 1,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Automatically clear mocks between tests
|
|
107
|
+
clearMocks: true,
|
|
108
|
+
|
|
109
|
+
// Restore mocks between tests
|
|
110
|
+
restoreMocks: true,
|
|
111
|
+
|
|
112
|
+
// Retry failed tests
|
|
113
|
+
retry: 0,
|
|
114
|
+
|
|
115
|
+
// Stop after first failure
|
|
116
|
+
bail: 0,
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Conditional Configuration
|
|
122
|
+
|
|
123
|
+
Use `mode` or `process.env.VITEST` for test-specific config:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
export default defineConfig(({ mode }) => ({
|
|
127
|
+
plugins: mode === 'test' ? [] : [myPlugin()],
|
|
128
|
+
test: {
|
|
129
|
+
// test options
|
|
130
|
+
},
|
|
131
|
+
}))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Projects (Monorepos)
|
|
135
|
+
|
|
136
|
+
Run different configurations in the same Vitest process:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
defineConfig({
|
|
140
|
+
test: {
|
|
141
|
+
projects: [
|
|
142
|
+
'packages/*',
|
|
143
|
+
{
|
|
144
|
+
test: {
|
|
145
|
+
name: 'unit',
|
|
146
|
+
include: ['tests/unit/**/*.test.ts'],
|
|
147
|
+
environment: 'node',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
test: {
|
|
152
|
+
name: 'integration',
|
|
153
|
+
include: ['tests/integration/**/*.test.ts'],
|
|
154
|
+
environment: 'jsdom',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Key Points
|
|
163
|
+
|
|
164
|
+
- Vitest uses Vite's transformation pipeline - same `resolve.alias`, plugins work
|
|
165
|
+
- `vitest.config.ts` takes priority over `vite.config.ts`
|
|
166
|
+
- Use `--config` flag to specify a custom config path
|
|
167
|
+
- `process.env.VITEST` is set to `true` when running tests
|
|
168
|
+
- Test config uses `test` property, rest is Vite config
|
|
169
|
+
|
|
170
|
+
<!--
|
|
171
|
+
Source references:
|
|
172
|
+
- https://vitest.dev/guide/#configuring-vitest
|
|
173
|
+
- https://vitest.dev/config/
|
|
174
|
+
-->
|
|
@@ -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
|
+
-->
|