@@ -14,6 +14,7 @@ interface SpyModule {
14
14
export class VitestBrowserClientMocker {
15
15
private queue = new Set < Promise < void > > ( )
16
16
private mocks : Record < string , undefined | null | string > = { }
17
+ private behaviours : Record < string , 'autospy' | 'automock' | 'manual' > = { }
17
18
private mockObjects : Record < string , any > = { }
18
19
private factories : Record < string , ( ) => any > = { }
19
20
private ids = new Set < string > ( )
@@ -91,14 +92,20 @@ export class VitestBrowserClientMocker {
91
92
return await this . resolve ( resolvedId )
92
93
}
93
94
94
- if ( type === 'redirect' ) {
95
- const url = new URL ( `/@id/${ mockPath } ` , location . href )
96
- return import ( /* @vite -ignore */ url . toString ( ) )
95
+ const behavior = this . behaviours [ resolvedId ] || 'automock'
96
+
97
+ if ( type === 'automock' || behavior === 'autospy' ) {
98
+ const url = new URL ( `/@id/${ resolvedId } ` , location . href )
99
+ const query = url . search ? `${ url . search } &t=${ now ( ) } ` : `?t=${ now ( ) } `
100
+ const moduleObject = await import ( /* @vite -ignore */ `${ url . pathname } ${ query } &mock=${ behavior } ${ url . hash } ` )
101
+ return this . mockObject ( moduleObject , { } , behavior )
102
+ }
103
+
104
+ if ( typeof mockPath !== 'string' ) {
105
+ throw new TypeError ( `Mock path is not a string: ${ mockPath } . This is a bug in Vitest. Please, open a new issue with reproduction.` )
97
106
}
98
- const url = new URL ( `/@id/${ resolvedId } ` , location . href )
99
- const query = url . search ? `${ url . search } &t=${ now ( ) } ` : `?t=${ now ( ) } `
100
- const moduleObject = await import ( /* @vite -ignore */ `${ url . pathname } ${ query } ${ url . hash } ` )
101
- return this . mockObject ( moduleObject )
107
+ const url = new URL ( `/@id/${ mockPath } ` , location . href )
108
+ return import ( /* @vite -ignore */ url . toString ( ) )
102
109
}
103
110
104
111
public getMockContext ( ) {
@@ -142,32 +149,35 @@ export class VitestBrowserClientMocker {
142
149
}
143
150
}
144
151
145
- public queueMock ( id : string , importer : string , factory ?: ( ) => any ) {
152
+ public queueMock ( id : string , importer : string , factoryOrOptions ?: MockOptions | ( ( ) => any ) ) {
146
153
const promise = rpc ( )
147
- . resolveMock ( id , importer , ! ! factory )
154
+ . resolveMock ( id , importer , typeof factoryOrOptions === 'function' )
148
155
. then ( async ( { mockPath, resolvedId, needsInterop } ) => {
149
156
this . ids . add ( resolvedId )
150
157
const urlPaths = resolveMockPaths ( cleanVersion ( resolvedId ) )
151
158
const resolvedMock
152
159
= typeof mockPath === 'string'
153
160
? new URL ( resolvedMockedPath ( cleanVersion ( mockPath ) ) , location . href ) . toString ( )
154
161
: mockPath
155
- const _factory = factory && needsInterop
162
+ const factory = typeof factoryOrOptions === 'function'
156
163
? async ( ) => {
157
- const data = await factory ( )
158
- return { default : data }
164
+ const data = await factoryOrOptions ( )
165
+ return needsInterop ? { default : data } : data
159
166
}
160
- : factory
167
+ : undefined
168
+ const behaviour = getMockBehaviour ( factoryOrOptions )
161
169
urlPaths . forEach ( ( url ) => {
162
170
this . mocks [ url ] = resolvedMock
163
- if ( _factory ) {
164
- this . factories [ url ] = _factory
171
+ if ( factory ) {
172
+ this . factories [ url ] = factory
165
173
}
174
+ this . behaviours [ url ] = behaviour
166
175
} )
167
176
channel . postMessage ( {
168
177
type : 'mock' ,
169
178
paths : urlPaths ,
170
179
mock : resolvedMock ,
180
+ behaviour,
171
181
} )
172
182
await waitForChannel ( 'mock:done' )
173
183
} )
@@ -214,6 +224,7 @@ export class VitestBrowserClientMocker {
214
224
public mockObject (
215
225
object : Record < Key , any > ,
216
226
mockExports : Record < Key , any > = { } ,
227
+ behaviour : 'autospy' | 'automock' | 'manual' = 'automock' ,
217
228
) {
218
229
const finalizers = new Array < ( ) => void > ( )
219
230
const refs = new RefTracker ( )
@@ -330,13 +341,14 @@ export class VitestBrowserClientMocker {
330
341
}
331
342
}
332
343
}
333
- const mock = spyModule
334
- . spyOn ( newContainer , property )
335
- . mockImplementation ( mockFunction )
336
- mock . mockRestore = ( ) => {
337
- mock . mockReset ( )
344
+ const mock = spyModule . spyOn ( newContainer , property )
345
+ if ( behaviour === 'automock' ) {
338
346
mock . mockImplementation ( mockFunction )
339
- return mock
347
+ mock . mockRestore = ( ) => {
348
+ mock . mockReset ( )
349
+ mock . mockImplementation ( mockFunction )
350
+ return mock
351
+ }
340
352
}
341
353
// tinyspy retains length, but jest doesn't.
342
354
Object . defineProperty ( newContainer [ property ] , 'length' , { value : 0 } )
@@ -465,3 +477,19 @@ const versionRegexp = /(\?|&)v=\w{8}/
465
477
function cleanVersion ( url : string ) {
466
478
return url . replace ( versionRegexp , '' )
467
479
}
480
+
481
+ export interface MockOptions {
482
+ spy ?: boolean
483
+ }
484
+
485
+ export type MockBehaviour = 'autospy' | 'automock' | 'manual'
486
+
487
+ function getMockBehaviour ( factoryOrOptions ?: ( ( ) => void ) | MockOptions ) : MockBehaviour {
488
+ if ( ! factoryOrOptions ) {
489
+ return 'automock'
490
+ }
491
+ if ( typeof factoryOrOptions === 'function' ) {
492
+ return 'manual'
493
+ }
494
+ return factoryOrOptions . spy ? 'autospy' : 'automock'
495
+ }
0 commit comments