1414/* eslint-disable @typescript-eslint/no-explicit-any */
1515import { setup , teardown } from "mocha" ;
1616import { Disposable , Event , EventEmitter } from "vscode" ;
17- import { instance , mock } from "ts-mockito" ;
17+ import { instance , mock , when } from "ts-mockito" ;
18+ import { MethodToStub } from "ts-mockito/lib/MethodToStub" ;
1819
1920export function getMochaHooks ( ) : { setup : typeof setup ; teardown : typeof teardown } {
2021 if ( ! ( "setup" in global && "teardown" in global ) ) {
@@ -135,33 +136,54 @@ export function eventListenerMock<T, K extends EventsOf<T>>(
135136 obj : T ,
136137 method : K
137138) : ListenerInterceptor < EventType < T [ K ] > > {
138- const interceptor = new ListenerInterceptorImpl < EventType < T [ K ] > > ( ) ;
139- const originalValue : T [ K ] = obj [ method ] ;
140139 const mocha = getMochaHooks ( ) ;
140+ let interceptor : ListenerInterceptorImpl < EventType < T [ K ] > > ;
141+ let originalValue : T [ K ] ;
141142 mocha . setup ( ( ) => {
142- Object . defineProperty ( obj , method , { value : interceptor . addListener } ) ;
143+ interceptor = new ListenerInterceptorImpl < EventType < T [ K ] > > ( ) ;
144+ originalValue = obj [ method ] ;
145+ if ( originalValue instanceof MethodToStub ) {
146+ when ( originalValue ) . thenReturn ( interceptor . addListener as any ) ;
147+ } else {
148+ Object . defineProperty ( obj , method , { value : interceptor . addListener } ) ;
149+ }
143150 } ) ;
144151 // Restore original value at teardown
145152 mocha . teardown ( ( ) => {
146- Object . defineProperty ( obj , method , { value : originalValue } ) ;
153+ if ( ! ( obj [ method ] instanceof MethodToStub ) ) {
154+ Object . defineProperty ( obj , method , { value : originalValue } ) ;
155+ }
147156 } ) ;
148- return interceptor ;
157+ // Return the proxy to the interceptor
158+ return new Proxy (
159+ { } ,
160+ {
161+ get : ( target : any , property : string ) : any => {
162+ if ( ! interceptor ) {
163+ throw Error ( "Interceptor proxy accessed before setup()" ) ;
164+ }
165+ return ( interceptor as any ) [ property ] ;
166+ } ,
167+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
168+ set : ( target : any , property : string , value : any ) : boolean => {
169+ // Ignore
170+ return true ;
171+ } ,
172+ }
173+ ) ;
149174}
150175
151176/**
152- * Allows setting the global value.
177+ * Allows setting the constant value.
153178 */
154- export interface GlobalVariableMock < T > {
179+ export interface ValueMock < T > {
155180 setValue ( value : T ) : void ;
156181}
157182
158183/**
159- * Create a new GlobalVariableMock that is restored after a test completes.
184+ * Create a new ValueMock that is restored after a test completes.
160185 */
161- export function globalVariableMock < T , K extends keyof T > (
162- obj : T ,
163- property : K
164- ) : GlobalVariableMock < T [ K ] > {
186+ export function mockValue < T , K extends keyof T > ( obj : T , property : K ) : ValueMock < T [ K ] > {
165187 let setupComplete : boolean = false ;
166188 let originalValue : T [ K ] ;
167189 const mocha = getMochaHooks ( ) ;
@@ -175,15 +197,56 @@ export function globalVariableMock<T, K extends keyof T>(
175197 Object . defineProperty ( obj , property , { value : originalValue } ) ;
176198 setupComplete = false ;
177199 } ) ;
178- // Return a GlobalVariableMock that allows for easy mocking of the value
200+ // Return a ValueMock that allows for easy mocking of the value
179201 return {
180202 setValue ( value : T [ K ] ) : void {
181203 if ( ! setupComplete ) {
182204 throw new Error (
183- `'${ String ( property ) } ' cannot be set before globalVariableMock () completes its setup through Mocha`
205+ `'${ String ( property ) } ' cannot be set before mockValue () completes its setup through Mocha`
184206 ) ;
185207 }
186208 Object . defineProperty ( obj , property , { value : value } ) ;
187209 } ,
188210 } ;
189211}
212+
213+ type Constructor < T > = T extends abstract new ( ...args : any ) => any ? T : never ;
214+ type Instance < T > = InstanceType < Constructor < T > > ;
215+
216+ export function mockConstructor < T , K extends keyof T > ( obj : T , property : K ) : Instance < T [ K ] > {
217+ const clazz = obj [ property ] as Constructor < T [ K ] > ;
218+ let realMock : Instance < T [ K ] > ;
219+
220+ // Replace constructor with a proxy that returns mock instance
221+ const classValueMock = mockValue ( obj , property ) ;
222+ const mocha = getMochaHooks ( ) ;
223+ mocha . setup ( ( ) => {
224+ realMock = mock ( clazz ) ;
225+ classValueMock . setValue (
226+ new Proxy ( clazz , {
227+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
228+ construct ( target ) {
229+ return instance ( realMock ) as object ;
230+ } ,
231+ } ) as T [ K ]
232+ ) ;
233+ } ) ;
234+
235+ // Return the proxy to the real mock
236+ return new Proxy (
237+ { } ,
238+ {
239+ get : ( target : any , property : string ) : any => {
240+ if ( ! realMock ) {
241+ throw Error ( "Mock proxy accessed before setup()" ) ;
242+ }
243+ return ( realMock as any ) [ property ] ;
244+ } ,
245+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
246+ set : ( target : any , property : string , value : any ) : boolean => {
247+ // Ignore
248+ return true ;
249+ } ,
250+ }
251+ ) ;
252+ }
0 commit comments