Skip to content

Commit

Permalink
feat(runner): test context can inject values from the config's `provi…
Browse files Browse the repository at this point in the history
…de` (#6813)
  • Loading branch information
sheremet-va authored Nov 13, 2024
1 parent 82f2b50 commit 85c64e3
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 8 deletions.
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

0 comments on commit 85c64e3

Please sign in to comment.