Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions docs/guide/test-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,61 @@ const test = base.extend({
],
})

test('', () => {})
test('works correctly')
```

#### Default fixture

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.

:::code-group
```ts [fixtures.test.ts]
import { test as base } from 'vitest'

const test = base.extend({
url: [
// default value if "url" is not defined in the config
'default',
// mark the fixure as "injected" to allow the override
{ injected: true },
],
})

test('works correctly', ({ url }) => {
// url is "/default" in "project-new"
// url is "/full" in "project-full"
// url is "/empty" in "project-empty"
})
```
```ts [vitest.workspace.ts]
import { defineWorkspace } from 'vitest/config'

export default defineWorkspace([
{
test: {
name: 'project-new',
},
},
{
test: {
name: 'project-full',
provide: {
url: '/full',
},
},
},
{
test: {
name: 'project-empty',
provide: {
url: '/empty',
},
},
},
])
```
:::

#### TypeScript

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

myTest('', (context) => {
myTest('types are defined correctly', (context) => {
expectTypeOf(context.todos).toEqualTypeOf<number[]>()
expectTypeOf(context.archive).toEqualTypeOf<number[]>()
})
Expand Down
14 changes: 11 additions & 3 deletions packages/runner/src/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { getFixture } from './map'
export interface FixtureItem extends FixtureOptions {
prop: string
value: any
/**
* Indicated if the injected value should be preferred over the fixture value
*/
injected?: boolean
/**
* Indicates whether the fixture is a function
*/
Expand All @@ -17,11 +21,12 @@ export interface FixtureItem extends FixtureOptions {

export function mergeContextFixtures(
fixtures: Record<string, any>,
context: { fixtures?: FixtureItem[] } = {},
context: { fixtures?: FixtureItem[] },
inject: (key: string) => unknown,
): {
fixtures?: FixtureItem[]
} {
const fixtureOptionKeys = ['auto']
const fixtureOptionKeys = ['auto', 'injected']
const fixtureArray: FixtureItem[] = Object.entries(fixtures).map(
([prop, value]) => {
const fixtureItem = { value } as FixtureItem
Expand All @@ -34,7 +39,10 @@ export function mergeContextFixtures(
) {
// fixture with options
Object.assign(fixtureItem, value[1])
fixtureItem.value = value[0]
const userValue = value[0]
fixtureItem.value = fixtureItem.injected
? (inject(prop) ?? userValue)
: userValue
}

fixtureItem.prop = prop
Expand Down
6 changes: 5 additions & 1 deletion packages/runner/src/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,11 @@ export function createTaskCollector(
}

taskFn.extend = function (fixtures: Fixtures<Record<string, any>>) {
const _context = mergeContextFixtures(fixtures, context)
const _context = mergeContextFixtures(
fixtures,
context || {},
(key: string) => getRunner().injectValue?.(key),
)

return createTest(function fn(
name: string | Function,
Expand Down
4 changes: 4 additions & 0 deletions packages/runner/src/types/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ export interface VitestRunner {
* Called when test and setup files are imported. Can be called in two situations: when collecting tests and when importing setup files.
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown
/**
* Function that is called when the runner attempts to get the value when `test.extend` is used with `{ injected: true }`
*/
injectValue?: (key: string) => unknown
/**
* Publicly available configuration.
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/vitest/src/runtime/runners/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { VitestExecutor } from '../execute'
import { getState, GLOBAL_EXPECT, setState } from '@vitest/expect'
import { getNames, getTestName, getTests } from '@vitest/runner/utils'
import { createExpect } from '../../integrations/chai/index'
import { inject } from '../../integrations/inject'
import { getSnapshotClient } from '../../integrations/snapshot/chai'
import { vi } from '../../integrations/vi'
import { rpc } from '../rpc'
Expand Down Expand Up @@ -89,6 +90,12 @@ export class VitestTestRunner implements VitestRunner {
this.cancelRun = true
}

injectValue(key: string) {
// inject has a very limiting type controlled by ProvidedContext
// some tests override it which causes the build to fail
return (inject as any)(key)
}

async onBeforeRunTask(test: Task) {
if (this.cancelRun) {
test.mode = 'skip'
Expand Down
6 changes: 4 additions & 2 deletions test/workspaces/globalTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare module 'vitest' {
invalidValue: unknown
projectConfigValue: boolean
globalConfigValue: boolean

providedConfigValue: string
}
}

Expand All @@ -35,8 +37,8 @@ export async function teardown() {
try {
assert.ok(results.success)
assert.equal(results.numTotalTestSuites, 28)
assert.equal(results.numTotalTests, 31)
assert.equal(results.numPassedTests, 31)
assert.equal(results.numTotalTests, 33)
assert.equal(results.numPassedTests, 33)
assert.ok(results.coverageMap)

const shared = results.testResults.filter((r: any) => r.name.includes('space_shared/test.spec.ts'))
Expand Down
13 changes: 13 additions & 0 deletions test/workspaces/space_shared/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ declare global {
const testValue: string
}

const custom = it.extend({
providedConfigValue: ['default value', { injected: true }],
})

custom('provided config value is injected', ({ providedConfigValue }) => {
expect(providedConfigValue).toBe(
// happy-dom provides the value in the workspace config
expect.getState().environment === 'node'
? 'default value'
: 'actual config value',
)
})

it('the same file works with different projects', () => {
expect(testValue).toBe(expect.getState().environment === 'node' ? 'node' : 'jsdom')
})
3 changes: 3 additions & 0 deletions test/workspaces/vitest.workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default defineWorkspace([
root: './space_shared',
environment: 'happy-dom',
setupFiles: ['./setup.jsdom.ts'],
provide: {
providedConfigValue: 'actual config value',
},
},
}),
Promise.resolve({
Expand Down