@jsenv/snapshot 2.10.1 → 2.10.3

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.10.1",
3
+ "version": "2.10.3",
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.16",
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",
@@ -379,24 +379,22 @@ ${extraUrls.join("\n")}`);
379
379
  const relativeUrl = urlToRelativeUrl(directoryItemUrl, directoryUrl);
380
380
  if (directoryItemStat.isDirectory()) {
381
381
  ensurePathnameTrailingSlash(directoryItemUrl);
382
- if (!shouldVisitDirectory(directoryItemUrl)) {
382
+ if (!shouldVisitDirectory(directoryItemUrl.href)) {
383
383
  continue;
384
384
  }
385
- contentSnapshotNaturalOrder[relativeUrl] = createDirectorySnapshot(
386
- directoryItemUrl,
387
- {
388
- shouldVisitDirectory,
389
- shouldIncludeFile,
390
- shouldCompareFileContent,
391
- clean,
392
- },
393
- );
394
- if (clean) {
385
+ const subdirSnapshot = createDirectorySnapshot(directoryItemUrl, {
386
+ shouldVisitDirectory,
387
+ shouldIncludeFile,
388
+ shouldCompareFileContent,
389
+ clean,
390
+ });
391
+ contentSnapshotNaturalOrder[relativeUrl] = subdirSnapshot;
392
+ if (clean && subdirSnapshot) {
395
393
  removeDirectorySync(directoryItemUrl);
396
394
  }
397
395
  continue;
398
396
  }
399
- if (!shouldIncludeFile(directoryItemUrl)) {
397
+ if (!shouldIncludeFile(directoryItemUrl.href)) {
400
398
  continue;
401
399
  }
402
400
  contentSnapshotNaturalOrder[relativeUrl] =
@@ -15,6 +15,13 @@ export const createCaptureSideEffects = ({
15
15
  rootDirectoryUrl,
16
16
  }),
17
17
  }) => {
18
+ if (executionEffects === false) {
19
+ executionEffects = {
20
+ catch: false,
21
+ return: false,
22
+ };
23
+ }
24
+
18
25
  const detectors = [];
19
26
  if (logEffects) {
20
27
  detectors.push(logSideEffects(logEffects === true ? {} : logEffects));
@@ -283,6 +290,33 @@ export const createCaptureSideEffects = ({
283
290
  finallyCallbackSet.clear();
284
291
  };
285
292
 
293
+ const onThrowOrReject = (value, isThrow) => {
294
+ if (executionEffects.catch === false) {
295
+ throw value;
296
+ }
297
+ if (typeof executionEffects.catch === "function") {
298
+ executionEffects.catch(value);
299
+ }
300
+ if (isThrow) {
301
+ onCatch(value);
302
+ } else {
303
+ onReject(value);
304
+ }
305
+ };
306
+ const onReturnOrResolve = (value, isReturn) => {
307
+ if (executionEffects.return === false) {
308
+ return;
309
+ }
310
+ if (typeof executionEffects.return === "function") {
311
+ executionEffects.return(value);
312
+ }
313
+ if (isReturn) {
314
+ onReturn(value);
315
+ } else {
316
+ onResolve(value);
317
+ }
318
+ };
319
+
286
320
  process.env.CAPTURING_SIDE_EFFECTS = "1";
287
321
  functionExecutingCount++;
288
322
  let returnedPromise = false;
@@ -292,34 +326,22 @@ export const createCaptureSideEffects = ({
292
326
  onReturn(RETURN_PROMISE);
293
327
  returnedPromise = valueReturned.then(
294
328
  (value) => {
295
- onResolve(value);
329
+ onReturnOrResolve(value);
296
330
  onFinally();
297
331
  return sideEffects;
298
332
  },
299
333
  (e) => {
300
- if (executionEffects.catch === false) {
301
- throw e;
302
- }
303
- if (typeof executionEffects.catch === "function") {
304
- executionEffects.catch(e);
305
- }
306
- onReject(e);
334
+ onThrowOrReject(e);
307
335
  onFinally();
308
336
  return sideEffects;
309
337
  },
310
338
  );
311
339
  return returnedPromise;
312
340
  }
313
- onReturn(valueReturned);
341
+ onReturnOrResolve(valueReturned, true);
314
342
  return sideEffects;
315
343
  } catch (e) {
316
- if (executionEffects.catch === false) {
317
- throw e;
318
- }
319
- if (typeof executionEffects.catch === "function") {
320
- executionEffects.catch(e);
321
- }
322
- onCatch(e);
344
+ onThrowOrReject(e, true);
323
345
  return sideEffects;
324
346
  } finally {
325
347
  if (!returnedPromise) {
@@ -4,13 +4,14 @@
4
4
  // https://github.com/tschaub/mock-fs/issues/348
5
5
 
6
6
  import {
7
+ comparePathnames,
7
8
  removeDirectorySync,
8
9
  removeFileSync,
9
10
  writeFileSync,
10
11
  } from "@jsenv/filesystem";
11
12
  import { URL_META } from "@jsenv/url-meta";
12
13
  import { ensurePathnameTrailingSlash, yieldAncestorUrls } from "@jsenv/urls";
13
- import { readFileSync, statSync } from "node:fs";
14
+ import { readdirSync, readFileSync, statSync } from "node:fs";
14
15
  import { pathToFileURL } from "node:url";
15
16
  import {
16
17
  disableHooksWhileCalling,
@@ -47,7 +48,8 @@ export const spyFilesystemCalls = (
47
48
  const filesystemStateInfoMap = new Map();
48
49
  const fileDescriptorPathMap = new Map();
49
50
  const fileRestoreMap = new Map();
50
- const onWriteFileDone = (fileUrl, stateBefore, stateAfter) => {
51
+ const dirRestoreMap = new Map();
52
+ const onFileMutationDone = (stateBefore, stateAfter) => {
51
53
  if (!stateAfter.found) {
52
54
  // seems to be possible somehow
53
55
  return;
@@ -56,39 +58,42 @@ export const spyFilesystemCalls = (
56
58
  // - writing file for the 1st time
57
59
  // - updating file content
58
60
  // the important part is the file content in the end of the function execution
59
- const reason =
60
- !stateBefore.found && stateAfter.found
61
- ? "created"
62
- : Buffer.compare(stateBefore.buffer, stateAfter.buffer)
63
- ? "content_modified"
64
- : stateBefore.mtimeMs === stateAfter.mtimeMs
65
- ? ""
66
- : "mtime_modified";
67
- if (!reason) {
61
+ let reason;
62
+ if (!stateBefore.found && stateAfter.found) {
63
+ reason = "created";
64
+ } else if (Buffer.compare(stateBefore.buffer, stateAfter.buffer)) {
65
+ reason = "content_modified";
66
+ } else if (stateBefore.mtimeMs !== stateAfter.mtimeMs) {
67
+ reason = "mtime_modified";
68
+ } else if (stateBefore.url !== stateAfter.url) {
69
+ reason = "moved";
70
+ } else {
68
71
  // file is exactly the same
69
72
  // function did not have any effect on the file
70
73
  return;
71
74
  }
72
- const action = getAction(fileUrl);
75
+ const beforeUrl = stateBefore.url;
76
+ const afterUrl = stateAfter.url;
77
+ const action = getAction(beforeUrl);
73
78
  const shouldCompare =
74
79
  action === "compare" ||
75
80
  action === "compare_presence_only" ||
76
81
  action === true;
77
82
  if (action === "undo" || shouldCompare) {
78
- if (undoFilesystemSideEffects && !fileRestoreMap.has(fileUrl)) {
83
+ if (undoFilesystemSideEffects && !fileRestoreMap.has(beforeUrl)) {
79
84
  if (stateBefore.found) {
80
- fileRestoreMap.set(fileUrl, () => {
81
- writeFileSync(fileUrl, stateBefore.buffer);
85
+ fileRestoreMap.set(beforeUrl, () => {
86
+ writeFileSync(beforeUrl, stateBefore.buffer);
82
87
  });
83
88
  } else {
84
- fileRestoreMap.set(fileUrl, () => {
85
- removeFileSync(fileUrl, { allowUseless: true });
89
+ fileRestoreMap.set(beforeUrl, () => {
90
+ removeFileSync(beforeUrl, { allowUseless: true });
86
91
  });
87
92
  }
88
93
  }
89
94
  }
90
95
  if (shouldCompare) {
91
- onWriteFile(fileUrl, stateAfter.buffer, reason);
96
+ onWriteFile(afterUrl, stateAfter.buffer, reason);
92
97
  }
93
98
  // "ignore", false, anything else
94
99
  };
@@ -99,12 +104,16 @@ export const spyFilesystemCalls = (
99
104
  action === "compare_presence_only" ||
100
105
  action === true;
101
106
  if (action === "undo" || shouldCompare) {
102
- if (undoFilesystemSideEffects && !fileRestoreMap.has(directoryUrl)) {
103
- fileRestoreMap.set(directoryUrl, () => {
104
- removeDirectorySync(directoryUrl, {
105
- allowUseless: true,
106
- recursive: true,
107
- });
107
+ if (undoFilesystemSideEffects && !dirRestoreMap.has(directoryUrl)) {
108
+ dirRestoreMap.set(directoryUrl, () => {
109
+ try {
110
+ const isEmpty = readdirSync(new URL(directoryUrl)).length === 0;
111
+ if (isEmpty) {
112
+ removeDirectorySync(directoryUrl, {
113
+ allowUseless: true,
114
+ });
115
+ }
116
+ } catch {}
108
117
  });
109
118
  }
110
119
  }
@@ -115,9 +124,9 @@ export const spyFilesystemCalls = (
115
124
  };
116
125
  const restoreCallbackSet = new Set();
117
126
 
118
- const getFileStateWithinHook = (fileUrl) => {
127
+ const getFileStateWithinHook = (filePath) => {
119
128
  return disableHooksWhileCalling(
120
- () => getFileState(fileUrl),
129
+ () => getFileState(filePath),
121
130
  [openHook, closeHook],
122
131
  );
123
132
  };
@@ -202,14 +211,13 @@ export const spyFilesystemCalls = (
202
211
  fileDescriptorPathMap.delete(fileDescriptor);
203
212
  return;
204
213
  }
205
- const fileUrl = pathToFileURL(filePath);
206
214
  if (buffer) {
207
- onReadFile(String(fileUrl));
215
+ onReadFile(filePath);
208
216
  }
209
217
  fileDescriptorPathMap.delete(fileDescriptor);
210
218
  filesystemStateInfoMap.delete(filePath);
211
- const stateAfter = getFileStateWithinHook(fileUrl);
212
- onWriteFileDone(String(fileUrl), stateBefore, stateAfter);
219
+ const stateAfter = getFileStateWithinHook(filePath);
220
+ onFileMutationDone(stateBefore, stateAfter);
213
221
  },
214
222
  };
215
223
  },
@@ -219,12 +227,11 @@ export const spyFilesystemCalls = (
219
227
  _internalFs,
220
228
  "writeFileUtf8",
221
229
  (filePath) => {
222
- const fileUrl = pathToFileURL(filePath);
223
- const stateBefore = getFileStateWithinHook(fileUrl);
230
+ const stateBefore = getFileStateWithinHook(filePath);
224
231
  return {
225
232
  return: () => {
226
- const stateAfter = getFileStateWithinHook(fileUrl);
227
- onWriteFileDone(String(fileUrl), stateBefore, stateAfter);
233
+ const stateAfter = getFileStateWithinHook(filePath);
234
+ onFileMutationDone(stateBefore, stateAfter);
228
235
  },
229
236
  };
230
237
  },
@@ -236,12 +243,44 @@ export const spyFilesystemCalls = (
236
243
  },
237
244
  };
238
245
  });
246
+ const copyFileHook = hookIntoMethod(
247
+ _internalFs,
248
+ "copyFile",
249
+ (fromPath, toPath) => {
250
+ const stateBefore = getFileStateWithinHook(fromPath);
251
+ return {
252
+ return: () => {
253
+ const stateAfter = getFileStateWithinHook(toPath);
254
+ onFileMutationDone(stateBefore, stateAfter);
255
+ },
256
+ };
257
+ },
258
+ { execute: METHOD_EXECUTION_NODE_CALLBACK },
259
+ );
260
+ const renameHook = hookIntoMethod(
261
+ _internalFs,
262
+ "rename",
263
+ (fromPath, toPath) => {
264
+ const stateBefore = getFileStateWithinHook(fromPath);
265
+ return {
266
+ return: () => {
267
+ const stateAfter = getFileStateWithinHook(toPath);
268
+ onFileMutationDone(stateBefore, stateAfter);
269
+ },
270
+ };
271
+ },
272
+ {
273
+ execute: METHOD_EXECUTION_NODE_CALLBACK,
274
+ },
275
+ );
239
276
  restoreCallbackSet.add(() => {
240
277
  mkdirHook.remove();
241
278
  openHook.remove();
242
279
  closeHook.remove();
243
280
  writeFileUtf8Hook.remove();
244
281
  unlinkHook.remove();
282
+ copyFileHook.remove();
283
+ renameHook.remove();
245
284
  });
246
285
  return {
247
286
  restore: () => {
@@ -253,15 +292,29 @@ export const spyFilesystemCalls = (
253
292
  restore();
254
293
  }
255
294
  fileRestoreMap.clear();
295
+ const dirUrls = Array.from(dirRestoreMap.keys());
296
+ dirUrls.sort((left, right) => {
297
+ return comparePathnames(
298
+ new URL(left).pathname,
299
+ new URL(right).pathname,
300
+ );
301
+ });
302
+ for (const dirUrl of dirUrls) {
303
+ const restore = dirRestoreMap.get(dirUrl);
304
+ restore();
305
+ }
306
+ dirRestoreMap.clear();
256
307
  },
257
308
  };
258
309
  };
259
310
 
260
- const getFileState = (file) => {
311
+ const getFileState = (filePath) => {
312
+ const fileUrl = pathToFileURL(filePath);
261
313
  try {
262
- const fileBuffer = readFileSync(file);
263
- const { mtimeMs } = statSync(file);
314
+ const fileBuffer = readFileSync(fileUrl);
315
+ const { mtimeMs } = statSync(fileUrl);
264
316
  return {
317
+ url: String(fileUrl),
265
318
  found: true,
266
319
  mtimeMs,
267
320
  buffer: fileBuffer,
@@ -269,6 +322,7 @@ const getFileState = (file) => {
269
322
  } catch (e) {
270
323
  if (e.code === "ENOENT") {
271
324
  return {
325
+ url: String(fileUrl),
272
326
  found: false,
273
327
  };
274
328
  }
@@ -31,10 +31,7 @@ export const snapshotTests = async (
31
31
  fnRegisteringTest,
32
32
  {
33
33
  outFilePattern = "./_[source_filename]/[filename]",
34
- filesystemActions = {
35
- "**": "compare",
36
- // "**/*.svg": "compare_presence_only",
37
- },
34
+ filesystemActions,
38
35
  rootDirectoryUrl,
39
36
  generatedBy = true,
40
37
  executionEffects,
@@ -44,6 +41,7 @@ export const snapshotTests = async (
44
41
  } = {},
45
42
  ) => {
46
43
  filesystemActions = {
44
+ "**": "compare",
47
45
  ...filesystemActions,
48
46
  "**/*.svg": "compare_presence_only",
49
47
  };