Skip to content

Commit 86010b3

Browse files
committed
feat: draft implementation
1 parent 6373ecc commit 86010b3

File tree

5 files changed

+106
-11
lines changed

5 files changed

+106
-11
lines changed

packages/runner/src/collect.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import type { FixtureItem } from './fixture'
12
import type { FileSpecification, VitestRunner } from './types/runner'
2-
import type { File, SuiteHooks } from './types/tasks'
3+
import type { File, SuiteHooks, Task } from './types/tasks'
34
import { toArray } from '@vitest/utils'
45
import { processError } from '@vitest/utils/error'
56
import { collectorContext } from './context'
6-
import { getHooks, setHooks } from './map'
7+
import { getHooks, getTestFixture, setHooks, setTestFixture } from './map'
78
import { runSetupFiles } from './setup'
89
import {
910
clearCollectorContext,
@@ -104,11 +105,36 @@ export async function collectTests(
104105
}
105106

106107
files.push(file)
108+
109+
// TODO: any
110+
setTestFixture(file.context as any, getFileFixtires(file))
107111
}
108112

109113
return files
110114
}
111115

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)
136+
}
137+
112138
function mergeHooks(baseHooks: SuiteHooks, hooks: SuiteHooks): SuiteHooks {
113139
for (const _key in hooks) {
114140
const key = _key as keyof SuiteHooks

packages/runner/src/fixture.ts

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function mergeContextFixtures<T extends { fixtures?: FixtureItem[] }>(
4545
context: T,
4646
inject: (key: string) => unknown,
4747
): T {
48-
const fixtureOptionKeys = ['auto', 'injected']
48+
const fixtureOptionKeys = ['auto', 'injected', 'scope']
4949
const fixtureArray: FixtureItem[] = Object.entries(fixtures).map(
5050
([prop, value]) => {
5151
const fixtureItem = { value } as FixtureItem
@@ -94,11 +94,11 @@ export function mergeContextFixtures<T extends { fixtures?: FixtureItem[] }>(
9494

9595
const fixtureValueMaps = new Map<TestContext, Map<FixtureItem, any>>()
9696
const cleanupFnArrayMap = new Map<
97-
TestContext,
97+
object,
9898
Array<() => void | Promise<void>>
9999
>()
100100

101-
export async function callFixtureCleanup(context: TestContext): Promise<void> {
101+
export async function callFixtureCleanup(context: object): Promise<void> {
102102
const cleanupFnArray = cleanupFnArrayMap.get(context) ?? []
103103
for (const cleanup of cleanupFnArray.reverse()) {
104104
await cleanup()
@@ -153,21 +153,75 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
153153
continue
154154
}
155155

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+
)
159161
context![fixture.prop] = resolvedValue
160162
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+
}
164169
}
165170
}
166171

167172
return resolveFixtures().then(() => fn(context))
168173
}
169174
}
170175

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
223+
}
224+
171225
async function resolveFixtureFunction(
172226
fixtureFn: (
173227
context: unknown,

packages/runner/src/run.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@ export async function runSuite(suite: Suite, runner: VitestRunner): Promise<void
526526
try {
527527
await callSuiteHook(suite, suite, 'afterAll', runner, [suite])
528528
await callCleanupHooks(runner, beforeAllCleanups)
529+
if (suite.file === suite) {
530+
await callFixtureCleanup((suite as File).context)
531+
}
529532
}
530533
catch (e) {
531534
failTask(suite.result, e, runner.config.diffOptions)

packages/runner/src/types/tasks.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ export interface File extends Suite {
239239
* @internal
240240
*/
241241
local?: boolean
242+
/** @internal */
243+
context: Record<string, unknown>
242244
}
243245

244246
export interface Test<ExtraContext = object> extends TaskPopulated {
@@ -479,12 +481,21 @@ export type { TestAPI as CustomAPI }
479481
export interface FixtureOptions {
480482
/**
481483
* Whether to automatically set up current fixture, even though it's not being used in tests.
484+
* @default false
482485
*/
483486
auto?: boolean
484487
/**
485488
* Indicated if the injected value from the config should be preferred over the fixture value
486489
*/
487490
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'
488499
}
489500

490501
export type Use<T> = (value: T) => Promise<void>

packages/runner/src/utils/collect.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export function createFileTask(
192192
projectName,
193193
file: undefined!,
194194
pool,
195+
context: Object.create(null),
195196
}
196197
file.file = file
197198
return file

0 commit comments

Comments
 (0)