@jsenv/snapshot 2.7.5 → 2.8.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.
|
|
3
|
+
"version": "2.8.1",
|
|
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.1.14",
|
|
37
|
-
"@jsenv/ast": "6.2.
|
|
37
|
+
"@jsenv/ast": "6.2.14",
|
|
38
38
|
"@jsenv/exception": "1.0.1",
|
|
39
39
|
"@jsenv/filesystem": "4.9.9",
|
|
40
40
|
"@jsenv/terminal-recorder": "1.4.3",
|
|
@@ -58,6 +58,7 @@ export const createWellKnown = (name, replacement = name) => {
|
|
|
58
58
|
|
|
59
59
|
export const createReplaceFilesystemWellKnownValues = ({
|
|
60
60
|
rootDirectoryUrl,
|
|
61
|
+
localhostUrl,
|
|
61
62
|
// for unit tests
|
|
62
63
|
isWindows = process.platform === "win32",
|
|
63
64
|
ancestorPackagesDisabled,
|
|
@@ -126,9 +127,32 @@ export const createReplaceFilesystemWellKnownValues = ({
|
|
|
126
127
|
}
|
|
127
128
|
};
|
|
128
129
|
};
|
|
130
|
+
const addWellKnownUrl = (url, replacement) => {
|
|
131
|
+
const wellKnownUrl = {
|
|
132
|
+
url,
|
|
133
|
+
replace: (string) => {
|
|
134
|
+
return string.replaceAll(url, replacement);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
wellKownUrlArray.push(wellKnownUrl);
|
|
138
|
+
return () => {
|
|
139
|
+
const urlIndex = wellKownUrlArray.indexOf(wellKnownUrl);
|
|
140
|
+
if (urlIndex > -1) {
|
|
141
|
+
wellKownUrlArray.splice(urlIndex, 1);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
};
|
|
129
145
|
if (rootDirectoryUrl) {
|
|
130
146
|
addWellKnownFileUrl(rootDirectoryUrl, WELL_KNOWN_ROOT);
|
|
131
147
|
}
|
|
148
|
+
if (localhostUrl) {
|
|
149
|
+
addWellKnownUrl(
|
|
150
|
+
localhostUrl,
|
|
151
|
+
localhostUrl.startsWith("https")
|
|
152
|
+
? "https://localhost"
|
|
153
|
+
: "http://localhost",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
132
156
|
/*
|
|
133
157
|
* When running code inside a node project ancestor packages
|
|
134
158
|
* should make things super predictible because
|
|
@@ -203,23 +227,17 @@ export const createReplaceFilesystemWellKnownValues = ({
|
|
|
203
227
|
|
|
204
228
|
const replaceFileUrls = (string, { willBeWrittenOnFilesystem }) => {
|
|
205
229
|
for (const wellKownUrl of wellKownUrlArray) {
|
|
206
|
-
|
|
230
|
+
string = wellKownUrl.replace(string, {
|
|
207
231
|
willBeWrittenOnFilesystem,
|
|
208
232
|
});
|
|
209
|
-
if (replaceResult !== string) {
|
|
210
|
-
return replaceResult;
|
|
211
|
-
}
|
|
212
233
|
}
|
|
213
234
|
return string;
|
|
214
235
|
};
|
|
215
236
|
const replaceFilePaths = (string, { willBeWrittenOnFilesystem }) => {
|
|
216
237
|
for (const wellKownPath of wellKnownPathArray) {
|
|
217
|
-
|
|
238
|
+
string = wellKownPath.replace(string, {
|
|
218
239
|
willBeWrittenOnFilesystem,
|
|
219
240
|
});
|
|
220
|
-
if (replaceResult !== string) {
|
|
221
|
-
return replaceResult;
|
|
222
|
-
}
|
|
223
241
|
}
|
|
224
242
|
return string;
|
|
225
243
|
};
|
|
@@ -12,119 +12,205 @@ import {
|
|
|
12
12
|
visitHtmlNodes,
|
|
13
13
|
} from "@jsenv/ast";
|
|
14
14
|
import { urlToExtension } from "@jsenv/urls";
|
|
15
|
+
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
15
16
|
import stripAnsi from "strip-ansi";
|
|
16
17
|
import { createReplaceFilesystemWellKnownValues } from "./filesystem_well_known_values.js";
|
|
17
18
|
|
|
18
19
|
export const replaceFluctuatingValues = (
|
|
19
|
-
|
|
20
|
+
value,
|
|
20
21
|
{
|
|
21
22
|
stringType,
|
|
22
23
|
rootDirectoryUrl,
|
|
24
|
+
localhostUrl,
|
|
23
25
|
fileUrl,
|
|
24
26
|
preserveAnsi,
|
|
25
27
|
// for unit test
|
|
26
28
|
replaceFilesystemWellKnownValues = createReplaceFilesystemWellKnownValues({
|
|
27
29
|
rootDirectoryUrl,
|
|
30
|
+
localhostUrl,
|
|
28
31
|
}),
|
|
29
32
|
} = {},
|
|
30
33
|
) => {
|
|
31
|
-
if (fileUrl
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
34
|
+
if (fileUrl) {
|
|
35
|
+
const contentType = CONTENT_TYPE.fromUrlExtension(fileUrl);
|
|
36
|
+
if (Buffer.isBuffer(value) && CONTENT_TYPE.isTextual(contentType)) {
|
|
37
|
+
value = String(value);
|
|
38
|
+
}
|
|
39
|
+
if (stringType === undefined) {
|
|
40
|
+
const extension = urlToExtension(fileUrl);
|
|
41
|
+
if (extension === ".html") {
|
|
42
|
+
stringType = "html";
|
|
43
|
+
} else if (extension === ".svg") {
|
|
44
|
+
stringType = "svg";
|
|
45
|
+
} else if (extension === ".json" || CONTENT_TYPE.isJson(contentType)) {
|
|
46
|
+
stringType = "json";
|
|
47
|
+
}
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
|
-
const replaceDurations = (
|
|
50
|
+
const replaceDurations = (string) => {
|
|
40
51
|
// https://stackoverflow.com/a/59202307/24573072
|
|
41
|
-
|
|
52
|
+
string = string.replace(
|
|
42
53
|
/(?<!\d|\.)\d+(?:\.\d+)?(\s*)(seconds|second|s)\b/g,
|
|
43
54
|
(match, space, unit) => {
|
|
44
55
|
if (unit === "seconds") unit = "second";
|
|
45
56
|
return `<X>${space}${unit}`;
|
|
46
57
|
},
|
|
47
58
|
);
|
|
48
|
-
return
|
|
59
|
+
return string;
|
|
49
60
|
};
|
|
50
|
-
const replaceSizes = (
|
|
61
|
+
const replaceSizes = (string) => {
|
|
51
62
|
// the size of files might slighly differ from an OS to an other
|
|
52
63
|
// we round the floats to make them predictable
|
|
53
64
|
// (happens for HTML files where one char is added on linux)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
);
|
|
60
|
-
return
|
|
65
|
+
// string = string.replace(
|
|
66
|
+
// /(?<!\d|\.)(\d+(?:\.\d+)?)(\s*)(B|kB|MB)\b/g,
|
|
67
|
+
// (match, size, space, unit) => {
|
|
68
|
+
// return `${Math.round(parseFloat(size))}${space}${unit}`;
|
|
69
|
+
// },
|
|
70
|
+
// );
|
|
71
|
+
return string;
|
|
61
72
|
};
|
|
62
|
-
const replaceThings = (
|
|
73
|
+
const replaceThings = (string) => {
|
|
63
74
|
if (stringType === "filesystem") {
|
|
64
|
-
return replaceFilesystemWellKnownValues(
|
|
75
|
+
return replaceFilesystemWellKnownValues(string);
|
|
65
76
|
}
|
|
66
77
|
if (!preserveAnsi) {
|
|
67
|
-
|
|
78
|
+
string = stripAnsi(string);
|
|
68
79
|
}
|
|
69
|
-
|
|
80
|
+
string = replaceFilesystemWellKnownValues(string, {
|
|
70
81
|
willBeWrittenOnFilesystem: false,
|
|
71
82
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
83
|
+
string = replaceHttpUrls(string);
|
|
84
|
+
string = replaceDurations(string);
|
|
85
|
+
string = replaceSizes(string);
|
|
86
|
+
return string;
|
|
76
87
|
};
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
if (value === null) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "string") {
|
|
92
|
+
if (stringType === "json") {
|
|
93
|
+
const jsValue = JSON.parse(value);
|
|
94
|
+
const replaced = replaceInObject(jsValue, { replace: replaceThings });
|
|
95
|
+
return JSON.stringify(replaced, null, " ");
|
|
96
|
+
}
|
|
97
|
+
if (stringType === "html" || stringType === "svg") {
|
|
98
|
+
// do parse html
|
|
99
|
+
const htmlAst =
|
|
100
|
+
stringType === "svg"
|
|
101
|
+
? parseSvgString(value)
|
|
102
|
+
: parseHtml({
|
|
103
|
+
html: value,
|
|
104
|
+
storeOriginalPositions: false,
|
|
105
|
+
});
|
|
106
|
+
// for each attribute value
|
|
107
|
+
// and each text node content
|
|
108
|
+
visitHtmlNodes(htmlAst, {
|
|
109
|
+
"*": (node) => {
|
|
110
|
+
const htmlNodeText = getHtmlNodeText(node);
|
|
111
|
+
if (htmlNodeText) {
|
|
112
|
+
setHtmlNodeText(node, replaceThings(htmlNodeText));
|
|
98
113
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
const attributes = getHtmlNodeAttributes(node);
|
|
115
|
+
if (attributes) {
|
|
116
|
+
for (const name of Object.keys(attributes)) {
|
|
117
|
+
attributes[name] = replaceThings(attributes[name]);
|
|
118
|
+
}
|
|
119
|
+
setHtmlNodeAttributes(node, attributes);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
return stringifyHtmlAst(htmlAst);
|
|
124
|
+
}
|
|
125
|
+
return replaceThings(value);
|
|
104
126
|
}
|
|
105
|
-
|
|
127
|
+
if (typeof value === "object") {
|
|
128
|
+
if (Buffer.isBuffer(value)) {
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
const jsValueReplaced = replaceInObject(value, { replace: replaceThings });
|
|
132
|
+
return JSON.stringify(jsValueReplaced, null, " ");
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
106
135
|
};
|
|
107
136
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
137
|
+
const replaceInObject = (object, { replace }) => {
|
|
138
|
+
const deepCopy = (
|
|
139
|
+
value,
|
|
140
|
+
{ shouldReplaceStrings, shouldReplaceNumbers } = {},
|
|
141
|
+
) => {
|
|
142
|
+
if (value === null) {
|
|
143
|
+
return null;
|
|
115
144
|
}
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
if (Array.isArray(value)) {
|
|
146
|
+
const copy = [];
|
|
147
|
+
let i = 0;
|
|
148
|
+
while (i < value.length) {
|
|
149
|
+
copy[i] = deepCopy(value[i], {
|
|
150
|
+
shouldReplaceStrings,
|
|
151
|
+
shouldReplaceNumbers,
|
|
152
|
+
});
|
|
153
|
+
i++;
|
|
120
154
|
}
|
|
121
|
-
|
|
122
|
-
|
|
155
|
+
return copy;
|
|
156
|
+
}
|
|
157
|
+
if (typeof value === "object") {
|
|
158
|
+
const copy = {};
|
|
159
|
+
const keysToVisit = Object.keys(value);
|
|
160
|
+
for (const keyToVisit of keysToVisit) {
|
|
161
|
+
const nestedValue = value[keyToVisit];
|
|
162
|
+
copy[keyToVisit] = deepCopy(nestedValue, {
|
|
163
|
+
shouldReplaceStrings: shouldReplaceStrings || keyToVisit === "os",
|
|
164
|
+
shouldReplaceNumbers:
|
|
165
|
+
shouldReplaceNumbers ||
|
|
166
|
+
keyToVisit === "timings" ||
|
|
167
|
+
keyToVisit === "performance" ||
|
|
168
|
+
keyToVisit === "memoryUsage" ||
|
|
169
|
+
keyToVisit === "cpuUsage" ||
|
|
170
|
+
keyToVisit === "os",
|
|
171
|
+
});
|
|
123
172
|
}
|
|
124
|
-
|
|
125
|
-
return url;
|
|
126
|
-
} catch (e) {
|
|
127
|
-
return match;
|
|
173
|
+
return copy;
|
|
128
174
|
}
|
|
129
|
-
|
|
175
|
+
if (typeof value === "string") {
|
|
176
|
+
if (shouldReplaceStrings) {
|
|
177
|
+
return "<X>";
|
|
178
|
+
}
|
|
179
|
+
return replace(value);
|
|
180
|
+
}
|
|
181
|
+
if (typeof value === "number") {
|
|
182
|
+
if (shouldReplaceNumbers) {
|
|
183
|
+
return "<X>";
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
};
|
|
189
|
+
const copy = deepCopy(object);
|
|
190
|
+
return copy;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const replaceHttpUrls = (source) => {
|
|
194
|
+
return source;
|
|
195
|
+
// return source.replace(/(?:https?|ftp):\/\/\S+[\w/]/g, (match) => {
|
|
196
|
+
// const lastChar = match[match.length - 1];
|
|
197
|
+
// // hotfix because our url regex sucks a bit
|
|
198
|
+
// const endsWithSeparationChar = lastChar === ")" || lastChar === ":";
|
|
199
|
+
// if (endsWithSeparationChar) {
|
|
200
|
+
// match = match.slice(0, -1);
|
|
201
|
+
// }
|
|
202
|
+
// try {
|
|
203
|
+
// const urlObject = new URL(match);
|
|
204
|
+
// if (urlObject.hostname === "www.w3.org") {
|
|
205
|
+
// return match;
|
|
206
|
+
// }
|
|
207
|
+
// if (urlObject.port) {
|
|
208
|
+
// urlObject.port = 9999;
|
|
209
|
+
// }
|
|
210
|
+
// const url = urlObject.href;
|
|
211
|
+
// return url;
|
|
212
|
+
// } catch (e) {
|
|
213
|
+
// return match;
|
|
214
|
+
// }
|
|
215
|
+
// });
|
|
130
216
|
};
|
|
@@ -160,6 +160,16 @@ const renderOneSideEffect = (
|
|
|
160
160
|
lastSideEffectNumber,
|
|
161
161
|
});
|
|
162
162
|
if (text) {
|
|
163
|
+
if (
|
|
164
|
+
sideEffect.number === 1 &&
|
|
165
|
+
lastSideEffectNumber === 1 &&
|
|
166
|
+
(sideEffect.code === "return" ||
|
|
167
|
+
sideEffect.code === "throw" ||
|
|
168
|
+
sideEffect.code === "resolve" ||
|
|
169
|
+
sideEffect.code === "reject")
|
|
170
|
+
) {
|
|
171
|
+
label = null;
|
|
172
|
+
}
|
|
163
173
|
text = renderText(text, {
|
|
164
174
|
sideEffect,
|
|
165
175
|
sideEffectFileUrl,
|
|
@@ -167,11 +177,6 @@ const renderOneSideEffect = (
|
|
|
167
177
|
replace,
|
|
168
178
|
rootDirectoryUrl,
|
|
169
179
|
errorStackHidden,
|
|
170
|
-
onRenderError: () => {
|
|
171
|
-
if (sideEffect.number === 1 && lastSideEffectNumber === 1) {
|
|
172
|
-
label = null;
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
180
|
});
|
|
176
181
|
}
|
|
177
182
|
if (sideEffect.code === "source_code") {
|
|
@@ -208,7 +213,6 @@ const renderText = (
|
|
|
208
213
|
replace,
|
|
209
214
|
rootDirectoryUrl,
|
|
210
215
|
errorStackHidden,
|
|
211
|
-
onRenderError = () => {},
|
|
212
216
|
},
|
|
213
217
|
) => {
|
|
214
218
|
if (text && typeof text === "object") {
|
|
@@ -233,21 +237,20 @@ const renderText = (
|
|
|
233
237
|
return sourceMd;
|
|
234
238
|
}
|
|
235
239
|
if (text.type === "js_value") {
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
240
|
+
const jsValue = text.value;
|
|
241
|
+
if (jsValue === undefined) {
|
|
238
242
|
return renderMarkdownBlock("undefined", "js");
|
|
239
243
|
}
|
|
240
244
|
if (
|
|
241
|
-
|
|
242
|
-
(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
typeof
|
|
245
|
+
jsValue instanceof Error ||
|
|
246
|
+
(jsValue &&
|
|
247
|
+
jsValue.constructor &&
|
|
248
|
+
jsValue.constructor.name.includes("Error") &&
|
|
249
|
+
jsValue.stack &&
|
|
250
|
+
typeof jsValue.stack === "string")
|
|
247
251
|
) {
|
|
248
|
-
onRenderError();
|
|
249
252
|
// return renderMarkdownBlock(text.value.stack);
|
|
250
|
-
const exception = createException(
|
|
253
|
+
const exception = createException(jsValue, { rootDirectoryUrl });
|
|
251
254
|
const exceptionText = errorStackHidden
|
|
252
255
|
? `${exception.name}: ${exception.message}`
|
|
253
256
|
: exception.stack || exception.message || exception;
|
|
@@ -259,14 +262,7 @@ const renderText = (
|
|
|
259
262
|
replace,
|
|
260
263
|
});
|
|
261
264
|
}
|
|
262
|
-
|
|
263
|
-
return renderMarkdownBlock(
|
|
264
|
-
replace(
|
|
265
|
-
typeof value === "string" ? value : JSON.stringify(value, null, " "),
|
|
266
|
-
{ stringType: "json" },
|
|
267
|
-
),
|
|
268
|
-
"js",
|
|
269
|
-
);
|
|
265
|
+
return renderMarkdownBlock(replace(jsValue), "js");
|
|
270
266
|
}
|
|
271
267
|
if (text.type === "console") {
|
|
272
268
|
return renderConsole(text.value, {
|
|
@@ -279,7 +275,6 @@ const renderText = (
|
|
|
279
275
|
if (text.type === "file_content") {
|
|
280
276
|
return renderFileContent(text, {
|
|
281
277
|
sideEffect,
|
|
282
|
-
sideEffectFileUrl,
|
|
283
278
|
replace,
|
|
284
279
|
});
|
|
285
280
|
}
|
|
@@ -341,16 +336,18 @@ const renderPotentialAnsi = (
|
|
|
341
336
|
|
|
342
337
|
export const renderFileContent = (text, { sideEffect, replace }) => {
|
|
343
338
|
const { url, buffer, outDirectoryReason } = sideEffect.value;
|
|
339
|
+
const { value } = text;
|
|
340
|
+
let content = value;
|
|
344
341
|
if (outDirectoryReason) {
|
|
345
|
-
const {
|
|
346
|
-
writeFileSync(urlInsideOutDirectory, buffer);
|
|
342
|
+
const { outRelativeUrl, urlInsideOutDirectory } = text;
|
|
343
|
+
writeFileSync(urlInsideOutDirectory, replace(buffer, { fileUrl: url }));
|
|
347
344
|
let md = "";
|
|
348
345
|
if (
|
|
349
346
|
outDirectoryReason === "lot_of_chars" ||
|
|
350
347
|
outDirectoryReason === "lot_of_lines"
|
|
351
348
|
) {
|
|
352
349
|
md += "\n";
|
|
353
|
-
md += renderMarkdownBlock(escapeMarkdownBlockContent(replace(
|
|
350
|
+
md += renderMarkdownBlock(escapeMarkdownBlockContent(replace(content)));
|
|
354
351
|
const fileLink = renderLinkMarkdown(
|
|
355
352
|
{
|
|
356
353
|
text: outRelativeUrl,
|
|
@@ -371,13 +368,14 @@ export const renderFileContent = (text, { sideEffect, replace }) => {
|
|
|
371
368
|
);
|
|
372
369
|
return md;
|
|
373
370
|
}
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
const extension = urlToExtension(url).slice(1);
|
|
377
|
-
if (extension === "md") {
|
|
371
|
+
const extension = urlToExtension(url);
|
|
372
|
+
if (extension === ".md") {
|
|
378
373
|
content = escapeMarkdownBlockContent(content);
|
|
379
374
|
}
|
|
380
|
-
return renderMarkdownBlock(
|
|
375
|
+
return renderMarkdownBlock(
|
|
376
|
+
replace(content, { fileUrl: url }),
|
|
377
|
+
extension.slice(1),
|
|
378
|
+
);
|
|
381
379
|
};
|
|
382
380
|
|
|
383
381
|
const escapeMarkdownBlockContent = (content) => {
|