@jsenv/snapshot 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/snapshot",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Snapshot testing",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -0,0 +1,261 @@
1
+ const jsenvMethodProxySymbol = Symbol.for("jsenv_method_proxy");
2
+
3
+ export const hookIntoMethod = (
4
+ object,
5
+ method,
6
+ initCallback,
7
+ { execute = METHOD_EXECUTION_STANDARD } = {},
8
+ ) => {
9
+ const current = object[method];
10
+ const jsenvSymbolValue = current[jsenvMethodProxySymbol];
11
+ let addInitCallback;
12
+ let removeInitCallback;
13
+ if (jsenvSymbolValue) {
14
+ addInitCallback = jsenvSymbolValue.addInitCallback;
15
+ removeInitCallback = jsenvSymbolValue.removeInitCallback;
16
+ } else {
17
+ const original = current;
18
+ let allWantsToPreventOriginalCall;
19
+ let hookExecuting;
20
+ const initCallbackSet = new Set();
21
+ const callHooks = (hookCallbackSet, ...args) => {
22
+ hookExecuting = true;
23
+ for (const hookCallback of hookCallbackSet) {
24
+ hookCallback(...args);
25
+ }
26
+ hookExecuting = false;
27
+ hookCallbackSet.clear();
28
+ };
29
+
30
+ const proxy = function (...args) {
31
+ if (hookExecuting) {
32
+ // when a spy is executing
33
+ // if it calls the method himself
34
+ // then we want this call to go trough
35
+ // and others spy should not know about it
36
+ return original.call(this, ...args);
37
+ }
38
+ allWantsToPreventOriginalCall = undefined;
39
+ const returnPromiseCallbackSet = new Set();
40
+ const returnCallbackSet = new Set();
41
+ const catchCallbackSet = new Set();
42
+ const finallyCallbackSet = new Set();
43
+ hookExecuting = true;
44
+ for (const initCallback of initCallbackSet) {
45
+ if (initCallback.disabled) {
46
+ continue;
47
+ }
48
+ const hooks = initCallback(...args) || {};
49
+ if (hooks.preventOriginalCall) {
50
+ if (allWantsToPreventOriginalCall === undefined) {
51
+ allWantsToPreventOriginalCall = true;
52
+ }
53
+ } else {
54
+ allWantsToPreventOriginalCall = false;
55
+ }
56
+ if (hooks.returnPromise) {
57
+ returnPromiseCallbackSet.add(hooks.returnPromise);
58
+ }
59
+ if (hooks.return) {
60
+ returnCallbackSet.add(hooks.return);
61
+ }
62
+ if (hooks.catch) {
63
+ catchCallbackSet.add(hooks.catch);
64
+ }
65
+ if (hooks.finally) {
66
+ finallyCallbackSet.add(hooks.catch);
67
+ }
68
+ }
69
+ hookExecuting = false;
70
+ const onCatch = (valueThrown) => {
71
+ returnCallbackSet.clear();
72
+ callHooks(catchCallbackSet, valueThrown);
73
+ };
74
+ const onReturn = (...values) => {
75
+ returnPromiseCallbackSet.clear();
76
+ catchCallbackSet.clear();
77
+ callHooks(returnCallbackSet, ...values);
78
+ };
79
+ const onReturnPromise = () => {
80
+ callHooks(returnPromiseCallbackSet);
81
+ };
82
+ const onFinally = () => {
83
+ callHooks(finallyCallbackSet);
84
+ };
85
+ if (allWantsToPreventOriginalCall) {
86
+ onReturn(undefined);
87
+ onFinally();
88
+ return undefined;
89
+ }
90
+ return execute({
91
+ original,
92
+ thisValue: this,
93
+ args,
94
+ onCatch,
95
+ onReturn,
96
+ onReturnPromise,
97
+ onFinally,
98
+ });
99
+ };
100
+ addInitCallback = (initCallback) => {
101
+ if (initCallbackSet.size === 0) {
102
+ object[method] = proxy;
103
+ }
104
+ initCallbackSet.add(initCallback);
105
+ };
106
+ removeInitCallback = (initCallback) => {
107
+ initCallbackSet.delete(initCallback);
108
+ if (initCallbackSet.size === 0) {
109
+ object[method] = original;
110
+ }
111
+ };
112
+ proxy[jsenvMethodProxySymbol] = {
113
+ addInitCallback,
114
+ removeInitCallback,
115
+ original,
116
+ };
117
+ object[method] = proxy;
118
+ }
119
+ addInitCallback(initCallback);
120
+ const hook = {
121
+ disable: () => {
122
+ initCallback.disabled = true;
123
+ },
124
+ enable: () => {
125
+ initCallback.disabled = false;
126
+ },
127
+ remove: () => {
128
+ removeInitCallback(initCallback);
129
+ },
130
+ };
131
+ return hook;
132
+ };
133
+ export const METHOD_EXECUTION_STANDARD = ({
134
+ original,
135
+ thisValue,
136
+ args,
137
+ onCatch,
138
+ onFinally,
139
+ onReturnPromise,
140
+ onReturn,
141
+ }) => {
142
+ let valueReturned;
143
+ let thrown = false;
144
+ let valueThrown;
145
+ try {
146
+ valueReturned = original.call(thisValue, ...args);
147
+ } catch (e) {
148
+ thrown = true;
149
+ valueThrown = e;
150
+ }
151
+ if (thrown) {
152
+ onCatch(valueThrown);
153
+ onFinally();
154
+ throw valueThrown;
155
+ }
156
+ if (valueReturned && typeof valueReturned.then === "function") {
157
+ onReturnPromise();
158
+ valueReturned.then(
159
+ (valueResolved) => {
160
+ onReturn(valueResolved);
161
+ onFinally();
162
+ },
163
+ (valueRejected) => {
164
+ onCatch(valueRejected);
165
+ onFinally();
166
+ },
167
+ );
168
+ return valueReturned;
169
+ }
170
+ onReturn(valueReturned);
171
+ onFinally();
172
+ return valueReturned;
173
+ };
174
+ export const METHOD_EXECUTION_NODE_CALLBACK = ({
175
+ original,
176
+ thisValue,
177
+ args,
178
+ onCatch,
179
+ onFinally,
180
+ onReturnPromise,
181
+ onReturn,
182
+ }) => {
183
+ const lastArgIndex = args.length - 1;
184
+ const lastArg = args[lastArgIndex];
185
+ const installProxyAndCall = (originalCallback, installCallbackProxy) => {
186
+ const callbackProxy = function (...callbackArgs) {
187
+ uninstallCallbackProxy();
188
+ const [error, ...remainingArgs] = callbackArgs;
189
+ if (error) {
190
+ onCatch(error);
191
+ originalCallback.call(this, ...callbackArgs);
192
+ onFinally();
193
+ return;
194
+ }
195
+ onReturn(...remainingArgs);
196
+ originalCallback.call(this, ...callbackArgs);
197
+ onFinally();
198
+ };
199
+ const uninstallCallbackProxy = installCallbackProxy(callbackProxy);
200
+ try {
201
+ return original.call(thisValue, ...args);
202
+ } catch (e) {
203
+ onCatch(e);
204
+ onFinally();
205
+ throw e;
206
+ }
207
+ };
208
+ if (typeof lastArg === "function") {
209
+ return installProxyAndCall(lastArg, (callbackProxy) => {
210
+ args[lastArgIndex] = callbackProxy;
211
+ return () => {
212
+ // useless because are a copy of the args
213
+ // so the mutation we do above ( args[lastArgIndex] =)
214
+ // cannot be important for the method being proxied
215
+ args[lastArgIndex] = lastArg;
216
+ };
217
+ });
218
+ }
219
+ if (lastArg && typeof lastArg === "object") {
220
+ if (lastArg.context && typeof lastArg.context.callback === "function") {
221
+ const originalCallback = lastArg.context.callback;
222
+ return installProxyAndCall(originalCallback, (callbackProxy) => {
223
+ lastArg.context.callback = callbackProxy;
224
+ return () => {
225
+ lastArg.context.callback = originalCallback;
226
+ };
227
+ });
228
+ }
229
+ if (typeof lastArg.oncomplete === "function") {
230
+ const originalCallback = lastArg.oncomplete;
231
+ return installProxyAndCall(originalCallback, (callbackProxy) => {
232
+ lastArg.oncomplete = callbackProxy;
233
+ return () => {
234
+ lastArg.oncomplete = originalCallback;
235
+ };
236
+ });
237
+ }
238
+ }
239
+ return METHOD_EXECUTION_STANDARD({
240
+ original,
241
+ thisValue,
242
+ args,
243
+ onCatch,
244
+ onFinally,
245
+ onReturnPromise,
246
+ onReturn,
247
+ });
248
+ };
249
+
250
+ export const disableHooksWhileCalling = (fn, hookToDisableArray) => {
251
+ for (const toDisable of hookToDisableArray) {
252
+ toDisable.disable();
253
+ }
254
+ try {
255
+ return fn();
256
+ } finally {
257
+ for (const toEnable of hookToDisableArray) {
258
+ toEnable.enable();
259
+ }
260
+ }
261
+ };
@@ -1,46 +1,56 @@
1
- import { spyMethod } from "./spy_method.js";
1
+ import { hookIntoMethod } from "./hook_into_method.js";
2
2
 
3
3
  export const spyConsoleCalls = (
4
4
  { error, warn, info, log, trace },
5
5
  { preventConsoleSideEffects },
6
6
  ) => {
7
7
  const restoreCallbackSet = new Set();
8
- const errorSpy = spyMethod(console, "error", (message) => {
9
- if (preventConsoleSideEffects) {
10
- errorSpy.preventOriginalCall();
11
- }
12
- error(message);
8
+ const errorHook = hookIntoMethod(console, "error", (message) => {
9
+ return {
10
+ preventOriginalCall: preventConsoleSideEffects,
11
+ return: () => {
12
+ error(message);
13
+ },
14
+ };
13
15
  });
14
- const warnSpy = spyMethod(console, "warn", (message) => {
15
- if (preventConsoleSideEffects) {
16
- warnSpy.preventOriginalCall();
17
- }
18
- warn(message);
16
+ const warnHook = hookIntoMethod(console, "warn", (message) => {
17
+ return {
18
+ preventOriginalCall: preventConsoleSideEffects,
19
+ return: () => {
20
+ warn(message);
21
+ },
22
+ };
19
23
  });
20
- const infoSpy = spyMethod(console, "info", (message) => {
21
- if (preventConsoleSideEffects) {
22
- infoSpy.preventOriginalCall();
23
- }
24
- info(message);
24
+ const infoHook = hookIntoMethod(console, "info", (message) => {
25
+ return {
26
+ preventOriginalCall: preventConsoleSideEffects,
27
+ return: () => {
28
+ info(message);
29
+ },
30
+ };
25
31
  });
26
- const logSpy = spyMethod(console, "log", (message) => {
27
- if (preventConsoleSideEffects) {
28
- logSpy.preventOriginalCall();
29
- }
30
- log(message);
32
+ const logHook = hookIntoMethod(console, "log", (message) => {
33
+ return {
34
+ preventOriginalCall: preventConsoleSideEffects,
35
+ return: () => {
36
+ log(message);
37
+ },
38
+ };
31
39
  });
32
- const traceSpy = spyMethod(console, "trace", (message) => {
33
- if (preventConsoleSideEffects) {
34
- traceSpy.preventOriginalCall();
35
- }
36
- trace(message);
40
+ const traceHook = hookIntoMethod(console, "trace", (message) => {
41
+ return {
42
+ preventOriginalCall: preventConsoleSideEffects,
43
+ return: () => {
44
+ trace(message);
45
+ },
46
+ };
37
47
  });
38
48
  restoreCallbackSet.add(() => {
39
- errorSpy.remove();
40
- warnSpy.remove();
41
- infoSpy.remove();
42
- logSpy.remove();
43
- traceSpy.remove();
49
+ errorHook.remove();
50
+ warnHook.remove();
51
+ infoHook.remove();
52
+ logHook.remove();
53
+ traceHook.remove();
44
54
  });
45
55
  return {
46
56
  restore: () => {
@@ -6,7 +6,11 @@
6
6
  import { removeDirectorySync, removeFileSync } from "@jsenv/filesystem";
7
7
  import { readFileSync, statSync, writeFileSync } from "node:fs";
8
8
  import { pathToFileURL } from "node:url";
9
- import { disableSpiesWhileCalling, spyMethod } from "./spy_method.js";
9
+ import {
10
+ disableHooksWhileCalling,
11
+ hookIntoMethod,
12
+ METHOD_EXECUTION_NODE_CALLBACK,
13
+ } from "./hook_into_method.js";
10
14
 
11
15
  export const spyFilesystemCalls = (
12
16
  {
@@ -65,167 +69,101 @@ export const spyFilesystemCalls = (
65
69
  };
66
70
  const restoreCallbackSet = new Set();
67
71
 
68
- const getFileStateWithinSpy = (fileUrl) => {
69
- return disableSpiesWhileCalling(
72
+ const getFileStateWithinHook = (fileUrl) => {
73
+ return disableHooksWhileCalling(
70
74
  () => getFileState(fileUrl),
71
- [openSpy, closeSpy],
75
+ [openHook, closeHook],
72
76
  );
73
77
  };
74
78
 
75
- const mkdirSpy = spyMethod(
79
+ const mkdirHook = hookIntoMethod(
76
80
  _internalFs,
77
81
  "mkdir",
78
- (directoryPath, mode, recursive, callback) => {
82
+ (directoryPath) => {
79
83
  const directoryUrl = pathToFileURL(directoryPath);
80
- if (callback) {
81
- const stateBefore = getDirectoryState(directoryPath);
82
- const oncomplete = callback.oncomplete;
83
- callback.oncomplete = (error, fd) => {
84
- if (error) {
85
- oncomplete.call(callback, error);
86
- } else {
87
- fileDescriptorPathMap.set(fd, directoryPath);
88
- oncomplete.call(callback);
89
- onWriteDirectoryDone(directoryUrl, stateBefore, { found: true });
90
- }
91
- };
92
- return mkdirSpy.callOriginal();
93
- }
94
84
  const stateBefore = getDirectoryState(directoryPath);
95
- mkdirSpy.callOriginal();
96
- onWriteDirectoryDone(directoryUrl, stateBefore, { found: true });
97
- return undefined;
85
+ return {
86
+ return: (fd) => {
87
+ fileDescriptorPathMap.set(fd, directoryPath);
88
+ onWriteDirectoryDone(directoryUrl, stateBefore, { found: true });
89
+ },
90
+ };
98
91
  },
92
+ { execute: METHOD_EXECUTION_NODE_CALLBACK },
99
93
  );
100
- const openSpy = spyMethod(
94
+ const openHook = hookIntoMethod(
101
95
  _internalFs,
102
96
  "open",
103
- (filePath, flags, mode, callback) => {
104
- const stateBefore = getFileStateWithinSpy(filePath);
97
+ (filePath) => {
98
+ const stateBefore = getFileStateWithinHook(filePath);
105
99
  filesystemStateInfoMap.set(filePath, stateBefore);
106
- if (callback) {
107
- if (callback.context) {
108
- const original = callback.context.callback;
109
- callback.context.callback = function (...args) {
110
- callback.context.callback = original;
111
- const [error, fd] = args;
112
- if (error) {
113
- original.call(this, ...args);
114
- } else {
115
- if (typeof fd === "number") {
116
- fileDescriptorPathMap.set(fd, filePath);
117
- } else {
118
- // it's a buffer (happens for readFile)
119
- }
120
- original.call(this, ...args);
121
- }
122
- };
123
- openSpy.callOriginal();
124
- return;
125
- }
126
- const original = callback.oncomplete;
127
- callback.oncomplete = function (...args) {
128
- callback.oncomplete = original;
129
- const [error, fd] = args;
130
- if (error) {
131
- original.call(this, ...args);
132
- } else {
100
+ return {
101
+ return: (fd) => {
102
+ if (typeof fd === "number") {
133
103
  fileDescriptorPathMap.set(fd, filePath);
134
- original.call(this, ...args);
104
+ } else {
105
+ // it's a buffer (happens for readFile)
135
106
  }
136
- };
137
- openSpy.callOriginal();
138
- return;
139
- }
140
- const fd = openSpy.callOriginal();
141
- if (typeof fd === "number") {
142
- fileDescriptorPathMap.set(fd, filePath);
143
- } else {
144
- // it's a buffer (happens for readFile)
145
- }
107
+ },
108
+ };
146
109
  },
110
+ { execute: METHOD_EXECUTION_NODE_CALLBACK },
147
111
  );
148
- const closeSpy = spyMethod(
112
+ const closeHook = hookIntoMethod(
149
113
  _internalFs,
150
114
  "close",
151
- (fileDescriptor, callback) => {
152
- const filePath = fileDescriptorPathMap.get(fileDescriptor);
153
- if (!filePath) {
154
- closeSpy.callOriginal();
155
- return;
156
- }
157
- const stateBefore = filesystemStateInfoMap.get(filePath);
158
- if (!stateBefore) {
159
- closeSpy.callOriginal();
160
- fileDescriptorPathMap.delete(fileDescriptor);
161
- return;
162
- }
163
- const fileUrl = pathToFileURL(filePath);
164
- if (callback) {
165
- if (callback.context) {
166
- const original = callback.context.callback;
167
- callback.context.callback = function (...args) {
168
- callback.context.callback = original;
169
- const [error] = args;
170
- if (error) {
171
- original.call(this, ...args);
172
- } else {
173
- original.call(this, ...args);
174
- fileDescriptorPathMap.delete(fileDescriptor);
175
- filesystemStateInfoMap.delete(filePath);
176
- const stateAfter = getFileStateWithinSpy(fileUrl);
177
- readFile(fileUrl);
178
- onWriteFileDone(fileUrl, stateBefore, stateAfter);
179
- }
180
- };
181
- closeSpy.callOriginal();
182
- return;
183
- }
184
- const original = callback.oncomplete;
185
- callback.oncomplete = function (...args) {
186
- callback.oncomplete = original;
187
- const [error] = args;
188
- if (error) {
189
- original.call(this, ...args);
190
- } else {
191
- original.call(this, ...args);
115
+ (fileDescriptor) => {
116
+ return {
117
+ return: (buffer) => {
118
+ const filePath = fileDescriptorPathMap.get(fileDescriptor);
119
+ if (!filePath) {
120
+ return;
121
+ }
122
+ const stateBefore = filesystemStateInfoMap.get(filePath);
123
+ if (!stateBefore) {
192
124
  fileDescriptorPathMap.delete(fileDescriptor);
193
- filesystemStateInfoMap.delete(filePath);
194
- const stateAfter = getFileStateWithinSpy(fileUrl);
195
- onWriteFileDone(fileUrl, stateBefore, stateAfter);
125
+ return;
196
126
  }
197
- };
198
- closeSpy.callOriginal();
199
- return;
200
- }
201
- closeSpy.callOriginal();
202
- fileDescriptorPathMap.delete(fileDescriptor);
203
- filesystemStateInfoMap.delete(filePath);
204
- const stateAfter = getFileStateWithinSpy(fileUrl);
205
- onWriteFileDone(fileUrl, stateBefore, stateAfter);
127
+ const fileUrl = pathToFileURL(filePath);
128
+ if (buffer) {
129
+ readFile(fileUrl);
130
+ }
131
+ fileDescriptorPathMap.delete(fileDescriptor);
132
+ filesystemStateInfoMap.delete(filePath);
133
+ const stateAfter = getFileStateWithinHook(fileUrl);
134
+ onWriteFileDone(fileUrl, stateBefore, stateAfter);
135
+ },
136
+ };
206
137
  },
138
+ { execute: METHOD_EXECUTION_NODE_CALLBACK },
207
139
  );
208
- const writeFileUtf8Spy = spyMethod(
140
+ const writeFileUtf8Hook = hookIntoMethod(
209
141
  _internalFs,
210
142
  "writeFileUtf8",
211
143
  (filePath) => {
212
144
  const fileUrl = pathToFileURL(filePath);
213
- const stateBefore = getFileStateWithinSpy(fileUrl);
214
- writeFileUtf8Spy.callOriginal();
215
- const stateAfter = getFileStateWithinSpy(fileUrl);
216
- onWriteFileDone(fileUrl, stateBefore, stateAfter);
145
+ const stateBefore = getFileStateWithinHook(fileUrl);
146
+ return {
147
+ return: () => {
148
+ const stateAfter = getFileStateWithinHook(fileUrl);
149
+ onWriteFileDone(fileUrl, stateBefore, stateAfter);
150
+ },
151
+ };
217
152
  },
218
153
  );
219
- const unlinkSpy = spyMethod(_internalFs, "unlink", (filePath) => {
220
- unlinkSpy.callOriginal();
221
- removeFile(filePath); // TODO eventually split in removeFile/removeDirectory
154
+ const unlinkHook = hookIntoMethod(_internalFs, "unlink", (filePath) => {
155
+ return {
156
+ return: () => {
157
+ removeFile(filePath); // TODO eventually split in removeFile/removeDirectory
158
+ },
159
+ };
222
160
  });
223
161
  restoreCallbackSet.add(() => {
224
- mkdirSpy.remove();
225
- openSpy.remove();
226
- closeSpy.remove();
227
- writeFileUtf8Spy.remove();
228
- unlinkSpy.remove();
162
+ mkdirHook.remove();
163
+ openHook.remove();
164
+ closeHook.remove();
165
+ writeFileUtf8Hook.remove();
166
+ unlinkHook.remove();
229
167
  });
230
168
  return {
231
169
  restore: () => {
@@ -1,139 +0,0 @@
1
- const jsenvSpySymbol = Symbol.for("jsenv_spy");
2
-
3
- export const spyMethod = (object, method, spyCallback) => {
4
- const current = object[method];
5
- const jsenvSpySymbolValue = current[jsenvSpySymbol];
6
- let addCallback;
7
- let removeCallback;
8
- let callOriginal;
9
- let onOriginalCall;
10
- let preventOriginalCall;
11
- if (jsenvSpySymbolValue) {
12
- addCallback = jsenvSpySymbolValue.addCallback;
13
- removeCallback = jsenvSpySymbolValue.removeCallback;
14
- callOriginal = jsenvSpySymbolValue.callOriginal;
15
- onOriginalCall = jsenvSpySymbolValue.onOriginalCall;
16
- preventOriginalCall = jsenvSpySymbolValue.preventOriginalCall;
17
- } else {
18
- const original = current;
19
- let currentThis;
20
- let currentArgs;
21
- let originalCalled = false;
22
- let originalReturnValue;
23
- let preventOriginalCallCalled;
24
- let spyExecuting;
25
- let someSpyUsedCallOriginal;
26
- let allSpyUsedPreventOriginalCall;
27
- onOriginalCall = (returnValue) => {
28
- originalCalled = true;
29
- originalReturnValue = returnValue;
30
- };
31
- callOriginal = () => {
32
- if (someSpyUsedCallOriginal) {
33
- return originalReturnValue;
34
- }
35
- someSpyUsedCallOriginal = true;
36
- onOriginalCall(original.call(currentThis, ...currentArgs));
37
- return originalReturnValue;
38
- };
39
- preventOriginalCall = () => {
40
- preventOriginalCallCalled = true;
41
- };
42
- const spyCallbackSet = new Set();
43
- const spy = function (...args) {
44
- if (spyExecuting) {
45
- // when a spy is executing
46
- // if it calls the method himself
47
- // then we want this call to go trough
48
- // and others spy should not know about it
49
- onOriginalCall(original.call(this, ...args));
50
- return originalReturnValue;
51
- }
52
- spyExecuting = true;
53
- originalCalled = false;
54
- currentThis = this;
55
- currentArgs = args;
56
- someSpyUsedCallOriginal = false;
57
- allSpyUsedPreventOriginalCall = undefined;
58
- for (const spyCallback of spyCallbackSet) {
59
- if (spyCallback.disabled) {
60
- continue;
61
- }
62
- try {
63
- spyCallback(...args);
64
- } finally {
65
- if (preventOriginalCallCalled) {
66
- preventOriginalCallCalled = false;
67
- if (allSpyUsedPreventOriginalCall === undefined) {
68
- allSpyUsedPreventOriginalCall = true;
69
- }
70
- } else {
71
- allSpyUsedPreventOriginalCall = false;
72
- }
73
- }
74
- }
75
- spyExecuting = false;
76
- if (!someSpyUsedCallOriginal && !allSpyUsedPreventOriginalCall) {
77
- callOriginal();
78
- }
79
- currentThis = null;
80
- currentArgs = null;
81
- if (originalCalled) {
82
- originalCalled = false;
83
- const value = originalReturnValue;
84
- originalReturnValue = undefined;
85
- return value;
86
- }
87
- return undefined;
88
- };
89
- addCallback = (spyCallback) => {
90
- if (spyCallbackSet.size === 0) {
91
- object[method] = spy;
92
- }
93
- spyCallbackSet.add(spyCallback);
94
- };
95
- removeCallback = (spyCallback) => {
96
- spyCallbackSet.delete(spyCallback);
97
- if (spyCallbackSet.size === 0) {
98
- object[method] = original;
99
- }
100
- };
101
- spy[jsenvSpySymbol] = {
102
- addCallback,
103
- removeCallback,
104
- original,
105
- callOriginal,
106
- onOriginalCall,
107
- preventOriginalCall,
108
- };
109
- object[method] = spy;
110
- }
111
- addCallback(spyCallback);
112
- const spyHooks = {
113
- callOriginal,
114
- preventOriginalCall,
115
- disable: () => {
116
- spyCallback.disabled = true;
117
- },
118
- enable: () => {
119
- spyCallback.disabled = false;
120
- },
121
- remove: () => {
122
- removeCallback(spyCallback);
123
- },
124
- };
125
- return spyHooks;
126
- };
127
-
128
- export const disableSpiesWhileCalling = (fn, spyToDisableArray) => {
129
- for (const spyToDisable of spyToDisableArray) {
130
- spyToDisable.disable();
131
- }
132
- try {
133
- return fn();
134
- } finally {
135
- for (const spyToEnable of spyToDisableArray) {
136
- spyToEnable.enable();
137
- }
138
- }
139
- };