@jsenv/snapshot 2.8.3 → 2.8.5
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 +4 -4
- package/src/filesystem_snapshot.js +12 -8
- package/src/side_effects/create_capture_side_effects.js +5 -1
- package/src/side_effects/filesystem/filesystem_side_effects.js +18 -16
- package/src/side_effects/filesystem/spy_filesystem_calls.js +65 -29
- package/src/side_effects/log/log_side_effects.js +1 -1
- package/src/side_effects/render_side_effects.js +22 -18
- package/src/side_effects/snapshot_side_effects.js +52 -50
- package/src/side_effects/snapshot_tests.js +73 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/snapshot",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.5",
|
|
4
4
|
"description": "Snapshot testing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@jsenv/assert": "4.1.15",
|
|
37
37
|
"@jsenv/ast": "6.2.14",
|
|
38
|
-
"@jsenv/exception": "1.0.
|
|
39
|
-
"@jsenv/filesystem": "4.9.
|
|
40
|
-
"@jsenv/terminal-recorder": "1.4.
|
|
38
|
+
"@jsenv/exception": "1.0.2",
|
|
39
|
+
"@jsenv/filesystem": "4.9.10",
|
|
40
|
+
"@jsenv/terminal-recorder": "1.4.4",
|
|
41
41
|
"@jsenv/urls": "2.5.2",
|
|
42
42
|
"@jsenv/utils": "2.1.2",
|
|
43
43
|
"ansi-regex": "6.0.1",
|
|
@@ -180,7 +180,7 @@ export const takeDirectorySnapshot = (
|
|
|
180
180
|
return URL_META.urlChildMayMatch({
|
|
181
181
|
url,
|
|
182
182
|
associations,
|
|
183
|
-
predicate: (meta) =>
|
|
183
|
+
predicate: (meta) => meta.action && meta.action !== "ignore",
|
|
184
184
|
});
|
|
185
185
|
};
|
|
186
186
|
const shouldIncludeFile = (url) => {
|
|
@@ -188,9 +188,13 @@ export const takeDirectorySnapshot = (
|
|
|
188
188
|
url,
|
|
189
189
|
associations,
|
|
190
190
|
});
|
|
191
|
-
return
|
|
191
|
+
return (
|
|
192
|
+
meta.action === true ||
|
|
193
|
+
meta.action === "compare" ||
|
|
194
|
+
meta.action === "compare_presence_only"
|
|
195
|
+
);
|
|
192
196
|
};
|
|
193
|
-
const
|
|
197
|
+
const shouldCompareFileContent = (url) => {
|
|
194
198
|
const meta = URL_META.applyAssociations({
|
|
195
199
|
url,
|
|
196
200
|
associations,
|
|
@@ -200,7 +204,7 @@ export const takeDirectorySnapshot = (
|
|
|
200
204
|
const directorySnapshot = createDirectorySnapshot(directoryUrl, {
|
|
201
205
|
shouldVisitDirectory,
|
|
202
206
|
shouldIncludeFile,
|
|
203
|
-
|
|
207
|
+
shouldCompareFileContent,
|
|
204
208
|
clean: true,
|
|
205
209
|
});
|
|
206
210
|
return {
|
|
@@ -209,7 +213,7 @@ export const takeDirectorySnapshot = (
|
|
|
209
213
|
const nextDirectorySnapshot = createDirectorySnapshot(directoryUrl, {
|
|
210
214
|
shouldVisitDirectory,
|
|
211
215
|
shouldIncludeFile,
|
|
212
|
-
|
|
216
|
+
shouldCompareFileContent,
|
|
213
217
|
});
|
|
214
218
|
directorySnapshot.compare(nextDirectorySnapshot, { throwWhenDiff });
|
|
215
219
|
},
|
|
@@ -237,7 +241,7 @@ export const takeDirectorySnapshot = (
|
|
|
237
241
|
};
|
|
238
242
|
const createDirectorySnapshot = (
|
|
239
243
|
directoryUrl,
|
|
240
|
-
{ shouldVisitDirectory, shouldIncludeFile,
|
|
244
|
+
{ shouldVisitDirectory, shouldIncludeFile, shouldCompareFileContent, clean },
|
|
241
245
|
) => {
|
|
242
246
|
const directorySnapshot = {
|
|
243
247
|
type: "directory",
|
|
@@ -322,7 +326,7 @@ ${extraUrls.join("\n")}`);
|
|
|
322
326
|
// content
|
|
323
327
|
{
|
|
324
328
|
for (const relativeUrl of relativeUrls) {
|
|
325
|
-
if (!
|
|
329
|
+
if (!shouldCompareFileContent(new URL(relativeUrl, directoryUrl))) {
|
|
326
330
|
continue;
|
|
327
331
|
}
|
|
328
332
|
const snapshot = directoryContentSnapshot[relativeUrl];
|
|
@@ -384,7 +388,7 @@ ${extraUrls.join("\n")}`);
|
|
|
384
388
|
{
|
|
385
389
|
shouldVisitDirectory,
|
|
386
390
|
shouldIncludeFile,
|
|
387
|
-
|
|
391
|
+
shouldCompareFileContent,
|
|
388
392
|
clean,
|
|
389
393
|
},
|
|
390
394
|
);
|
|
@@ -4,13 +4,15 @@ import { filesystemSideEffects } from "./filesystem/filesystem_side_effects.js";
|
|
|
4
4
|
import { logSideEffects } from "./log/log_side_effects.js";
|
|
5
5
|
|
|
6
6
|
export const createCaptureSideEffects = ({
|
|
7
|
+
sourceFileUrl,
|
|
7
8
|
logEffects = true,
|
|
8
9
|
filesystemEffects = true,
|
|
10
|
+
filesystemActions,
|
|
9
11
|
rootDirectoryUrl,
|
|
10
12
|
replaceFilesystemWellKnownValues = createReplaceFilesystemWellKnownValues({
|
|
11
13
|
rootDirectoryUrl,
|
|
12
14
|
}),
|
|
13
|
-
}
|
|
15
|
+
}) => {
|
|
14
16
|
const detectors = [];
|
|
15
17
|
if (logEffects) {
|
|
16
18
|
detectors.push(logSideEffects(logEffects === true ? {} : logEffects));
|
|
@@ -20,7 +22,9 @@ export const createCaptureSideEffects = ({
|
|
|
20
22
|
filesystemSideEffectsDetector = filesystemSideEffects(
|
|
21
23
|
filesystemEffects === true ? {} : filesystemEffects,
|
|
22
24
|
{
|
|
25
|
+
sourceFileUrl,
|
|
23
26
|
replaceFilesystemWellKnownValues,
|
|
27
|
+
filesystemActions,
|
|
24
28
|
},
|
|
25
29
|
);
|
|
26
30
|
detectors.push(filesystemSideEffectsDetector);
|
|
@@ -7,17 +7,16 @@ import { groupFileSideEffectsPerDirectory } from "./group_file_side_effects_per_
|
|
|
7
7
|
import { spyFilesystemCalls } from "./spy_filesystem_calls.js";
|
|
8
8
|
|
|
9
9
|
const filesystemSideEffectsOptionsDefault = {
|
|
10
|
-
include: null,
|
|
11
10
|
preserve: false,
|
|
12
11
|
baseDirectory: "",
|
|
13
|
-
|
|
12
|
+
textualFilesInline: false,
|
|
14
13
|
};
|
|
15
14
|
const INLINE_MAX_LINES = 20;
|
|
16
15
|
const INLINE_MAX_LENGTH = 2000;
|
|
17
16
|
|
|
18
17
|
export const filesystemSideEffects = (
|
|
19
18
|
filesystemSideEffectsOptions,
|
|
20
|
-
{ replaceFilesystemWellKnownValues },
|
|
19
|
+
{ sourceFileUrl, filesystemActions, replaceFilesystemWellKnownValues },
|
|
21
20
|
) => {
|
|
22
21
|
filesystemSideEffectsOptions = {
|
|
23
22
|
...filesystemSideEffectsOptionsDefault,
|
|
@@ -42,10 +41,11 @@ export const filesystemSideEffects = (
|
|
|
42
41
|
name: "filesystem",
|
|
43
42
|
setBaseDirectory,
|
|
44
43
|
install: (addSideEffect, { addSkippableHandler, addFinallyCallback }) => {
|
|
45
|
-
let {
|
|
46
|
-
filesystemSideEffectsOptions;
|
|
44
|
+
let { preserve, textualFilesInline } = filesystemSideEffectsOptions;
|
|
47
45
|
if (filesystemSideEffectsOptions.baseDirectory) {
|
|
48
46
|
setBaseDirectory(filesystemSideEffectsOptions.baseDirectory);
|
|
47
|
+
} else if (sourceFileUrl) {
|
|
48
|
+
setBaseDirectory(new URL("./", sourceFileUrl));
|
|
49
49
|
}
|
|
50
50
|
const getUrlRelativeToBase = (url) => {
|
|
51
51
|
if (baseDirectory) {
|
|
@@ -155,7 +155,7 @@ export const filesystemSideEffects = (
|
|
|
155
155
|
if (outDirectoryReason) {
|
|
156
156
|
const outUrlRelativeToCommonDirectory = urlToRelativeUrl(
|
|
157
157
|
text.urlInsideOutDirectory,
|
|
158
|
-
options.
|
|
158
|
+
options.sideEffectMdFileUrl,
|
|
159
159
|
);
|
|
160
160
|
groupMd += `${"#".repeat(2)} ${urlRelativeToCommonDirectory}
|
|
161
161
|
${renderFileContent(
|
|
@@ -193,7 +193,7 @@ ${renderFileContent(
|
|
|
193
193
|
);
|
|
194
194
|
const commonDirectoryOutRelativeUrl = urlToRelativeUrl(
|
|
195
195
|
commonDirectoryOutUrl,
|
|
196
|
-
options.
|
|
196
|
+
options.sideEffectMdFileUrl,
|
|
197
197
|
{ preferRelativeNotation: true },
|
|
198
198
|
);
|
|
199
199
|
return {
|
|
@@ -219,12 +219,14 @@ ${renderFileContent(
|
|
|
219
219
|
const isTextual = CONTENT_TYPE.isTextual(contentType);
|
|
220
220
|
let outDirectoryReason;
|
|
221
221
|
if (isTextual) {
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
222
|
+
if (textualFilesInline) {
|
|
223
|
+
if (String(buffer).split("\n").length > INLINE_MAX_LINES) {
|
|
224
|
+
outDirectoryReason = "lot_of_lines";
|
|
225
|
+
} else if (buffer.size > INLINE_MAX_LENGTH) {
|
|
226
|
+
outDirectoryReason = "lot_of_chars";
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
outDirectoryReason = "text";
|
|
228
230
|
}
|
|
229
231
|
} else {
|
|
230
232
|
outDirectoryReason = "binary";
|
|
@@ -240,7 +242,7 @@ ${renderFileContent(
|
|
|
240
242
|
outDirectoryReason,
|
|
241
243
|
},
|
|
242
244
|
render: {
|
|
243
|
-
md: ({
|
|
245
|
+
md: ({ sideEffectMdFileUrl, generateOutFileUrl }) => {
|
|
244
246
|
const urlRelativeToBase = getUrlRelativeToBase(url);
|
|
245
247
|
if (outDirectoryReason) {
|
|
246
248
|
let urlInsideOutDirectory = getUrlInsideOutDirectory(
|
|
@@ -267,7 +269,7 @@ ${renderFileContent(
|
|
|
267
269
|
}
|
|
268
270
|
const outRelativeUrl = urlToRelativeUrl(
|
|
269
271
|
urlInsideOutDirectory,
|
|
270
|
-
|
|
272
|
+
sideEffectMdFileUrl,
|
|
271
273
|
{
|
|
272
274
|
preferRelativeNotation: true,
|
|
273
275
|
},
|
|
@@ -311,7 +313,7 @@ ${renderFileContent(
|
|
|
311
313
|
},
|
|
312
314
|
},
|
|
313
315
|
{
|
|
314
|
-
include,
|
|
316
|
+
include: filesystemActions,
|
|
315
317
|
undoFilesystemSideEffects: !preserve,
|
|
316
318
|
},
|
|
317
319
|
);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
writeFileSync,
|
|
10
10
|
} from "@jsenv/filesystem";
|
|
11
11
|
import { URL_META } from "@jsenv/url-meta";
|
|
12
|
-
import { yieldAncestorUrls } from "@jsenv/urls";
|
|
12
|
+
import { ensurePathnameTrailingSlash, yieldAncestorUrls } from "@jsenv/urls";
|
|
13
13
|
import { readFileSync, statSync } from "node:fs";
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
15
|
import {
|
|
@@ -28,9 +28,20 @@ export const spyFilesystemCalls = (
|
|
|
28
28
|
},
|
|
29
29
|
{ include, undoFilesystemSideEffects } = {},
|
|
30
30
|
) => {
|
|
31
|
-
const
|
|
32
|
-
?
|
|
33
|
-
|
|
31
|
+
const getAction = include
|
|
32
|
+
? (() => {
|
|
33
|
+
const associations = URL_META.resolveAssociations(
|
|
34
|
+
{
|
|
35
|
+
action: include,
|
|
36
|
+
},
|
|
37
|
+
"file:///",
|
|
38
|
+
);
|
|
39
|
+
return (url) => {
|
|
40
|
+
const meta = URL_META.applyAssociations({ url, associations });
|
|
41
|
+
return meta.action;
|
|
42
|
+
};
|
|
43
|
+
})()
|
|
44
|
+
: () => "compare";
|
|
34
45
|
|
|
35
46
|
const _internalFs = process.binding("fs");
|
|
36
47
|
const filesystemStateInfoMap = new Map();
|
|
@@ -58,35 +69,49 @@ export const spyFilesystemCalls = (
|
|
|
58
69
|
// function did not have any effect on the file
|
|
59
70
|
return;
|
|
60
71
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
72
|
+
const action = getAction(fileUrl);
|
|
73
|
+
const shouldCompare =
|
|
74
|
+
action === "compare" ||
|
|
75
|
+
action === "compare_presence_only" ||
|
|
76
|
+
action === true;
|
|
77
|
+
if (action === "undo" || shouldCompare) {
|
|
78
|
+
if (undoFilesystemSideEffects && !fileRestoreMap.has(fileUrl)) {
|
|
79
|
+
if (stateBefore.found) {
|
|
80
|
+
fileRestoreMap.set(fileUrl, () => {
|
|
81
|
+
writeFileSync(fileUrl, stateBefore.buffer);
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
fileRestoreMap.set(fileUrl, () => {
|
|
85
|
+
removeFileSync(fileUrl, { allowUseless: true });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
|
-
|
|
90
|
+
if (shouldCompare) {
|
|
91
|
+
onWriteFile(fileUrl, stateAfter.buffer, reason);
|
|
92
|
+
}
|
|
93
|
+
// "ignore", false, anything else
|
|
76
94
|
};
|
|
77
95
|
const onWriteDirectoryDone = (directoryUrl) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
const action = getAction(directoryUrl);
|
|
97
|
+
const shouldCompare =
|
|
98
|
+
action === "compare" ||
|
|
99
|
+
action === "compare_presence_only" ||
|
|
100
|
+
action === true;
|
|
101
|
+
if (action === "undo" || shouldCompare) {
|
|
102
|
+
if (undoFilesystemSideEffects && !fileRestoreMap.has(directoryUrl)) {
|
|
103
|
+
fileRestoreMap.set(directoryUrl, () => {
|
|
104
|
+
removeDirectorySync(directoryUrl, {
|
|
105
|
+
allowUseless: true,
|
|
106
|
+
recursive: true,
|
|
107
|
+
});
|
|
86
108
|
});
|
|
87
|
-
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (shouldCompare) {
|
|
112
|
+
onWriteDirectory(directoryUrl);
|
|
88
113
|
}
|
|
89
|
-
|
|
114
|
+
// "ignore", false, anything else
|
|
90
115
|
};
|
|
91
116
|
const restoreCallbackSet = new Set();
|
|
92
117
|
|
|
@@ -101,7 +126,9 @@ export const spyFilesystemCalls = (
|
|
|
101
126
|
_internalFs,
|
|
102
127
|
"mkdir",
|
|
103
128
|
(directoryPath, mode, recursive) => {
|
|
104
|
-
const directoryUrl =
|
|
129
|
+
const directoryUrl = ensurePathnameTrailingSlash(
|
|
130
|
+
pathToFileURL(directoryPath),
|
|
131
|
+
);
|
|
105
132
|
const stateBefore = getDirectoryState(directoryPath);
|
|
106
133
|
if (!stateBefore.found && recursive) {
|
|
107
134
|
const ancestorNotFoundArray = [];
|
|
@@ -110,7 +137,9 @@ export const spyFilesystemCalls = (
|
|
|
110
137
|
if (ancestorState.found) {
|
|
111
138
|
break;
|
|
112
139
|
}
|
|
113
|
-
ancestorNotFoundArray.unshift(
|
|
140
|
+
ancestorNotFoundArray.unshift(
|
|
141
|
+
ensurePathnameTrailingSlash(ancestorUrl),
|
|
142
|
+
);
|
|
114
143
|
}
|
|
115
144
|
return {
|
|
116
145
|
return: (fd) => {
|
|
@@ -151,6 +180,13 @@ export const spyFilesystemCalls = (
|
|
|
151
180
|
},
|
|
152
181
|
{ execute: METHOD_EXECUTION_NODE_CALLBACK },
|
|
153
182
|
);
|
|
183
|
+
/*
|
|
184
|
+
* Relying on open/close to detect writes is done to be able to catch
|
|
185
|
+
* write done async, not sure how to distinguish open/close done to write
|
|
186
|
+
* from open/close done to read file stat
|
|
187
|
+
* open/close for file stat are excluded because we compare stateBefore/stateAfter
|
|
188
|
+
* but ideally we would early return by detecting open/close is not for write operations
|
|
189
|
+
*/
|
|
154
190
|
const closeHook = hookIntoMethod(
|
|
155
191
|
_internalFs,
|
|
156
192
|
"close",
|
|
@@ -37,8 +37,7 @@ export const createBigSizeEffect =
|
|
|
37
37
|
export const renderSideEffects = (
|
|
38
38
|
sideEffects,
|
|
39
39
|
{
|
|
40
|
-
|
|
41
|
-
outDirectoryUrl,
|
|
40
|
+
sideEffectMdFileUrl,
|
|
42
41
|
generateOutFileUrl,
|
|
43
42
|
generatedBy = true,
|
|
44
43
|
titleLevel = 1,
|
|
@@ -50,6 +49,7 @@ export const renderSideEffects = (
|
|
|
50
49
|
dedicatedFile: { line: 50, length: 5000 },
|
|
51
50
|
}),
|
|
52
51
|
errorStackHidden,
|
|
52
|
+
errorMessageTransform,
|
|
53
53
|
} = {},
|
|
54
54
|
) => {
|
|
55
55
|
const { rootDirectoryUrl, replaceFilesystemWellKnownValues } =
|
|
@@ -85,14 +85,14 @@ export const renderSideEffects = (
|
|
|
85
85
|
markdown += "\n\n";
|
|
86
86
|
}
|
|
87
87
|
markdown += renderOneSideEffect(sideEffect, {
|
|
88
|
-
|
|
89
|
-
outDirectoryUrl,
|
|
88
|
+
sideEffectMdFileUrl,
|
|
90
89
|
generateOutFileUrl,
|
|
91
90
|
rootDirectoryUrl,
|
|
92
91
|
titleLevel,
|
|
93
92
|
getBigSizeEffect,
|
|
94
93
|
replace,
|
|
95
94
|
errorStackHidden,
|
|
95
|
+
errorMessageTransform,
|
|
96
96
|
lastSideEffectNumber,
|
|
97
97
|
});
|
|
98
98
|
}
|
|
@@ -133,14 +133,14 @@ ${" ".repeat(indent)}</sub>`;
|
|
|
133
133
|
const renderOneSideEffect = (
|
|
134
134
|
sideEffect,
|
|
135
135
|
{
|
|
136
|
-
|
|
137
|
-
outDirectoryUrl,
|
|
136
|
+
sideEffectMdFileUrl,
|
|
138
137
|
generateOutFileUrl,
|
|
139
138
|
rootDirectoryUrl,
|
|
140
139
|
titleLevel,
|
|
141
140
|
getBigSizeEffect,
|
|
142
141
|
replace,
|
|
143
142
|
errorStackHidden,
|
|
143
|
+
errorMessageTransform,
|
|
144
144
|
lastSideEffectNumber,
|
|
145
145
|
},
|
|
146
146
|
) => {
|
|
@@ -152,8 +152,7 @@ const renderOneSideEffect = (
|
|
|
152
152
|
}
|
|
153
153
|
const { md } = sideEffect.render;
|
|
154
154
|
let { label, text } = md({
|
|
155
|
-
|
|
156
|
-
outDirectoryUrl,
|
|
155
|
+
sideEffectMdFileUrl,
|
|
157
156
|
generateOutFileUrl,
|
|
158
157
|
replace,
|
|
159
158
|
rootDirectoryUrl,
|
|
@@ -172,11 +171,12 @@ const renderOneSideEffect = (
|
|
|
172
171
|
}
|
|
173
172
|
text = renderText(text, {
|
|
174
173
|
sideEffect,
|
|
175
|
-
|
|
174
|
+
sideEffectMdFileUrl,
|
|
176
175
|
generateOutFileUrl,
|
|
177
176
|
replace,
|
|
178
177
|
rootDirectoryUrl,
|
|
179
178
|
errorStackHidden,
|
|
179
|
+
errorMessageTransform,
|
|
180
180
|
});
|
|
181
181
|
}
|
|
182
182
|
if (sideEffect.code === "source_code") {
|
|
@@ -208,11 +208,12 @@ const renderText = (
|
|
|
208
208
|
text,
|
|
209
209
|
{
|
|
210
210
|
sideEffect,
|
|
211
|
-
|
|
211
|
+
sideEffectMdFileUrl,
|
|
212
212
|
generateOutFileUrl,
|
|
213
213
|
replace,
|
|
214
214
|
rootDirectoryUrl,
|
|
215
215
|
errorStackHidden,
|
|
216
|
+
errorMessageTransform,
|
|
216
217
|
},
|
|
217
218
|
) => {
|
|
218
219
|
if (text && typeof text === "object") {
|
|
@@ -224,7 +225,7 @@ const renderText = (
|
|
|
224
225
|
}
|
|
225
226
|
const callSiteRelativeUrl = urlToRelativeUrl(
|
|
226
227
|
callSite.url,
|
|
227
|
-
|
|
228
|
+
sideEffectMdFileUrl,
|
|
228
229
|
{ preferRelativeNotation: true },
|
|
229
230
|
);
|
|
230
231
|
const sourceCodeLinkText = `${callSiteRelativeUrl}:${callSite.line}:${callSite.column}`;
|
|
@@ -250,14 +251,17 @@ const renderText = (
|
|
|
250
251
|
typeof jsValue.stack === "string")
|
|
251
252
|
) {
|
|
252
253
|
// return renderMarkdownBlock(text.value.stack);
|
|
253
|
-
const exception = createException(jsValue, {
|
|
254
|
+
const exception = createException(jsValue, {
|
|
255
|
+
rootDirectoryUrl,
|
|
256
|
+
errorMessageTransform,
|
|
257
|
+
});
|
|
254
258
|
const exceptionText = errorStackHidden
|
|
255
259
|
? `${exception.name}: ${exception.message}`
|
|
256
260
|
: exception.stack || exception.message || exception;
|
|
257
261
|
return renderPotentialAnsi(exceptionText, {
|
|
258
262
|
stringType: "error",
|
|
259
263
|
sideEffect,
|
|
260
|
-
|
|
264
|
+
sideEffectMdFileUrl,
|
|
261
265
|
generateOutFileUrl,
|
|
262
266
|
replace,
|
|
263
267
|
});
|
|
@@ -267,7 +271,7 @@ const renderText = (
|
|
|
267
271
|
if (text.type === "console") {
|
|
268
272
|
return renderConsole(text.value, {
|
|
269
273
|
sideEffect,
|
|
270
|
-
|
|
274
|
+
sideEffectMdFileUrl,
|
|
271
275
|
generateOutFileUrl,
|
|
272
276
|
replace,
|
|
273
277
|
});
|
|
@@ -287,12 +291,12 @@ const renderText = (
|
|
|
287
291
|
|
|
288
292
|
export const renderConsole = (
|
|
289
293
|
string,
|
|
290
|
-
{ sideEffect,
|
|
294
|
+
{ sideEffect, sideEffectMdFileUrl, generateOutFileUrl, replace },
|
|
291
295
|
) => {
|
|
292
296
|
return renderPotentialAnsi(string, {
|
|
293
297
|
stringType: "console",
|
|
294
298
|
sideEffect,
|
|
295
|
-
|
|
299
|
+
sideEffectMdFileUrl,
|
|
296
300
|
generateOutFileUrl,
|
|
297
301
|
replace,
|
|
298
302
|
});
|
|
@@ -300,7 +304,7 @@ export const renderConsole = (
|
|
|
300
304
|
|
|
301
305
|
const renderPotentialAnsi = (
|
|
302
306
|
string,
|
|
303
|
-
{ stringType, sideEffect,
|
|
307
|
+
{ stringType, sideEffect, sideEffectMdFileUrl, generateOutFileUrl, replace },
|
|
304
308
|
) => {
|
|
305
309
|
const rawTextBlock = renderMarkdownBlock(
|
|
306
310
|
replace(string, { stringType }),
|
|
@@ -324,7 +328,7 @@ const renderPotentialAnsi = (
|
|
|
324
328
|
);
|
|
325
329
|
svgFileContent = replace(svgFileContent, { fileUrl: svgFileUrl });
|
|
326
330
|
writeFileSync(svgFileUrl, svgFileContent);
|
|
327
|
-
const svgFileRelativeUrl = urlToRelativeUrl(svgFileUrl,
|
|
331
|
+
const svgFileRelativeUrl = urlToRelativeUrl(svgFileUrl, sideEffectMdFileUrl);
|
|
328
332
|
let md = ``;
|
|
329
333
|
md += "\n\n";
|
|
330
334
|
md += renderMarkdownDetails(`${rawTextBlock}`, {
|
|
@@ -1,69 +1,71 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
takeFileSnapshot,
|
|
5
|
-
} from "../filesystem_snapshot.js";
|
|
1
|
+
import { writeFileSync } from "@jsenv/filesystem";
|
|
2
|
+
import { urlToBasename, urlToFilename } from "@jsenv/urls";
|
|
3
|
+
import { takeDirectorySnapshot } from "../filesystem_snapshot.js";
|
|
6
4
|
import { createCaptureSideEffects } from "./create_capture_side_effects.js";
|
|
7
5
|
import { renderSideEffects } from "./render_side_effects.js";
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Generate a markdown file describing code side effects. When executed in CI throw if there is a diff.
|
|
8
|
+
* @param {URL} sourceFileUrl
|
|
9
|
+
* Url where the function is located (import.meta.url)
|
|
10
|
+
* @param {Function} fn
|
|
11
|
+
* Function to snapshot
|
|
12
|
+
* @param {Object} snapshotSideEffectsOptions
|
|
13
|
+
* @param {string|url} snapshotSideEffectsOptions.outFilePattern
|
|
14
|
+
* @param {string|url} snapshotSideEffectsOptions.sideEffectMdFileUrl
|
|
15
|
+
* Where to write the markdown file. Defaults to ./[]
|
|
16
|
+
* @param {string|url} snapshotSideEffectsOptions.rootDirectoryUrl
|
|
17
|
+
* @param {Object|boolean} [snapshotSideEffectsOptions.filesystemEffects]
|
|
18
|
+
* @param {boolean} [snapshotSideEffectsOptions.filesystemEffects.textualFilesInline=false]
|
|
19
|
+
* Put textual files content in the markdown (instead of separate files).
|
|
20
|
+
* Big files will still be put in dedicated files.
|
|
21
|
+
* @param {boolean} [snapshotSideEffectsOptions.filesystemEffects.preserve=false]
|
|
22
|
+
* Preserve filesystem side effect when function ends. By default
|
|
23
|
+
* filesystem effects are undone when function ends
|
|
24
|
+
* @param {url} [snapshotSideEffectsOptions.filesystemEffects.baseDirectory]
|
|
25
|
+
* Urls of filesystem side effects will be relative to this base directory
|
|
26
|
+
* Default to the directory containing @sourceFileUrl
|
|
27
|
+
* @return {Array.<Object>} sideEffects
|
|
28
|
+
*/
|
|
9
29
|
export const snapshotSideEffects = (
|
|
10
30
|
sourceFileUrl,
|
|
11
31
|
fn,
|
|
12
32
|
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
sideEffectFilePattern = "./side_effects/[basename].md",
|
|
16
|
-
outFilePattern = "./side_effects/[name]/[filename]",
|
|
17
|
-
generateOutFileUrl,
|
|
18
|
-
outDirectoryUrl,
|
|
33
|
+
sideEffectMdFileUrl,
|
|
34
|
+
outFilePattern = "_[source_filename]/[filename]",
|
|
19
35
|
errorStackHidden,
|
|
36
|
+
throwWhenDiff,
|
|
20
37
|
...captureOptions
|
|
21
38
|
} = {},
|
|
22
39
|
) => {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.replaceAll("[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
generateOutFileUrl = (filename) => {
|
|
44
|
-
const outRelativeUrl = outFilePattern
|
|
45
|
-
.replaceAll("[name]", name)
|
|
46
|
-
.replaceAll("[basename]", basename)
|
|
47
|
-
.replaceAll("[filename]", filename);
|
|
48
|
-
const outFileUrl = new URL(outRelativeUrl, new URL("./", sourceFileUrl))
|
|
49
|
-
.href;
|
|
50
|
-
return outFileUrl;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const sideEffectFileSnapshot = takeFileSnapshot(sideEffectFileUrl);
|
|
40
|
+
const sourceName = urlToBasename(sourceFileUrl, true);
|
|
41
|
+
const sourceBasename = urlToBasename(sourceFileUrl);
|
|
42
|
+
const sourceFilename = urlToFilename(sourceFileUrl);
|
|
43
|
+
const generateOutFileUrl = (filename) => {
|
|
44
|
+
const outRelativeUrl = outFilePattern
|
|
45
|
+
.replaceAll("[source_name]", sourceName)
|
|
46
|
+
.replaceAll("[source_basename]", sourceBasename)
|
|
47
|
+
.replaceAll("[source_filename]", sourceFilename)
|
|
48
|
+
.replaceAll("[filename]", filename);
|
|
49
|
+
const outFileUrl = new URL(outRelativeUrl, new URL("./", sourceFileUrl))
|
|
50
|
+
.href;
|
|
51
|
+
return outFileUrl;
|
|
52
|
+
};
|
|
53
|
+
const outDirectoryUrl = generateOutFileUrl("");
|
|
54
|
+
sideEffectMdFileUrl =
|
|
55
|
+
sideEffectMdFileUrl || generateOutFileUrl(`${sourceFilename}.md`);
|
|
56
|
+
const captureSideEffects = createCaptureSideEffects({
|
|
57
|
+
...captureOptions,
|
|
58
|
+
sourceFileUrl,
|
|
59
|
+
});
|
|
55
60
|
const outDirectorySnapshot = takeDirectorySnapshot(outDirectoryUrl);
|
|
56
61
|
const onSideEffects = (sideEffects) => {
|
|
57
62
|
const sideEffectFileContent = renderSideEffects(sideEffects, {
|
|
58
|
-
|
|
59
|
-
outDirectoryUrl,
|
|
63
|
+
sideEffectMdFileUrl,
|
|
60
64
|
generateOutFileUrl,
|
|
61
65
|
errorStackHidden,
|
|
62
66
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
outDirectorySnapshot.compare();
|
|
67
|
+
writeFileSync(sideEffectMdFileUrl, sideEffectFileContent);
|
|
68
|
+
outDirectorySnapshot.compare(throwWhenDiff);
|
|
67
69
|
};
|
|
68
70
|
const returnValue = captureSideEffects(fn);
|
|
69
71
|
if (returnValue && typeof returnValue.then === "function") {
|
|
@@ -1,49 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
takeFileSnapshot,
|
|
5
|
-
} from "../filesystem_snapshot.js";
|
|
1
|
+
import { writeFileSync } from "@jsenv/filesystem";
|
|
2
|
+
import { urlToBasename, urlToFilename, urlToRelativeUrl } from "@jsenv/urls";
|
|
3
|
+
import { takeDirectorySnapshot } from "../filesystem_snapshot.js";
|
|
6
4
|
import { getCallerLocation } from "../get_caller_location.js";
|
|
7
5
|
import { createCaptureSideEffects } from "./create_capture_side_effects.js";
|
|
8
6
|
import { renderSideEffects, renderSmallLink } from "./render_side_effects.js";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
|
-
* Generate a markdown file describing
|
|
12
|
-
* @param {URL}
|
|
9
|
+
* Generate a markdown file describing test(s) side effects. When executed in CI throw if there is a diff.
|
|
10
|
+
* @param {URL} sourceFileUrl
|
|
13
11
|
* @param {Function} fnRegisteringTest
|
|
14
12
|
* @param {Object} snapshotTestsOptions
|
|
15
|
-
* @param {string|url} snapshotTestsOptions.
|
|
13
|
+
* @param {string|url} snapshotTestsOptions.outFilePattern
|
|
16
14
|
* @param {string|url} snapshotTestsOptions.rootDirectoryUrl
|
|
17
|
-
* @
|
|
15
|
+
* @param {Object|boolean} [snapshotTestsOptions.filesystemEffects]
|
|
16
|
+
* @param {boolean} [snapshotTestsOptions.filesystemEffects.textualFilesInline=false]
|
|
17
|
+
* Put textual files content in the markdown (instead of separate files).
|
|
18
|
+
* Big files will still be put in dedicated files.
|
|
19
|
+
* @param {boolean} [snapshotTestsOptions.filesystemEffects.preserve=false]
|
|
20
|
+
* Preserve filesystem side effect when function ends. By default
|
|
21
|
+
* filesystem effects are undone when function ends
|
|
22
|
+
* @param {url} [snapshotTestsOptions.filesystemEffects.baseDirectory]
|
|
23
|
+
* Urls of filesystem side effects will be relative to this base directory
|
|
24
|
+
* Default to the directory containing @sourceFileUrl
|
|
18
25
|
*/
|
|
19
26
|
export const snapshotTests = async (
|
|
20
|
-
|
|
27
|
+
sourceFileUrl,
|
|
21
28
|
fnRegisteringTest,
|
|
22
29
|
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
outFilePattern = "./_[source_filename]/[filename]",
|
|
31
|
+
filesystemActions = {
|
|
32
|
+
"**": "compare",
|
|
33
|
+
// "**/*.svg": "compare_presence_only",
|
|
34
|
+
},
|
|
28
35
|
rootDirectoryUrl,
|
|
29
36
|
generatedBy = true,
|
|
30
37
|
linkToSource = true,
|
|
31
38
|
linkToEachSource,
|
|
32
39
|
errorStackHidden,
|
|
40
|
+
errorMessageTransform,
|
|
33
41
|
logEffects,
|
|
34
42
|
filesystemEffects,
|
|
35
43
|
throwWhenDiff = process.env.CI,
|
|
36
44
|
} = {},
|
|
37
45
|
) => {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const sourceName = urlToBasename(sourceFileUrl, true);
|
|
47
|
+
const sourceBasename = urlToBasename(sourceFileUrl);
|
|
48
|
+
const sourceFilename = urlToFilename(sourceFileUrl);
|
|
49
|
+
const generateOutFileUrl = (outFilename) => {
|
|
50
|
+
const outFileRelativeUrl = outFilePattern
|
|
51
|
+
.replaceAll("[source_name]", sourceName)
|
|
52
|
+
.replaceAll("[source_basename]", sourceBasename)
|
|
53
|
+
.replaceAll("[source_filename]", sourceFilename)
|
|
54
|
+
.replaceAll("[filename]", outFilename);
|
|
55
|
+
const outFileUrl = new URL(outFileRelativeUrl, sourceFileUrl).href;
|
|
56
|
+
return outFileUrl;
|
|
57
|
+
};
|
|
58
|
+
const outDirectoryUrl = generateOutFileUrl("");
|
|
59
|
+
const outDirectorySnapshot = takeDirectorySnapshot(
|
|
60
|
+
outDirectoryUrl,
|
|
61
|
+
filesystemActions,
|
|
62
|
+
);
|
|
63
|
+
const sideEffectMdFileUrl = generateOutFileUrl(`${sourceFilename}.md`);
|
|
47
64
|
|
|
48
65
|
const dirUrlMap = new Map();
|
|
49
66
|
const sideEffectsMap = new Map();
|
|
@@ -65,12 +82,14 @@ export const snapshotTests = async (
|
|
|
65
82
|
|
|
66
83
|
const activeTestMap = onlyTestMap.size ? onlyTestMap : testMap;
|
|
67
84
|
const captureSideEffects = createCaptureSideEffects({
|
|
85
|
+
sourceFileUrl,
|
|
68
86
|
rootDirectoryUrl,
|
|
69
87
|
logEffects,
|
|
70
88
|
filesystemEffects,
|
|
89
|
+
filesystemActions,
|
|
71
90
|
});
|
|
72
91
|
let markdown = "";
|
|
73
|
-
markdown += `# ${
|
|
92
|
+
markdown += `# ${sourceName}`;
|
|
74
93
|
if (generatedBy) {
|
|
75
94
|
let generatedByLink = renderSmallLink(
|
|
76
95
|
{
|
|
@@ -80,8 +99,8 @@ export const snapshotTests = async (
|
|
|
80
99
|
{
|
|
81
100
|
prefix: "Generated by ",
|
|
82
101
|
suffix:
|
|
83
|
-
linkToSource &&
|
|
84
|
-
? generateExecutingLink(
|
|
102
|
+
linkToSource && sourceFileUrl
|
|
103
|
+
? generateExecutingLink(sourceFileUrl, sideEffectMdFileUrl)
|
|
85
104
|
: "",
|
|
86
105
|
},
|
|
87
106
|
);
|
|
@@ -89,6 +108,7 @@ export const snapshotTests = async (
|
|
|
89
108
|
markdown += generatedByLink;
|
|
90
109
|
}
|
|
91
110
|
|
|
111
|
+
const scenarioDirs = [];
|
|
92
112
|
for (const [scenario, { fn, callSite }] of activeTestMap) {
|
|
93
113
|
markdown += "\n\n";
|
|
94
114
|
markdown += `## ${scenario}`;
|
|
@@ -99,43 +119,40 @@ export const snapshotTests = async (
|
|
|
99
119
|
});
|
|
100
120
|
sideEffectsMap.set(scenario, sideEffects);
|
|
101
121
|
const testScenario = asValidFilename(scenario);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const outDirectoryUrl = new URL(outDirectoryRelativeUrl, testFileUrl);
|
|
106
|
-
const outDirectorySnapshot = takeDirectorySnapshot(outDirectoryUrl, {
|
|
107
|
-
pattern: {
|
|
108
|
-
"**/*": true,
|
|
109
|
-
"**/*.svg": "presence_only",
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
const generateOutFileUrl = (filename) => {
|
|
113
|
-
const outFileRelativeUrl = outFilePattern
|
|
114
|
-
.replaceAll("[test_name]", testName)
|
|
115
|
-
.replaceAll("[test_basename]", testBasename)
|
|
116
|
-
.replaceAll("[test_scenario]", testScenario)
|
|
117
|
-
.replaceAll("[filename]", filename);
|
|
118
|
-
const outFileUrl = new URL(outFileRelativeUrl, testFileUrl).href;
|
|
119
|
-
return outFileUrl;
|
|
122
|
+
scenarioDirs.push(testScenario);
|
|
123
|
+
const generateScenarioOutFileUrl = (outfilename) => {
|
|
124
|
+
return generateOutFileUrl(`${testScenario}/${outfilename}`);
|
|
120
125
|
};
|
|
121
|
-
const
|
|
122
|
-
dirUrlMap.set(scenario,
|
|
126
|
+
const scenarioOutDirectoryUrl = generateScenarioOutFileUrl("");
|
|
127
|
+
dirUrlMap.set(scenario, scenarioOutDirectoryUrl);
|
|
123
128
|
const sideEffectsMarkdown = renderSideEffects(sideEffects, {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
generateOutFileUrl,
|
|
129
|
+
sideEffectMdFileUrl,
|
|
130
|
+
generateOutFileUrl: generateScenarioOutFileUrl,
|
|
127
131
|
generatedBy: false,
|
|
128
132
|
titleLevel: 3,
|
|
129
133
|
errorStackHidden,
|
|
134
|
+
errorMessageTransform,
|
|
130
135
|
});
|
|
131
|
-
outDirectorySnapshot.compare(throwWhenDiff);
|
|
132
136
|
markdown += sideEffectsMarkdown;
|
|
133
137
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
// if (sideEffectFilePattern === "./side_effects/[filename]/[filename].md") {
|
|
139
|
+
// const scenarioParentDirUrl = new URL("./", sideEffectFileUrl);
|
|
140
|
+
// const dirContent = readDirectorySync(scenarioParentDirUrl);
|
|
141
|
+
// for (const entry of dirContent) {
|
|
142
|
+
// const entryUrl = new URL(entry, scenarioParentDirUrl);
|
|
143
|
+
// if (!readEntryStatSync(entryUrl).isDirectory()) {
|
|
144
|
+
// continue;
|
|
145
|
+
// }
|
|
146
|
+
// if (scenarioDirs.includes(entry)) {
|
|
147
|
+
// continue;
|
|
148
|
+
// }
|
|
149
|
+
// removeDirectorySync(entryUrl, {
|
|
150
|
+
// recursive: true,
|
|
151
|
+
// });
|
|
152
|
+
// }
|
|
153
|
+
// }
|
|
154
|
+
writeFileSync(sideEffectMdFileUrl, markdown);
|
|
155
|
+
outDirectorySnapshot.compare(throwWhenDiff);
|
|
139
156
|
|
|
140
157
|
return { dirUrlMap, sideEffectsMap };
|
|
141
158
|
};
|