1+ /**
2+ * Jest-specific setup for unified testing framework abstraction.
3+ *
4+ * This file provides Jest implementations of the unified testing utilities
5+ * available on the global `runner` object. It creates a consistent interface
6+ * that allows the library's tests to work identically across Jest, Vitest,
7+ * and SWC test environments.
8+ *
9+ * The key pattern here is the SmartSpy system, which wraps framework-specific
10+ * spy objects (like Jest's SpyInstance) to provide a unified API.
11+ */
12+
13+ /**
14+ * Unified spy interface that works consistently across Jest and Vitest.
15+ *
16+ * This interface defines the common spy methods that our library needs,
17+ * providing a consistent API regardless of the underlying testing framework.
18+ * The implementation only includes the methods we actually use in our tests.
19+ */
20+ interface SmartSpy {
21+ /** Mock the implementation of the spied method */
22+ mockImplementation : ( fn : ( ) => void ) => SmartSpy ;
23+ /** Assert that the spy was called with specific arguments */
24+ toHaveBeenCalledWith : ( ...args : unknown [ ] ) => void ;
25+ /** Restore the original method implementation */
26+ mockRestore : ( ) => void ;
27+ /** Allow access to any other spy properties/methods from the underlying framework */
28+ [ key : string ] : unknown ;
29+ }
30+
131function useFakeTimers ( ) {
232 jest . useFakeTimers ( ) ;
333}
@@ -14,32 +44,78 @@ function fn() {
1444 return jest . fn ( ) ;
1545}
1646
17- // Smart proxy that only implements what we need
18- function createSmartSpy ( realSpy : unknown ) {
47+ /**
48+ * Creates a unified spy interface that works consistently across Jest and Vitest.
49+ *
50+ * This function wraps a Jest SpyInstance in a Proxy to provide a consistent API
51+ * that matches the SmartSpy interface expected by the testing framework abstraction.
52+ *
53+ * The proxy intercepts calls to specific methods (mockImplementation, toHaveBeenCalledWith,
54+ * mockRestore) and ensures they work the same way regardless of whether the underlying
55+ * spy is from Jest or Vitest. For all other properties/methods, it passes through
56+ * to the original spy.
57+ *
58+ * This is part of the broader pattern in this library where we create unified interfaces
59+ * across different testing frameworks (Jest, Vitest, SWC) so that the library's mocks
60+ * work consistently in any environment.
61+ *
62+ * @param realSpy - The underlying Jest SpyInstance to wrap
63+ * @returns A proxy that implements the SmartSpy interface
64+ */
65+ function createSmartSpy ( realSpy : jest . SpyInstance ) : SmartSpy {
1966 return new Proxy ( realSpy as object , {
2067 get ( target , prop ) {
2168 // Only implement the methods we actually use
2269 if ( prop === 'mockImplementation' ) {
2370 return ( fn : ( ) => void ) => {
24- ( target as { mockImplementation : ( fn : ( ) => void ) => unknown } ) . mockImplementation ( fn ) ;
25- return createSmartSpy ( target ) ;
71+ ( target as jest . SpyInstance ) . mockImplementation ( fn ) ;
72+ return createSmartSpy ( target as jest . SpyInstance ) ;
2673 } ;
2774 }
2875 if ( prop === 'toHaveBeenCalledWith' ) {
29- return ( target as { toHaveBeenCalledWith : ( ...args : unknown [ ] ) => unknown } ) . toHaveBeenCalledWith . bind ( target ) ;
76+ return ( ...args : unknown [ ] ) => {
77+ // Jest SpyInstance doesn't have toHaveBeenCalledWith as a method,
78+ // it's available through expect(spy).toHaveBeenCalledWith()
79+ // For compatibility, we'll check if the method exists and call it
80+ const spy = target as Record < string , unknown > ;
81+ if ( typeof spy . toHaveBeenCalledWith === 'function' ) {
82+ return (
83+ spy . toHaveBeenCalledWith as ( ...args : unknown [ ] ) => unknown
84+ ) ( ...args ) ;
85+ }
86+ return undefined ;
87+ } ;
3088 }
3189 if ( prop === 'mockRestore' ) {
32- return ( target as { mockRestore : ( ) => void } ) . mockRestore . bind ( target ) ;
90+ return ( ) => {
91+ ( target as jest . SpyInstance ) . mockRestore ( ) ;
92+ } ;
3393 }
34-
94+
3595 // For everything else, just pass through to the real spy
3696 return ( target as Record < string | symbol , unknown > ) [ prop ] ;
37- }
38- } ) ;
97+ } ,
98+ } ) as SmartSpy ;
3999}
40100
41- function spyOn < T extends object , K extends keyof T > ( object : T , method : K ) {
42- const realSpy = ( jest . spyOn as ( obj : T , method : K ) => unknown ) ( object , method ) ;
101+ /**
102+ * Creates a spy on an object method using Jest's spyOn, wrapped with SmartSpy interface.
103+ *
104+ * This is the Jest-specific implementation of the unified spyOn function that's available
105+ * on the global `runner` object. It creates a Jest spy and wraps it with createSmartSpy
106+ * to provide a consistent interface across testing frameworks.
107+ *
108+ * @param object - The object to spy on
109+ * @param method - The method name to spy on
110+ * @returns A SmartSpy that provides unified spy functionality
111+ */
112+ function spyOn < T extends object , K extends keyof T > (
113+ object : T ,
114+ method : K
115+ ) : SmartSpy {
116+ // Use a more specific type assertion to avoid 'any'
117+ const spyFunction = jest . spyOn as ( object : T , method : K ) => jest . SpyInstance ;
118+ const realSpy = spyFunction ( object , method ) ;
43119 return createSmartSpy ( realSpy ) ;
44120}
45121
0 commit comments