@jsenv/snapshot 2.10.2 → 2.11.0
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.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Snapshot testing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@jsenv/assert": "4.4.1",
|
|
37
|
-
"@jsenv/ast": "6.2.
|
|
37
|
+
"@jsenv/ast": "6.2.17",
|
|
38
38
|
"@jsenv/exception": "1.1.2",
|
|
39
39
|
"@jsenv/humanize": "1.2.8",
|
|
40
40
|
"@jsenv/filesystem": "4.10.2",
|
|
@@ -4,17 +4,30 @@ import { createReplaceFilesystemWellKnownValues } from "../filesystem_well_known
|
|
|
4
4
|
import { filesystemSideEffects } from "./filesystem/filesystem_side_effects.js";
|
|
5
5
|
import { logSideEffects } from "./log/log_side_effects.js";
|
|
6
6
|
|
|
7
|
+
const executionEffectsDefault = {
|
|
8
|
+
catch: true,
|
|
9
|
+
return: true,
|
|
10
|
+
};
|
|
11
|
+
|
|
7
12
|
export const createCaptureSideEffects = ({
|
|
8
13
|
sourceFileUrl,
|
|
9
14
|
logEffects = true,
|
|
10
15
|
filesystemEffects = true,
|
|
11
16
|
filesystemActions,
|
|
12
|
-
executionEffects =
|
|
17
|
+
executionEffects = executionEffectsDefault,
|
|
13
18
|
rootDirectoryUrl,
|
|
14
19
|
replaceFilesystemWellKnownValues = createReplaceFilesystemWellKnownValues({
|
|
15
20
|
rootDirectoryUrl,
|
|
16
21
|
}),
|
|
17
22
|
}) => {
|
|
23
|
+
if (executionEffects === false) {
|
|
24
|
+
executionEffects = {
|
|
25
|
+
catch: false,
|
|
26
|
+
return: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
executionEffects = { ...executionEffectsDefault, ...executionEffects };
|
|
30
|
+
|
|
18
31
|
const detectors = [];
|
|
19
32
|
if (logEffects) {
|
|
20
33
|
detectors.push(logSideEffects(logEffects === true ? {} : logEffects));
|
|
@@ -283,6 +296,33 @@ export const createCaptureSideEffects = ({
|
|
|
283
296
|
finallyCallbackSet.clear();
|
|
284
297
|
};
|
|
285
298
|
|
|
299
|
+
const onThrowOrReject = (value, isThrow) => {
|
|
300
|
+
if (executionEffects.catch === false) {
|
|
301
|
+
throw value;
|
|
302
|
+
}
|
|
303
|
+
if (typeof executionEffects.catch === "function") {
|
|
304
|
+
executionEffects.catch(value);
|
|
305
|
+
}
|
|
306
|
+
if (isThrow) {
|
|
307
|
+
onCatch(value);
|
|
308
|
+
} else {
|
|
309
|
+
onReject(value);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const onReturnOrResolve = (value, isReturn) => {
|
|
313
|
+
if (executionEffects.return === false) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (typeof executionEffects.return === "function") {
|
|
317
|
+
executionEffects.return(value);
|
|
318
|
+
}
|
|
319
|
+
if (isReturn) {
|
|
320
|
+
onReturn(value);
|
|
321
|
+
} else {
|
|
322
|
+
onResolve(value);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
286
326
|
process.env.CAPTURING_SIDE_EFFECTS = "1";
|
|
287
327
|
functionExecutingCount++;
|
|
288
328
|
let returnedPromise = false;
|
|
@@ -292,34 +332,22 @@ export const createCaptureSideEffects = ({
|
|
|
292
332
|
onReturn(RETURN_PROMISE);
|
|
293
333
|
returnedPromise = valueReturned.then(
|
|
294
334
|
(value) => {
|
|
295
|
-
|
|
335
|
+
onReturnOrResolve(value);
|
|
296
336
|
onFinally();
|
|
297
337
|
return sideEffects;
|
|
298
338
|
},
|
|
299
339
|
(e) => {
|
|
300
|
-
|
|
301
|
-
throw e;
|
|
302
|
-
}
|
|
303
|
-
if (typeof executionEffects.catch === "function") {
|
|
304
|
-
executionEffects.catch(e);
|
|
305
|
-
}
|
|
306
|
-
onReject(e);
|
|
340
|
+
onThrowOrReject(e);
|
|
307
341
|
onFinally();
|
|
308
342
|
return sideEffects;
|
|
309
343
|
},
|
|
310
344
|
);
|
|
311
345
|
return returnedPromise;
|
|
312
346
|
}
|
|
313
|
-
|
|
347
|
+
onReturnOrResolve(valueReturned, true);
|
|
314
348
|
return sideEffects;
|
|
315
349
|
} catch (e) {
|
|
316
|
-
|
|
317
|
-
throw e;
|
|
318
|
-
}
|
|
319
|
-
if (typeof executionEffects.catch === "function") {
|
|
320
|
-
executionEffects.catch(e);
|
|
321
|
-
}
|
|
322
|
-
onCatch(e);
|
|
350
|
+
onThrowOrReject(e, true);
|
|
323
351
|
return sideEffects;
|
|
324
352
|
} finally {
|
|
325
353
|
if (!returnedPromise) {
|
|
@@ -49,7 +49,26 @@ export const spyFilesystemCalls = (
|
|
|
49
49
|
const fileDescriptorPathMap = new Map();
|
|
50
50
|
const fileRestoreMap = new Map();
|
|
51
51
|
const dirRestoreMap = new Map();
|
|
52
|
-
|
|
52
|
+
|
|
53
|
+
const onWriteFileDone = (stateBefore, stateAfter) =>
|
|
54
|
+
onFileMutationDone(stateBefore, stateAfter);
|
|
55
|
+
const onCopyFileDone = (stateBefore, stateAfter) =>
|
|
56
|
+
onFileMutationDone(stateBefore, stateAfter);
|
|
57
|
+
const onMoveFileDone = (fromStateBefore, toStateBefore, toStateAfter) => {
|
|
58
|
+
if (!toStateAfter.found) {
|
|
59
|
+
// seems to be possible somehow
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// effect on source file
|
|
63
|
+
registerUndoAndNotify(fromStateBefore, () => {
|
|
64
|
+
onRemoveFile(fromStateBefore.url, fromStateBefore.buffer, "move");
|
|
65
|
+
});
|
|
66
|
+
// effect on target file
|
|
67
|
+
registerUndoAndNotify(toStateBefore, () => {
|
|
68
|
+
onWriteFile(toStateBefore.url, fromStateBefore.buffer, "created");
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const onFileMutationDone = (stateBefore, stateAfter) => {
|
|
53
72
|
if (!stateAfter.found) {
|
|
54
73
|
// seems to be possible somehow
|
|
55
74
|
return;
|
|
@@ -58,42 +77,61 @@ export const spyFilesystemCalls = (
|
|
|
58
77
|
// - writing file for the 1st time
|
|
59
78
|
// - updating file content
|
|
60
79
|
// the important part is the file content in the end of the function execution
|
|
61
|
-
const reason =
|
|
62
|
-
!stateBefore.found && stateAfter.found
|
|
63
|
-
? "created"
|
|
64
|
-
: Buffer.compare(stateBefore.buffer, stateAfter.buffer)
|
|
65
|
-
? "content_modified"
|
|
66
|
-
: stateBefore.mtimeMs === stateAfter.mtimeMs
|
|
67
|
-
? ""
|
|
68
|
-
: "mtime_modified";
|
|
80
|
+
const reason = getMutationReason(stateBefore, stateAfter);
|
|
69
81
|
if (!reason) {
|
|
70
|
-
// file is exactly the same
|
|
71
|
-
// function did not have any effect on the file
|
|
72
82
|
return;
|
|
73
83
|
}
|
|
74
|
-
|
|
84
|
+
registerUndoAndNotify(stateBefore, () => {
|
|
85
|
+
onWriteFile(stateAfter.url, stateAfter.buffer, reason);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const getMutationReason = (stateBefore, stateAfter) => {
|
|
89
|
+
if (!stateBefore.found && stateAfter.found) {
|
|
90
|
+
return "created";
|
|
91
|
+
}
|
|
92
|
+
if (Buffer.compare(stateBefore.buffer, stateAfter.buffer)) {
|
|
93
|
+
return "content_modified";
|
|
94
|
+
}
|
|
95
|
+
if (stateBefore.mtimeMs !== stateAfter.mtimeMs) {
|
|
96
|
+
return "mtime_modified";
|
|
97
|
+
}
|
|
98
|
+
// file is exactly the same
|
|
99
|
+
// function did not have any effect on the file
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
const registerUndoAndNotify = (stateBefore, notify) => {
|
|
103
|
+
const url = stateBefore.url;
|
|
104
|
+
const action = getAction(url);
|
|
75
105
|
const shouldCompare =
|
|
76
106
|
action === "compare" ||
|
|
77
107
|
action === "compare_presence_only" ||
|
|
78
108
|
action === true;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
// "ignore", false, anything else
|
|
110
|
+
undo: {
|
|
111
|
+
if (!undoFilesystemSideEffects) {
|
|
112
|
+
break undo;
|
|
113
|
+
}
|
|
114
|
+
if (action !== "undo" && !shouldCompare) {
|
|
115
|
+
break undo;
|
|
116
|
+
}
|
|
117
|
+
if (fileRestoreMap.has(url)) {
|
|
118
|
+
break undo;
|
|
119
|
+
}
|
|
120
|
+
if (stateBefore.found) {
|
|
121
|
+
fileRestoreMap.set(url, () => {
|
|
122
|
+
writeFileSync(url, stateBefore.buffer);
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
fileRestoreMap.set(url, () => {
|
|
126
|
+
removeFileSync(url, { allowUseless: true });
|
|
127
|
+
});
|
|
90
128
|
}
|
|
91
129
|
}
|
|
92
130
|
if (shouldCompare) {
|
|
93
|
-
|
|
131
|
+
notify();
|
|
94
132
|
}
|
|
95
|
-
// "ignore", false, anything else
|
|
96
133
|
};
|
|
134
|
+
|
|
97
135
|
const onWriteDirectoryDone = (directoryUrl) => {
|
|
98
136
|
const action = getAction(directoryUrl);
|
|
99
137
|
const shouldCompare =
|
|
@@ -121,9 +159,9 @@ export const spyFilesystemCalls = (
|
|
|
121
159
|
};
|
|
122
160
|
const restoreCallbackSet = new Set();
|
|
123
161
|
|
|
124
|
-
const getFileStateWithinHook = (
|
|
162
|
+
const getFileStateWithinHook = (filePath) => {
|
|
125
163
|
return disableHooksWhileCalling(
|
|
126
|
-
() => getFileState(
|
|
164
|
+
() => getFileState(filePath),
|
|
127
165
|
[openHook, closeHook],
|
|
128
166
|
);
|
|
129
167
|
};
|
|
@@ -208,14 +246,13 @@ export const spyFilesystemCalls = (
|
|
|
208
246
|
fileDescriptorPathMap.delete(fileDescriptor);
|
|
209
247
|
return;
|
|
210
248
|
}
|
|
211
|
-
const fileUrl = pathToFileURL(filePath);
|
|
212
249
|
if (buffer) {
|
|
213
|
-
onReadFile(
|
|
250
|
+
onReadFile(filePath);
|
|
214
251
|
}
|
|
215
252
|
fileDescriptorPathMap.delete(fileDescriptor);
|
|
216
253
|
filesystemStateInfoMap.delete(filePath);
|
|
217
|
-
const stateAfter = getFileStateWithinHook(
|
|
218
|
-
onWriteFileDone(
|
|
254
|
+
const stateAfter = getFileStateWithinHook(filePath);
|
|
255
|
+
onWriteFileDone(stateBefore, stateAfter);
|
|
219
256
|
},
|
|
220
257
|
};
|
|
221
258
|
},
|
|
@@ -225,12 +262,11 @@ export const spyFilesystemCalls = (
|
|
|
225
262
|
_internalFs,
|
|
226
263
|
"writeFileUtf8",
|
|
227
264
|
(filePath) => {
|
|
228
|
-
const
|
|
229
|
-
const stateBefore = getFileStateWithinHook(fileUrl);
|
|
265
|
+
const stateBefore = getFileStateWithinHook(filePath);
|
|
230
266
|
return {
|
|
231
267
|
return: () => {
|
|
232
|
-
const stateAfter = getFileStateWithinHook(
|
|
233
|
-
onWriteFileDone(
|
|
268
|
+
const stateAfter = getFileStateWithinHook(filePath);
|
|
269
|
+
onWriteFileDone(stateBefore, stateAfter);
|
|
234
270
|
},
|
|
235
271
|
};
|
|
236
272
|
},
|
|
@@ -242,12 +278,45 @@ export const spyFilesystemCalls = (
|
|
|
242
278
|
},
|
|
243
279
|
};
|
|
244
280
|
});
|
|
281
|
+
const copyFileHook = hookIntoMethod(
|
|
282
|
+
_internalFs,
|
|
283
|
+
"copyFile",
|
|
284
|
+
(fromPath, toPath) => {
|
|
285
|
+
const toStateBefore = getFileStateWithinHook(toPath);
|
|
286
|
+
return {
|
|
287
|
+
return: () => {
|
|
288
|
+
const toStateAfter = getFileStateWithinHook(toPath);
|
|
289
|
+
onCopyFileDone(toStateBefore, toStateAfter);
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
{ execute: METHOD_EXECUTION_NODE_CALLBACK },
|
|
294
|
+
);
|
|
295
|
+
const renameHook = hookIntoMethod(
|
|
296
|
+
_internalFs,
|
|
297
|
+
"rename",
|
|
298
|
+
(fromPath, toPath) => {
|
|
299
|
+
const fromStateBefore = getFileStateWithinHook(fromPath);
|
|
300
|
+
const toStateBefore = getFileStateWithinHook(toPath);
|
|
301
|
+
return {
|
|
302
|
+
return: () => {
|
|
303
|
+
const toStateAfter = getFileStateWithinHook(toPath);
|
|
304
|
+
onMoveFileDone(fromStateBefore, toStateBefore, toStateAfter);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
execute: METHOD_EXECUTION_NODE_CALLBACK,
|
|
310
|
+
},
|
|
311
|
+
);
|
|
245
312
|
restoreCallbackSet.add(() => {
|
|
246
313
|
mkdirHook.remove();
|
|
247
314
|
openHook.remove();
|
|
248
315
|
closeHook.remove();
|
|
249
316
|
writeFileUtf8Hook.remove();
|
|
250
317
|
unlinkHook.remove();
|
|
318
|
+
copyFileHook.remove();
|
|
319
|
+
renameHook.remove();
|
|
251
320
|
});
|
|
252
321
|
return {
|
|
253
322
|
restore: () => {
|
|
@@ -275,11 +344,13 @@ export const spyFilesystemCalls = (
|
|
|
275
344
|
};
|
|
276
345
|
};
|
|
277
346
|
|
|
278
|
-
const getFileState = (
|
|
347
|
+
const getFileState = (filePath) => {
|
|
348
|
+
const fileUrl = pathToFileURL(filePath);
|
|
279
349
|
try {
|
|
280
|
-
const fileBuffer = readFileSync(
|
|
281
|
-
const { mtimeMs } = statSync(
|
|
350
|
+
const fileBuffer = readFileSync(fileUrl);
|
|
351
|
+
const { mtimeMs } = statSync(fileUrl);
|
|
282
352
|
return {
|
|
353
|
+
url: String(fileUrl),
|
|
283
354
|
found: true,
|
|
284
355
|
mtimeMs,
|
|
285
356
|
buffer: fileBuffer,
|
|
@@ -287,6 +358,7 @@ const getFileState = (file) => {
|
|
|
287
358
|
} catch (e) {
|
|
288
359
|
if (e.code === "ENOENT") {
|
|
289
360
|
return {
|
|
361
|
+
url: String(fileUrl),
|
|
290
362
|
found: false,
|
|
291
363
|
};
|
|
292
364
|
}
|
|
@@ -12,6 +12,11 @@ import { renderSideEffects, renderSmallLink } from "./render_side_effects.js";
|
|
|
12
12
|
* @param {Object} snapshotTestsOptions
|
|
13
13
|
* @param {string|url} snapshotTestsOptions.outFilePattern
|
|
14
14
|
* @param {string|url} snapshotTestsOptions.rootDirectoryUrl
|
|
15
|
+
* @param {Object} [snapshotTestsOptions.executionEffects]
|
|
16
|
+
* @param {boolean} [snapshotTestsOptions.executionEffects.catch=true]
|
|
17
|
+
* Any error thrown by test function is detected and added to side effects
|
|
18
|
+
* @param {boolean} [snapshotTestsOptions.executionEffects.return=true]
|
|
19
|
+
* Test function return value is added to side effects
|
|
15
20
|
* @param {Object} [snapshotTestsOptions.filesystemActions]
|
|
16
21
|
* Control what to do when there is a file side effect
|
|
17
22
|
* "compare", "compare_presence_only", "undo", "ignore"
|