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