[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

feat(runner): test context can inject values from the config's `provide` (#6813)

authored by

Vladimir and committed by
GitHub
(Nov 13, 2024, 5:20 PM +0100) 85c64e35 82f2b508

+101 -8
+54 -2
docs/guide/test-context.md
··· 176 176 ], 177 177 }) 178 178 179 - test('', () => {}) 179 + test('works correctly') 180 180 ``` 181 + 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 + ::: 181 233 182 234 #### TypeScript 183 235 ··· 194 246 archive: [] 195 247 }) 196 248 197 - myTest('', (context) => { 249 + myTest('types are defined correctly', (context) => { 198 250 expectTypeOf(context.todos).toEqualTypeOf<number[]>() 199 251 expectTypeOf(context.archive).toEqualTypeOf<number[]>() 200 252 })
+4 -2
test/workspaces/globalTest.ts
··· 9 9 invalidValue: unknown 10 10 projectConfigValue: boolean 11 11 globalConfigValue: boolean 12 + 13 + providedConfigValue: string 12 14 } 13 15 } 14 16 ··· 35 37 try { 36 38 assert.ok(results.success) 37 39 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) 40 42 assert.ok(results.coverageMap) 41 43 42 44 const shared = results.testResults.filter((r: any) => r.name.includes('space_shared/test.spec.ts'))
+3
test/workspaces/vitest.workspace.ts
··· 13 13 root: './space_shared', 14 14 environment: 'happy-dom', 15 15 setupFiles: ['./setup.jsdom.ts'], 16 + provide: { 17 + providedConfigValue: 'actual config value', 18 + }, 16 19 }, 17 20 }), 18 21 Promise.resolve({
+11 -3
packages/runner/src/fixture.ts
··· 6 6 prop: string 7 7 value: any 8 8 /** 9 + * Indicated if the injected value should be preferred over the fixture value 10 + */ 11 + injected?: boolean 12 + /** 9 13 * Indicates whether the fixture is a function 10 14 */ 11 15 isFn: boolean ··· 17 21 18 22 export function mergeContextFixtures( 19 23 fixtures: Record<string, any>, 20 - context: { fixtures?: FixtureItem[] } = {}, 24 + context: { fixtures?: FixtureItem[] }, 25 + inject: (key: string) => unknown, 21 26 ): { 22 27 fixtures?: FixtureItem[] 23 28 } { 24 - const fixtureOptionKeys = ['auto'] 29 + const fixtureOptionKeys = ['auto', 'injected'] 25 30 const fixtureArray: FixtureItem[] = Object.entries(fixtures).map( 26 31 ([prop, value]) => { 27 32 const fixtureItem = { value } as FixtureItem ··· 34 39 ) { 35 40 // fixture with options 36 41 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 38 46 } 39 47 40 48 fixtureItem.prop = prop
+5 -1
packages/runner/src/suite.ts
··· 710 710 } 711 711 712 712 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 + ) 714 718 715 719 return createTest(function fn( 716 720 name: string | Function,
+13
test/workspaces/space_shared/test.spec.ts
··· 4 4 const testValue: string 5 5 } 6 6 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 + 7 20 it('the same file works with different projects', () => { 8 21 expect(testValue).toBe(expect.getState().environment === 'node' ? 'node' : 'jsdom') 9 22 })
+4
packages/runner/src/types/runner.ts
··· 148 148 */ 149 149 importFile: (filepath: string, source: VitestRunnerImportSource) => unknown 150 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 154 + /** 151 155 * Publicly available configuration. 152 156 */ 153 157 config: VitestRunnerConfig
+7
packages/vitest/src/runtime/runners/test.ts
··· 16 16 import { getState, GLOBAL_EXPECT, setState } from '@vitest/expect' 17 17 import { getNames, getTestName, getTests } from '@vitest/runner/utils' 18 18 import { createExpect } from '../../integrations/chai/index' 19 + import { inject } from '../../integrations/inject' 19 20 import { getSnapshotClient } from '../../integrations/snapshot/chai' 20 21 import { vi } from '../../integrations/vi' 21 22 import { rpc } from '../rpc' ··· 87 88 88 89 onCancel(_reason: CancelReason) { 89 90 this.cancelRun = true 91 + } 92 + 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) 90 97 } 91 98 92 99 async onBeforeRunTask(test: Task) {