Skip to content

Commit 85c64e3

Browse files
authoredNov 13, 2024··
feat(runner): test context can inject values from the config's provide (#6813)
1 parent 82f2b50 commit 85c64e3

File tree

8 files changed

+101
-8
lines changed

8 files changed

+101
-8
lines changed
 

‎docs/guide/test-context.md

+54-2
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,61 @@ const test = base.extend({
176176
],
177177
})
178178

179-
test('', () => {})
179+
test('works correctly')
180180
```
181181

182+
#### Default fixture
183+
184+
Since Vitest 2.2, you can provide different values in different [projects](/guide/workspace). To enable this feature, pass down `{ injected: true }` to the options. If the key is not specified in the [project configuration](/config/#provide), then the default value will be used.
185+
186+
:::code-group
187+
```ts [fixtures.test.ts]
188+
import { test as base } from 'vitest'
189+
190+
const test = base.extend({
191+
url: [
192+
// default value if "url" is not defined in the config
193+
'default',
194+
// mark the fixure as "injected" to allow the override
195+
{ injected: true },
196+
],
197+
})
198+
199+
test('works correctly', ({ url }) => {
200+
// url is "/default" in "project-new"
201+
// url is "/full" in "project-full"
202+
// url is "/empty" in "project-empty"
203+
})
204+
```
205+
```ts [vitest.workspace.ts]
206+
import { defineWorkspace } from 'vitest/config'
207+
208+
export default defineWorkspace([
209+
{
210+
test: {
211+
name: 'project-new',
212+
},
213+
},
214+
{
215+
test: {
216+
name: 'project-full',
217+
provide: {
218+
url: '/full',
219+
},
220+
},
221+
},
222+
{
223+
test: {
224+
name: 'project-empty',
225+
provide: {
226+
url: '/empty',
227+
},
228+
},
229+
},
230+
])
231+
```
232+
:::
233+
182234
#### TypeScript
183235

184236
To provide fixture types for all your custom contexts, you can pass the fixtures type as a generic.
@@ -194,7 +246,7 @@ const myTest = test.extend<MyFixtures>({
194246
archive: []
195247
})
196248

197-
myTest('', (context) => {
249+
myTest('types are defined correctly', (context) => {
198250
expectTypeOf(context.todos).toEqualTypeOf<number[]>()
199251
expectTypeOf(context.archive).toEqualTypeOf<number[]>()
200252
})

‎packages/runner/src/fixture.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { getFixture } from './map'
55
export interface FixtureItem extends FixtureOptions {
66
prop: string
77
value: any
8+
/**
9+
* Indicated if the injected value should be preferred over the fixture value
10+
*/
11+
injected?: boolean
812
/**
913
* Indicates whether the fixture is a function
1014
*/
@@ -17,11 +21,12 @@ export interface FixtureItem extends FixtureOptions {
1721

1822
export function mergeContextFixtures(
1923
fixtures: Record<string, any>,
20-
context: { fixtures?: FixtureItem[] } = {},
24+
context: { fixtures?: FixtureItem[] },
25+
inject: (key: string) => unknown,
2126
): {
2227
fixtures?: FixtureItem[]
2328
} {
24-
const fixtureOptionKeys = ['auto']
29+
const fixtureOptionKeys = ['auto', 'injected']
2530
const fixtureArray: FixtureItem[] = Object.entries(fixtures).map(
2631
([prop, value]) => {
2732
const fixtureItem = { value } as FixtureItem
@@ -34,7 +39,10 @@ export function mergeContextFixtures(
3439
) {
3540
// fixture with options
3641
Object.assign(fixtureItem, value[1])
37-
fixtureItem.value = value[0]
42+
const userValue = value[0]
43+
fixtureItem.value = fixtureItem.injected
44+
? (inject(prop) ?? userValue)
45+
: userValue
3846
}
3947

4048
fixtureItem.prop = prop

‎packages/runner/src/suite.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,11 @@ export function createTaskCollector(
710710
}
711711

712712
taskFn.extend = function (fixtures: Fixtures<Record<string, any>>) {
713-
const _context = mergeContextFixtures(fixtures, context)
713+
const _context = mergeContextFixtures(
714+
fixtures,
715+
context || {},
716+
(key: string) => getRunner().injectValue?.(key),
717+
)
714718

715719
return createTest(function fn(
716720
name: string | Function,

‎packages/runner/src/types/runner.ts

+4
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ export interface VitestRunner {
147147
* Called when test and setup files are imported. Can be called in two situations: when collecting tests and when importing setup files.
148148
*/
149149
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown
150+
/**
151+
* Function that is called when the runner attempts to get the value when `test.extend` is used with `{ injected: true }`
152+
*/
153+
injectValue?: (key: string) => unknown
150154
/**
151155
* Publicly available configuration.
152156
*/

‎packages/vitest/src/runtime/runners/test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { VitestExecutor } from '../execute'
1616
import { getState, GLOBAL_EXPECT, setState } from '@vitest/expect'
1717
import { getNames, getTestName, getTests } from '@vitest/runner/utils'
1818
import { createExpect } from '../../integrations/chai/index'
19+
import { inject } from '../../integrations/inject'
1920
import { getSnapshotClient } from '../../integrations/snapshot/chai'
2021
import { vi } from '../../integrations/vi'
2122
import { rpc } from '../rpc'
@@ -89,6 +90,12 @@ export class VitestTestRunner implements VitestRunner {
8990
this.cancelRun = true
9091
}
9192

93+
injectValue(key: string) {
94+
// inject has a very limiting type controlled by ProvidedContext
95+
// some tests override it which causes the build to fail
96+
return (inject as any)(key)
97+
}
98+
9299
async onBeforeRunTask(test: Task) {
93100
if (this.cancelRun) {
94101
test.mode = 'skip'

‎test/workspaces/globalTest.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ declare module 'vitest' {
99
invalidValue: unknown
1010
projectConfigValue: boolean
1111
globalConfigValue: boolean
12+
13+
providedConfigValue: string
1214
}
1315
}
1416

@@ -35,8 +37,8 @@ export async function teardown() {
3537
try {
3638
assert.ok(results.success)
3739
assert.equal(results.numTotalTestSuites, 28)
38-
assert.equal(results.numTotalTests, 31)
39-
assert.equal(results.numPassedTests, 31)
40+
assert.equal(results.numTotalTests, 33)
41+
assert.equal(results.numPassedTests, 33)
4042
assert.ok(results.coverageMap)
4143

4244
const shared = results.testResults.filter((r: any) => r.name.includes('space_shared/test.spec.ts'))

‎test/workspaces/space_shared/test.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ declare global {
44
const testValue: string
55
}
66

7+
const custom = it.extend({
8+
providedConfigValue: ['default value', { injected: true }],
9+
})
10+
11+
custom('provided config value is injected', ({ providedConfigValue }) => {
12+
expect(providedConfigValue).toBe(
13+
// happy-dom provides the value in the workspace config
14+
expect.getState().environment === 'node'
15+
? 'default value'
16+
: 'actual config value',
17+
)
18+
})
19+
720
it('the same file works with different projects', () => {
821
expect(testValue).toBe(expect.getState().environment === 'node' ? 'node' : 'jsdom')
922
})

‎test/workspaces/vitest.workspace.ts

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export default defineWorkspace([
1313
root: './space_shared',
1414
environment: 'happy-dom',
1515
setupFiles: ['./setup.jsdom.ts'],
16+
provide: {
17+
providedConfigValue: 'actual config value',
18+
},
1619
},
1720
}),
1821
Promise.resolve({

0 commit comments

Comments
 (0)
Please sign in to comment.