@@ -17,18 +17,10 @@ export interface TestFixtureItem extends FixtureOptions {
1717export type UserFixtures = Record < string , unknown >
1818export type FixtureRegistrations = Map < string , TestFixtureItem >
1919
20- const kPropsSymbol = Symbol ( 'vitest:fixture-props' )
21-
22- interface FixturePropsOptions {
23- original : any
24- index ?: number
25- }
26-
2720export class TestFixtures {
28- private _suiteContexts : WeakMap < Suite | symbol , Record < string , unknown > >
21+ private _suiteContexts : WeakMap < Suite | symbol , /* context object */ Record < string , unknown > >
2922 private _overrides = new WeakMap < Suite , FixtureRegistrations > ( )
3023 private _registrations : FixtureRegistrations
31- private _parent ?: TestFixtures
3224
3325 private static _definitions : TestFixtures [ ] = [ ]
3426 private static _builtinFixtures : string [ ] = [
@@ -56,129 +48,64 @@ export class TestFixtures {
5648 return TestFixtures . _definitions . map ( f => f . getFileContext ( file ) )
5749 }
5850
59- constructor (
60- registrations ?: FixtureRegistrations ,
61- parent ?: TestFixtures ,
62- ) {
51+ constructor ( registrations ?: FixtureRegistrations ) {
6352 this . _registrations = registrations ?? new Map ( )
64- this . _parent = parent
65-
66- // isolation fix: do NOT share contexts with parent
6753 this . _suiteContexts = new WeakMap ( )
68-
6954 TestFixtures . _definitions . push ( this )
7055 }
7156
72- extend ( runner : VitestRunner , userFixtures : UserFixtures ) : TestFixtures {
57+ extend ( runner : VitestRunner , userFixtures : UserFixtures | TestFixtures ) : TestFixtures {
7358 const { suite } = getCurrentSuite ( )
7459 const isTopLevel = ! suite || suite . file === suite
75- // Pass empty map to avoid copying parent registrations
76- const registrations = this . parseUserFixtures ( runner , userFixtures , isTopLevel , new Map ( ) )
77- return new TestFixtures ( registrations , this )
78- }
79-
80- get ( suite : Suite ) : FixtureRegistrations {
81- let currentSuite : Suite | undefined = suite
82- while ( currentSuite ) {
83- if ( this . _overrides . has ( currentSuite ) ) {
84- return this . _overrides . get ( currentSuite ) !
85- }
8660
87- if ( currentSuite === currentSuite . file ) {
88- break
61+ if ( userFixtures instanceof TestFixtures ) {
62+ const registrations = new Map ( this . _registrations )
63+ for ( const [ name , item ] of userFixtures . _registrations ) {
64+ registrations . set ( name , item )
8965 }
90- currentSuite = currentSuite . suite || currentSuite . file
91- }
92-
93- if ( this . _parent ) {
94- return new Map ( [ ...this . _parent . getPureRegistrations ( ) , ...this . _registrations ] )
66+ return new TestFixtures ( registrations )
9567 }
9668
97- return this . _registrations
69+ const registrations = this . parseUserFixtures ( runner , userFixtures , isTopLevel )
70+ return new TestFixtures ( registrations )
9871 }
9972
100- private getPureRegistrations ( ) : FixtureRegistrations {
101- if ( this . _parent ) {
102- return new Map ( [ ...this . _parent . getPureRegistrations ( ) , ...this . _registrations ] )
103- }
104- return this . _registrations
73+ getFixtures ( ) : TestFixtures {
74+ return this
10575 }
10676
107- isAncestor ( fixture : TestFixtures ) : boolean {
108- let current : TestFixtures | undefined = this as TestFixtures
109- while ( current ) {
110- if ( current === fixture ) {
111- return true
77+ get ( suite : Suite ) : FixtureRegistrations {
78+ let currentSuite : Suite | undefined = suite
79+ while ( currentSuite ) {
80+ const overrides = this . _overrides . get ( currentSuite )
81+ // return the closest override
82+ if ( overrides ) {
83+ return overrides
11284 }
113- current = current . _parent
114- }
115- return false
116- }
117-
118- resolveFixture ( name : string ) : TestFixtureItem | undefined {
119- if ( this . _registrations . has ( name ) ) {
120- return this . _registrations . get ( name )
121- }
122- if ( this . _parent ) {
123- return this . _parent . resolveFixture ( name )
124- }
125- return undefined
126- }
127-
128- toUserFixtures ( ignore ?: TestFixtures ) : UserFixtures {
129- const fixtures : UserFixtures = { }
130- const registrations = new Map < string , TestFixtureItem > ( )
131- let current : TestFixtures | undefined = this as TestFixtures
132-
133- while ( current ) {
134- if ( ignore && ignore . isAncestor ( current ) ) {
85+ if ( currentSuite === currentSuite . file ) {
13586 break
13687 }
137-
138- for ( const [ name , fixture ] of current . _registrations ) {
139- if ( registrations . has ( name ) ) {
140- continue
141- }
142- // If the fixture is just a copy from the parent, we skip it
143- // We will encounter it again when traversing up (unless it's a common ancestor)
144- if ( current . _parent ) {
145- const parentFixture = current . _parent . resolveFixture ( name )
146- if ( parentFixture === fixture ) {
147- continue
148- }
149- }
150- registrations . set ( name , fixture )
151- }
152- current = current . _parent
153- }
154-
155- for ( const [ name , fixture ] of registrations ) {
156- const options : FixtureOptions = {
157- auto : fixture . auto ,
158- scope : fixture . scope ,
159- injected : fixture . injected ,
160- }
161- let value = fixture . value
162- // Unwrap the fixture if it was wrapped by another mergeTests call
163- if ( typeof value === 'function' && kPropsSymbol in value ) {
164- const props = value [ kPropsSymbol ] as FixturePropsOptions
165- if ( props . original ) {
166- value = props . original
167- }
168- }
169- fixtures [ name ] = [ value , options ]
88+ currentSuite = currentSuite . suite || currentSuite . file
17089 }
171- return fixtures
90+ return this . _registrations
17291 }
17392
17493 override ( runner : VitestRunner , userFixtures : UserFixtures ) : void {
17594 const { suite : currentSuite , file } = getCurrentSuite ( )
17695 const suite = currentSuite || file
17796 const isTopLevel = ! currentSuite || currentSuite . file === currentSuite
97+ // Create a copy of the closest parent's registrations to avoid modifying them
98+ // For chained calls, this.get(suite) returns this suite's overrides; for first call, returns parent's
17899 const suiteRegistrations = new Map ( this . get ( suite ) )
179100 const registrations = this . parseUserFixtures ( runner , userFixtures , isTopLevel , suiteRegistrations )
180-
181- this . _overrides . set ( suite , registrations )
101+ // If defined in top-level, just override all registrations
102+ // We don't support overriding suite-level fixtures anyway (it will throw an error)
103+ if ( isTopLevel ) {
104+ this . _registrations = registrations
105+ }
106+ else {
107+ this . _overrides . set ( suite , registrations )
108+ }
182109 }
183110
184111 getFileContext ( file : File ) : Record < string , any > {
@@ -190,10 +117,7 @@ export class TestFixtures {
190117
191118 getWorkerContext ( ) : Record < string , any > {
192119 if ( ! this . _suiteContexts . has ( TestFixtures . _workerContextSymbol ) ) {
193- this . _suiteContexts . set (
194- TestFixtures . _workerContextSymbol ,
195- Object . create ( null ) ,
196- )
120+ this . _suiteContexts . set ( TestFixtures . _workerContextSymbol , Object . create ( null ) )
197121 }
198122 return this . _suiteContexts . get ( TestFixtures . _workerContextSymbol ) !
199123 }
@@ -231,8 +155,7 @@ export class TestFixtures {
231155 value = fn
232156 }
233157
234- // Check against current registrations mask or parent
235- const parent = registrations . get ( name ) || this . _registrations . get ( name ) || ( this . _parent ? this . _parent . resolveFixture ( name ) : undefined )
158+ const parent = registrations . get ( name )
236159 if ( parent && options ) {
237160 if ( parent . scope !== options . scope ) {
238161 errors . push ( new FixtureDependencyError ( `The "${ name } " fixture was already registered with a "${ options . scope } " scope.` ) )
@@ -256,10 +179,6 @@ export class TestFixtures {
256179 }
257180 }
258181
259- if ( ! options ) {
260- throw new Error ( 'Fixture options are not defined. This should not happen.' )
261- }
262-
263182 if ( options . scope && ! TestFixtures . _fixtureScopes . includes ( options . scope ) ) {
264183 errors . push ( new FixtureDependencyError ( `The "${ name } " fixture has unknown scope "${ options . scope } ".` ) )
265184 }
@@ -288,19 +207,14 @@ export class TestFixtures {
288207 }
289208 } )
290209
291- this . validateFixtureGraph ( registrations , errors )
292-
293- return registrations
294- }
295-
296- private validateFixtureGraph ( registrations : FixtureRegistrations , errors : Error [ ] = [ ] ) {
210+ // validate fixture dependency scopes
297211 for ( const fixture of registrations . values ( ) ) {
298212 for ( const depName of fixture . deps ) {
299213 if ( TestFixtures . _builtinFixtures . includes ( depName ) ) {
300214 continue
301215 }
302216
303- const dep = registrations . get ( depName ) || this . resolveFixture ( depName )
217+ const dep = registrations . get ( depName )
304218 if ( ! dep ) {
305219 errors . push ( new FixtureDependencyError ( `The "${ fixture . name } " fixture depends on unknown fixture "${ depName } ".` ) )
306220 continue
@@ -323,6 +237,7 @@ export class TestFixtures {
323237 else if ( errors . length > 1 ) {
324238 throw new AggregateError ( errors , 'Cannot resolve user fixtures. See errors for more information.' )
325239 }
240+ return registrations
326241 }
327242}
328243
@@ -507,7 +422,7 @@ function isFixtureFunction(value: unknown): value is FixtureFn<any, any, any> {
507422 return typeof value === 'function'
508423}
509424
510- async function resolveTestFixtureValue (
425+ function resolveTestFixtureValue (
511426 fixture : TestFixtureItem ,
512427 context : TestContext & { [ key : string ] : any } ,
513428 cleanupFnArray : ( ( ) => void | Promise < void > ) [ ] ,
@@ -516,12 +431,11 @@ async function resolveTestFixtureValue(
516431 return fixture . value
517432 }
518433
519- const value = await resolveFixtureFunction (
434+ return resolveFixtureFunction (
520435 fixture . value ,
521436 context ,
522437 cleanupFnArray ,
523438 )
524- return value
525439}
526440
527441const scopedFixturePromiseCache = new WeakMap < TestFixtureItem , Promise < unknown > > ( )
@@ -659,6 +573,13 @@ function validateSuiteHook(fn: Function, hook: string, error: Error | undefined)
659573 }
660574}
661575
576+ const kPropsSymbol = Symbol ( '$vitest:fixture-props' )
577+
578+ interface FixturePropsOptions {
579+ original : any
580+ index ?: number
581+ }
582+
662583export function configureProps ( fn : Function , options : FixturePropsOptions ) : void {
663584 Object . defineProperty ( fn , kPropsSymbol , {
664585 value : options ,
0 commit comments