1+ const m = require ( 'module' ) ;
2+
3+ export interface MockOptions {
4+ useCleanCache ?: boolean ,
5+ warnOnReplace ?: boolean ,
6+ warnOnUnregistered ?: boolean
7+ } ;
8+
9+ let registeredMocks = { } ;
10+ let registeredAllowables = new Set < string > ( ) ;
11+ let originalLoader : Function | null = null ;
12+ let originalCache : Record < string , any > = { } ;
13+ let options : MockOptions = { } ;
14+ let defaultOptions : MockOptions = {
15+ useCleanCache : false ,
16+ warnOnReplace : true ,
17+ warnOnUnregistered : true
18+ } ;
19+
20+ function _getEffectiveOptions ( opts : MockOptions ) : MockOptions {
21+ var options : MockOptions = { } ;
22+
23+ Object . keys ( defaultOptions ) . forEach ( function ( key ) {
24+ if ( opts && opts . hasOwnProperty ( key ) ) {
25+ options [ key ] = opts [ key ] ;
26+ } else {
27+ options [ key ] = defaultOptions [ key ] ;
28+ }
29+ } ) ;
30+ return options ;
31+ }
32+
33+ /*
34+ * Loader function that used when hooking is enabled.
35+ * if the requested module is registered as a mock, return the mock.
36+ * otherwise, invoke the original loader + put warning in the output.
37+ */
38+ function _hookedLoader ( request : string , parent , isMain : boolean ) {
39+ if ( ! originalLoader ) {
40+ throw new Error ( "Loader has not been hooked" ) ;
41+ }
42+
43+ if ( registeredMocks . hasOwnProperty ( request ) ) {
44+ return registeredMocks [ request ] ;
45+ }
46+
47+ if ( ! registeredAllowables . has ( request ) && options . warnOnUnregistered ) {
48+ console . warn ( "WARNING: loading non-allowed module: " + request ) ;
49+ }
50+
51+ return originalLoader ( request , parent , isMain ) ;
52+ }
53+
54+
55+ /**
56+ * Remove references to modules in the cache from
57+ * their parents' children.
58+ */
59+ function _removeParentReferences ( ) : void {
60+ Object . keys ( m . _cache ) . forEach ( function ( k ) {
61+ if ( k . indexOf ( '\.node' ) === - 1 ) {
62+ // don't touch native modules, because they're special
63+ const mod = m . _cache [ k ] ;
64+ const idx = mod ?. parent ?. children . indexOf ( mod ) ;
65+ if ( idx > - 1 ) {
66+ mod . parent . children . splice ( idx , 1 ) ;
67+ }
68+ }
69+ } ) ;
70+ }
71+
72+ /*
73+ * Starting in node 0.12 node won't reload native modules
74+ * The reason is that native modules can register themselves to be loaded automatically
75+ * This will re-populate the cache with the native modules that have not been mocked
76+ */
77+ function _repopulateNative ( ) : void {
78+ Object . keys ( originalCache ) . forEach ( function ( k ) {
79+ if ( k . indexOf ( '\.node' ) > - 1 && ! m . _cache [ k ] ) {
80+ m . _cache [ k ] = originalCache [ k ] ;
81+ }
82+ } ) ;
83+ }
84+
85+ /*
86+ * Enable function, hooking the Node loader with options.
87+ */
88+ export function enable ( opts : MockOptions ) : void {
89+ if ( originalLoader ) {
90+ // Already hooked
91+ return ;
92+ }
93+
94+ options = _getEffectiveOptions ( opts ) ;
95+
96+ if ( options . useCleanCache ) {
97+ originalCache = m . _cache ;
98+ m . _cache = { } ;
99+ _repopulateNative ( ) ;
100+ }
101+
102+ originalLoader = m . _load ;
103+ m . _load = _hookedLoader ;
104+ }
105+
106+ /*
107+ * Disables mock loading, reverting to normal 'require' behaviour.
108+ */
109+ export function disable ( ) : void {
110+ if ( ! originalLoader ) return ;
111+
112+ if ( options . useCleanCache ) {
113+ Object . keys ( m . _cache ) . forEach ( function ( k ) {
114+ if ( k . indexOf ( '\.node' ) > - 1 && ! originalCache [ k ] ) {
115+ originalCache [ k ] = m . _cache [ k ] ;
116+ }
117+ } ) ;
118+ _removeParentReferences ( ) ;
119+ m . _cache = originalCache ;
120+ originalCache = { } ;
121+ }
122+
123+ m . _load = originalLoader ;
124+ originalLoader = null ;
125+ }
126+
127+ /*
128+ * If the clean cache option is in effect, reset the module cache to an empty
129+ * state. Calling this function when the clean cache option is not in effect
130+ * will have no ill effects, but will do nothing.
131+ */
132+ export function resetCache ( ) : void {
133+ if ( options . useCleanCache && originalCache ) {
134+ _removeParentReferences ( ) ;
135+ m . _cache = { } ;
136+ _repopulateNative ( ) ;
137+ }
138+ }
139+
140+ /*
141+ * Enable or disable warnings to the console when previously registered mocks are replaced.
142+ */
143+ export function warnOnReplace ( enable : boolean ) : void {
144+ options . warnOnReplace = enable ;
145+ }
146+
147+ /*
148+ * Enable or disable warnings to the console when modules are loaded that have
149+ * not been registered as a mock.
150+ */
151+ export function warnOnUnregistered ( enable : boolean ) : void {
152+ options . warnOnUnregistered = enable ;
153+ }
154+
155+ /*
156+ * Register a mock object for the specified module.
157+ */
158+ export function registerMock ( mod : string , mock ) : void {
159+ if ( options . warnOnReplace && registeredMocks . hasOwnProperty ( mod ) ) {
160+ console . warn ( "WARNING: Replacing existing mock for module: " + mod ) ;
161+ }
162+ registeredMocks [ mod ] = mock ;
163+ }
164+
165+ /*
166+ * Deregister a mock object for the specified module.
167+ */
168+ export function deregisterMock ( mod : string ) : void {
169+ if ( registeredMocks . hasOwnProperty ( mod ) ) {
170+ delete registeredMocks [ mod ] ;
171+ }
172+ }
173+
174+ /*
175+ * Deregister all mocks.
176+ */
177+ export function deregisterAll ( ) : void {
178+ registeredMocks = { } ;
179+ registeredAllowables = new Set ( ) ;
180+ }
181+
182+ /*
183+ Register a module as 'allowed'.
184+ This will allow the module to be loaded without mock otherwise a warning would be thrown.
185+ */
186+ export function registerAllowable ( mod : string ) : void {
187+ registeredAllowables . add ( mod ) ;
188+ }
189+
190+ /*
191+ * Register an array of 'allowed' modules.
192+ */
193+ export function registerAllowables ( mods : string [ ] ) : void {
194+ mods . forEach ( ( mod ) => registerAllowable ( mod ) ) ;
195+ }
196+
197+ /*
198+ * Deregister a module as 'allowed'.
199+ */
200+ export function deregisterAllowable ( mod : string ) : void {
201+ if ( registeredAllowables . hasOwnProperty ( mod ) ) {
202+ registeredAllowables . delete ( mod ) ;
203+ }
204+ }
205+
206+ /*
207+ * Deregister an array of modules as 'allowed'.
208+ */
209+ export function deregisterAllowables ( mods ) {
210+ mods . forEach ( function ( mod ) {
211+ deregisterAllowable ( mod ) ;
212+ } ) ;
213+ }
0 commit comments