[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: draft implementation

Vladimir Sheremet (Apr 3, 2025, 5:02 PM +0200) 86010b34 6373ecc8

+106 -11
+28 -2
packages/runner/src/collect.ts
··· 1 + import type { FixtureItem } from './fixture' 1 2 import type { FileSpecification, VitestRunner } from './types/runner' 2 - import type { File, SuiteHooks } from './types/tasks' 3 + import type { File, SuiteHooks, Task } from './types/tasks' 3 4 import { toArray } from '@vitest/utils' 4 5 import { processError } from '@vitest/utils/error' 5 6 import { collectorContext } from './context' 6 - import { getHooks, setHooks } from './map' 7 + import { getHooks, getTestFixture, setHooks, setTestFixture } from './map' 7 8 import { runSetupFiles } from './setup' 8 9 import { 9 10 clearCollectorContext, ··· 104 105 } 105 106 106 107 files.push(file) 108 + 109 + // TODO: any 110 + setTestFixture(file.context as any, getFileFixtires(file)) 107 111 } 108 112 109 113 return files 114 + } 115 + 116 + function getFileFixtires(file: File): FixtureItem[] { 117 + const fixtures = new Set<FixtureItem>() 118 + function traverse(children: Task[]) { 119 + for (const child of children) { 120 + if (child.type === 'test') { 121 + const childFixtures = getTestFixture(child.context) || [] 122 + for (const fixture of childFixtures) { 123 + // TODO: what if overriden? 124 + if (fixture.scope === 'file' && !fixtures.has(fixture)) { 125 + fixtures.add(fixture) 126 + } 127 + } 128 + } 129 + else { 130 + traverse(child.tasks) 131 + } 132 + } 133 + } 134 + traverse(file.tasks) 135 + return Array.from(fixtures) 110 136 } 111 137 112 138 function mergeHooks(baseHooks: SuiteHooks, hooks: SuiteHooks): SuiteHooks {
+63 -9
packages/runner/src/fixture.ts
··· 45 45 context: T, 46 46 inject: (key: string) => unknown, 47 47 ): T { 48 - const fixtureOptionKeys = ['auto', 'injected'] 48 + const fixtureOptionKeys = ['auto', 'injected', 'scope'] 49 49 const fixtureArray: FixtureItem[] = Object.entries(fixtures).map( 50 50 ([prop, value]) => { 51 51 const fixtureItem = { value } as FixtureItem ··· 94 94 95 95 const fixtureValueMaps = new Map<TestContext, Map<FixtureItem, any>>() 96 96 const cleanupFnArrayMap = new Map< 97 - TestContext, 97 + object, 98 98 Array<() => void | Promise<void>> 99 99 >() 100 100 101 - export async function callFixtureCleanup(context: TestContext): Promise<void> { 101 + export async function callFixtureCleanup(context: object): Promise<void> { 102 102 const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [] 103 103 for (const cleanup of cleanupFnArray.reverse()) { 104 104 await cleanup() ··· 153 153 continue 154 154 } 155 155 156 - const resolvedValue = fixture.isFn 157 - ? await resolveFixtureFunction(fixture.value, context, cleanupFnArray) 158 - : fixture.value 156 + const resolvedValue = await resolveFixtureValue( 157 + fixture, 158 + context!, 159 + cleanupFnArray, 160 + ) 159 161 context![fixture.prop] = resolvedValue 160 162 fixtureValueMap.set(fixture, resolvedValue) 161 - cleanupFnArray.unshift(() => { 162 - fixtureValueMap.delete(fixture) 163 - }) 163 + 164 + if (!fixture.scope || fixture.scope === 'test') { 165 + cleanupFnArray.unshift(() => { 166 + fixtureValueMap.delete(fixture) 167 + }) 168 + } 164 169 } 165 170 } 166 171 167 172 return resolveFixtures().then(() => fn(context)) 168 173 } 174 + } 175 + 176 + const fileFixturePromise = new WeakMap<FixtureItem, Promise<unknown>>() 177 + 178 + function resolveFixtureValue( 179 + fixture: FixtureItem, 180 + context: TestContext & { [key: string]: any }, 181 + cleanupFnArray: (() => void | Promise<void>)[], 182 + ) { 183 + if (!fixture.isFn) { 184 + return fixture.value 185 + } 186 + 187 + if (!fixture.scope || fixture.scope === 'test') { 188 + return resolveFixtureFunction( 189 + fixture.value, 190 + context, 191 + cleanupFnArray, 192 + ) 193 + } 194 + 195 + const fileContext = context.task.file.context 196 + 197 + if (fixture.prop in fileContext) { 198 + return fileContext[fixture.prop] 199 + } 200 + 201 + // in case the test runs in parallel 202 + if (fileFixturePromise.has(fixture)) { 203 + return fileFixturePromise.get(fixture)! 204 + } 205 + 206 + if (!cleanupFnArrayMap.has(fileContext)) { 207 + cleanupFnArrayMap.set(fileContext, []) 208 + } 209 + const cleanupFnFileArray = cleanupFnArrayMap.get(fileContext)! 210 + 211 + const promise = resolveFixtureFunction( 212 + fixture.value, 213 + fileContext, 214 + cleanupFnFileArray, 215 + ).then((value) => { 216 + fileContext[fixture.prop] = value 217 + fileFixturePromise.delete(fixture) 218 + return value 219 + }) 220 + 221 + fileFixturePromise.set(fixture, promise) 222 + return promise 169 223 } 170 224 171 225 async function resolveFixtureFunction(
+3
packages/runner/src/run.ts
··· 526 526 try { 527 527 await callSuiteHook(suite, suite, 'afterAll', runner, [suite]) 528 528 await callCleanupHooks(runner, beforeAllCleanups) 529 + if (suite.file === suite) { 530 + await callFixtureCleanup((suite as File).context) 531 + } 529 532 } 530 533 catch (e) { 531 534 failTask(suite.result, e, runner.config.diffOptions)
+11
packages/runner/src/types/tasks.ts
··· 239 239 * @internal 240 240 */ 241 241 local?: boolean 242 + /** @internal */ 243 + context: Record<string, unknown> 242 244 } 243 245 244 246 export interface Test<ExtraContext = object> extends TaskPopulated { ··· 479 481 export interface FixtureOptions { 480 482 /** 481 483 * Whether to automatically set up current fixture, even though it's not being used in tests. 484 + * @default false 482 485 */ 483 486 auto?: boolean 484 487 /** 485 488 * Indicated if the injected value from the config should be preferred over the fixture value 486 489 */ 487 490 injected?: boolean 491 + /** 492 + * When should the fixture be set up. 493 + * - **test**: fixture will be set up before ever test 494 + * - **worker**: fixture will be set up once per worker 495 + * - **file**: fixture will be set up once per file 496 + * @default 'test' 497 + */ 498 + scope?: 'test' | 'worker' | 'file' 488 499 } 489 500 490 501 export type Use<T> = (value: T) => Promise<void>
+1
packages/runner/src/utils/collect.ts
··· 192 192 projectName, 193 193 file: undefined!, 194 194 pool, 195 + context: Object.create(null), 195 196 } 196 197 file.file = file 197 198 return file