@vitest/snapshot 2.0.0-beta.8 → 2.0.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/README.md +7 -2
- package/dist/{environment-BaTWxlGj.d.ts → environment-Ddx0EDtY.d.ts} +8 -0
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +5 -6
- package/dist/{index-CTmsp3V1.d.ts → index-reKGmLsM.d.ts} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +269 -124
- package/dist/manager.d.ts +2 -2
- package/dist/manager.js +9 -8
- package/package.json +2 -2
package/README.md
CHANGED
@@ -12,7 +12,8 @@ import { SnapshotManager } from '@vitest/snapshot/manager'
|
|
12
12
|
const client = new SnapshotClient({
|
13
13
|
// you need to provide your own equality check implementation if you use it
|
14
14
|
// this function is called when `.toMatchSnapshot({ property: 1 })` is called
|
15
|
-
isEqual: (received, expected) =>
|
15
|
+
isEqual: (received, expected) =>
|
16
|
+
equals(received, expected, [iterableEquality, subsetEquality]),
|
16
17
|
})
|
17
18
|
|
18
19
|
// class that implements snapshot saving and reading
|
@@ -53,7 +54,11 @@ const options = {
|
|
53
54
|
snapshotEnvironment: environment,
|
54
55
|
}
|
55
56
|
|
56
|
-
await client.startCurrentRun(
|
57
|
+
await client.startCurrentRun(
|
58
|
+
getCurrentFilepath(),
|
59
|
+
getCurrentTestName(),
|
60
|
+
options
|
61
|
+
)
|
57
62
|
|
58
63
|
// this will save snapshot to a file which is returned by "snapshotEnvironment.resolvePath"
|
59
64
|
client.assert({
|
@@ -1,3 +1,10 @@
|
|
1
|
+
interface ParsedStack {
|
2
|
+
method: string;
|
3
|
+
file: string;
|
4
|
+
line: number;
|
5
|
+
column: number;
|
6
|
+
}
|
7
|
+
|
1
8
|
interface SnapshotEnvironment {
|
2
9
|
getVersion: () => string;
|
3
10
|
getHeader: () => string;
|
@@ -6,6 +13,7 @@ interface SnapshotEnvironment {
|
|
6
13
|
saveSnapshotFile: (filepath: string, snapshot: string) => Promise<void>;
|
7
14
|
readSnapshotFile: (filepath: string) => Promise<string | null>;
|
8
15
|
removeSnapshotFile: (filepath: string) => Promise<void>;
|
16
|
+
processStackTrace?: (stack: ParsedStack) => ParsedStack;
|
9
17
|
}
|
10
18
|
interface SnapshotEnvironmentOptions {
|
11
19
|
snapshotsDirName?: string;
|
package/dist/environment.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { S as SnapshotEnvironment, a as SnapshotEnvironmentOptions } from './environment-
|
1
|
+
import { S as SnapshotEnvironment, a as SnapshotEnvironmentOptions } from './environment-Ddx0EDtY.js';
|
2
2
|
|
3
3
|
declare class NodeSnapshotEnvironment implements SnapshotEnvironment {
|
4
4
|
private options;
|
package/dist/environment.js
CHANGED
@@ -16,10 +16,7 @@ class NodeSnapshotEnvironment {
|
|
16
16
|
}
|
17
17
|
async resolvePath(filepath) {
|
18
18
|
return join(
|
19
|
-
join(
|
20
|
-
dirname(filepath),
|
21
|
-
this.options.snapshotsDirName ?? "__snapshots__"
|
22
|
-
),
|
19
|
+
join(dirname(filepath), this.options.snapshotsDirName ?? "__snapshots__"),
|
23
20
|
`${basename(filepath)}.snap`
|
24
21
|
);
|
25
22
|
}
|
@@ -31,13 +28,15 @@ class NodeSnapshotEnvironment {
|
|
31
28
|
await promises.writeFile(filepath, snapshot, "utf-8");
|
32
29
|
}
|
33
30
|
async readSnapshotFile(filepath) {
|
34
|
-
if (!existsSync(filepath))
|
31
|
+
if (!existsSync(filepath)) {
|
35
32
|
return null;
|
33
|
+
}
|
36
34
|
return promises.readFile(filepath, "utf-8");
|
37
35
|
}
|
38
36
|
async removeSnapshotFile(filepath) {
|
39
|
-
if (existsSync(filepath))
|
37
|
+
if (existsSync(filepath)) {
|
40
38
|
await promises.unlink(filepath);
|
39
|
+
}
|
41
40
|
}
|
42
41
|
}
|
43
42
|
|
package/dist/index.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import { S as SnapshotStateOptions, a as SnapshotMatchOptions, b as SnapshotResult, R as RawSnapshotInfo } from './index-
|
2
|
-
export { c as SnapshotData, e as SnapshotSerializer, f as SnapshotSummary, d as SnapshotUpdateState, U as UncheckedSnapshot } from './index-
|
3
|
-
import { S as SnapshotEnvironment } from './environment-
|
1
|
+
import { S as SnapshotStateOptions, a as SnapshotMatchOptions, b as SnapshotResult, R as RawSnapshotInfo } from './index-reKGmLsM.js';
|
2
|
+
export { c as SnapshotData, e as SnapshotSerializer, f as SnapshotSummary, d as SnapshotUpdateState, U as UncheckedSnapshot } from './index-reKGmLsM.js';
|
3
|
+
import { S as SnapshotEnvironment } from './environment-Ddx0EDtY.js';
|
4
4
|
import { Plugin, Plugins } from 'pretty-format';
|
5
5
|
|
6
6
|
interface ParsedStack {
|
package/dist/index.js
CHANGED
@@ -86,19 +86,23 @@ function getCallLastIndex(code) {
|
|
86
86
|
const char = code[charIndex];
|
87
87
|
const isCharString = char === '"' || char === "'" || char === "`";
|
88
88
|
if (isCharString && beforeChar !== "\\") {
|
89
|
-
if (inString === char)
|
89
|
+
if (inString === char) {
|
90
90
|
inString = null;
|
91
|
-
else if (!inString)
|
91
|
+
} else if (!inString) {
|
92
92
|
inString = char;
|
93
|
+
}
|
93
94
|
}
|
94
95
|
if (!inString) {
|
95
|
-
if (char === "(")
|
96
|
+
if (char === "(") {
|
96
97
|
startedBracers++;
|
97
|
-
|
98
|
+
}
|
99
|
+
if (char === ")") {
|
98
100
|
endedBracers++;
|
101
|
+
}
|
99
102
|
}
|
100
|
-
if (startedBracers && endedBracers && startedBracers === endedBracers)
|
103
|
+
if (startedBracers && endedBracers && startedBracers === endedBracers) {
|
101
104
|
return charIndex;
|
105
|
+
}
|
102
106
|
}
|
103
107
|
return null;
|
104
108
|
}
|
@@ -142,10 +146,12 @@ function positionToOffset(source, lineNumber, columnNumber) {
|
|
142
146
|
const lines = source.split(lineSplitRE);
|
143
147
|
const nl = /\r\n/.test(source) ? 2 : 1;
|
144
148
|
let start = 0;
|
145
|
-
if (lineNumber > lines.length)
|
149
|
+
if (lineNumber > lines.length) {
|
146
150
|
return source.length;
|
147
|
-
|
151
|
+
}
|
152
|
+
for (let i = 0; i < lineNumber - 1; i++) {
|
148
153
|
start += lines[i].length + nl;
|
154
|
+
}
|
149
155
|
return start + columnNumber;
|
150
156
|
}
|
151
157
|
function offsetToLineNumber(source, offset) {
|
@@ -160,8 +166,9 @@ function offsetToLineNumber(source, offset) {
|
|
160
166
|
let line = 0;
|
161
167
|
for (; line < lines.length; line++) {
|
162
168
|
const lineLength = lines[line].length + nl;
|
163
|
-
if (counted + lineLength >= offset)
|
169
|
+
if (counted + lineLength >= offset) {
|
164
170
|
break;
|
171
|
+
}
|
165
172
|
counted += lineLength;
|
166
173
|
}
|
167
174
|
return line + 1;
|
@@ -231,7 +238,19 @@ const serialize$1 = (val, config, indentation, depth, refs, printer) => {
|
|
231
238
|
let callsString = "";
|
232
239
|
if (val.mock.calls.length !== 0) {
|
233
240
|
const indentationNext = indentation + config.indent;
|
234
|
-
callsString = ` {${config.spacingOuter}${indentationNext}"calls": ${printer(
|
241
|
+
callsString = ` {${config.spacingOuter}${indentationNext}"calls": ${printer(
|
242
|
+
val.mock.calls,
|
243
|
+
config,
|
244
|
+
indentationNext,
|
245
|
+
depth,
|
246
|
+
refs
|
247
|
+
)}${config.min ? ", " : ","}${config.spacingOuter}${indentationNext}"results": ${printer(
|
248
|
+
val.mock.results,
|
249
|
+
config,
|
250
|
+
indentationNext,
|
251
|
+
depth,
|
252
|
+
refs
|
253
|
+
)}${config.min ? "" : ","}${config.spacingOuter}${indentation}}`;
|
235
254
|
}
|
236
255
|
return `[MockFunction${nameString}]${callsString}`;
|
237
256
|
};
|
@@ -266,8 +285,9 @@ function testNameToKey(testName, count) {
|
|
266
285
|
return `${testName} ${count}`;
|
267
286
|
}
|
268
287
|
function keyToTestName(key) {
|
269
|
-
if (!/ \d+$/.test(key))
|
288
|
+
if (!/ \d+$/.test(key)) {
|
270
289
|
throw new Error("Snapshot keys must end with a number.");
|
290
|
+
}
|
271
291
|
return key.replace(/ \d+$/, "");
|
272
292
|
}
|
273
293
|
function getSnapshotData(content, options) {
|
@@ -284,8 +304,9 @@ function getSnapshotData(content, options) {
|
|
284
304
|
}
|
285
305
|
}
|
286
306
|
const isInvalid = snapshotContents;
|
287
|
-
if ((update === "all" || update === "new") && isInvalid)
|
307
|
+
if ((update === "all" || update === "new") && isInvalid) {
|
288
308
|
dirty = true;
|
309
|
+
}
|
289
310
|
return { data, dirty };
|
290
311
|
}
|
291
312
|
function addExtraLineBreaks(string) {
|
@@ -320,7 +341,9 @@ function normalizeNewlines(string) {
|
|
320
341
|
}
|
321
342
|
async function saveSnapshotFile(environment, snapshotData, snapshotPath) {
|
322
343
|
const snapshots = Object.keys(snapshotData).sort(naturalCompare$1).map(
|
323
|
-
(key) => `exports[${printBacktickString(key)}] = ${printBacktickString(
|
344
|
+
(key) => `exports[${printBacktickString(key)}] = ${printBacktickString(
|
345
|
+
normalizeNewlines(snapshotData[key])
|
346
|
+
)};`
|
324
347
|
);
|
325
348
|
const content = `${environment.getHeader()}
|
326
349
|
|
@@ -328,20 +351,19 @@ ${snapshots.join("\n\n")}
|
|
328
351
|
`;
|
329
352
|
const oldContent = await environment.readSnapshotFile(snapshotPath);
|
330
353
|
const skipWriting = oldContent != null && oldContent === content;
|
331
|
-
if (skipWriting)
|
354
|
+
if (skipWriting) {
|
332
355
|
return;
|
333
|
-
|
334
|
-
|
335
|
-
content
|
336
|
-
);
|
356
|
+
}
|
357
|
+
await environment.saveSnapshotFile(snapshotPath, content);
|
337
358
|
}
|
338
359
|
function prepareExpected(expected) {
|
339
360
|
function findStartIndent() {
|
340
361
|
var _a, _b;
|
341
362
|
const matchObject = /^( +)\}\s+$/m.exec(expected || "");
|
342
363
|
const objectIndent = (_a = matchObject == null ? void 0 : matchObject[1]) == null ? void 0 : _a.length;
|
343
|
-
if (objectIndent)
|
364
|
+
if (objectIndent) {
|
344
365
|
return objectIndent;
|
366
|
+
}
|
345
367
|
const matchText = /^\n( +)"/.exec(expected || "");
|
346
368
|
return ((_b = matchText == null ? void 0 : matchText[1]) == null ? void 0 : _b.length) || 0;
|
347
369
|
}
|
@@ -371,9 +393,11 @@ function deepMergeSnapshot(target, source) {
|
|
371
393
|
const mergedOutput = { ...target };
|
372
394
|
Object.keys(source).forEach((key) => {
|
373
395
|
if (isObject(source[key]) && !source[key].$$typeof) {
|
374
|
-
if (!(key in target))
|
396
|
+
if (!(key in target)) {
|
375
397
|
Object.assign(mergedOutput, { [key]: source[key] });
|
376
|
-
else
|
398
|
+
} else {
|
399
|
+
mergedOutput[key] = deepMergeSnapshot(target[key], source[key]);
|
400
|
+
}
|
377
401
|
} else if (Array.isArray(source[key])) {
|
378
402
|
mergedOutput[key] = deepMergeArray(target[key], source[key]);
|
379
403
|
} else {
|
@@ -959,21 +983,28 @@ const stackIgnorePatterns = [
|
|
959
983
|
"/node_modules/tinypool/",
|
960
984
|
"/node_modules/tinyspy/",
|
961
985
|
// browser related deps
|
962
|
-
"/deps/
|
963
|
-
"/deps
|
964
|
-
"/deps/
|
986
|
+
"/deps/chunk-",
|
987
|
+
"/deps/@vitest",
|
988
|
+
"/deps/loupe",
|
989
|
+
"/deps/chai",
|
965
990
|
/node:\w+/,
|
966
991
|
/__vitest_test__/,
|
967
|
-
/__vitest_browser__
|
992
|
+
/__vitest_browser__/,
|
993
|
+
/\/deps\/vitest_/
|
968
994
|
];
|
969
995
|
function extractLocation(urlLike) {
|
970
|
-
if (!urlLike.includes(":"))
|
996
|
+
if (!urlLike.includes(":")) {
|
971
997
|
return [urlLike];
|
998
|
+
}
|
972
999
|
const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
|
973
1000
|
const parts = regExp.exec(urlLike.replace(/^\(|\)$/g, ""));
|
974
|
-
if (!parts)
|
1001
|
+
if (!parts) {
|
975
1002
|
return [urlLike];
|
1003
|
+
}
|
976
1004
|
let url = parts[1];
|
1005
|
+
if (url.startsWith("async ")) {
|
1006
|
+
url = url.slice(6);
|
1007
|
+
}
|
977
1008
|
if (url.startsWith("http:") || url.startsWith("https:")) {
|
978
1009
|
const urlObj = new URL(url);
|
979
1010
|
url = urlObj.pathname;
|
@@ -986,18 +1017,27 @@ function extractLocation(urlLike) {
|
|
986
1017
|
}
|
987
1018
|
function parseSingleFFOrSafariStack(raw) {
|
988
1019
|
let line = raw.trim();
|
989
|
-
if (SAFARI_NATIVE_CODE_REGEXP.test(line))
|
1020
|
+
if (SAFARI_NATIVE_CODE_REGEXP.test(line)) {
|
990
1021
|
return null;
|
991
|
-
|
992
|
-
|
993
|
-
|
1022
|
+
}
|
1023
|
+
if (line.includes(" > eval")) {
|
1024
|
+
line = line.replace(
|
1025
|
+
/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,
|
1026
|
+
":$1"
|
1027
|
+
);
|
1028
|
+
}
|
1029
|
+
if (!line.includes("@") && !line.includes(":")) {
|
994
1030
|
return null;
|
1031
|
+
}
|
995
1032
|
const functionNameRegex = /((.*".+"[^@]*)?[^@]*)(@)/;
|
996
1033
|
const matches = line.match(functionNameRegex);
|
997
1034
|
const functionName = matches && matches[1] ? matches[1] : void 0;
|
998
|
-
const [url, lineNumber, columnNumber] = extractLocation(
|
999
|
-
|
1035
|
+
const [url, lineNumber, columnNumber] = extractLocation(
|
1036
|
+
line.replace(functionNameRegex, "")
|
1037
|
+
);
|
1038
|
+
if (!url || !lineNumber || !columnNumber) {
|
1000
1039
|
return null;
|
1040
|
+
}
|
1001
1041
|
return {
|
1002
1042
|
file: url,
|
1003
1043
|
method: functionName || "",
|
@@ -1007,25 +1047,33 @@ function parseSingleFFOrSafariStack(raw) {
|
|
1007
1047
|
}
|
1008
1048
|
function parseSingleV8Stack(raw) {
|
1009
1049
|
let line = raw.trim();
|
1010
|
-
if (!CHROME_IE_STACK_REGEXP.test(line))
|
1050
|
+
if (!CHROME_IE_STACK_REGEXP.test(line)) {
|
1011
1051
|
return null;
|
1012
|
-
|
1052
|
+
}
|
1053
|
+
if (line.includes("(eval ")) {
|
1013
1054
|
line = line.replace(/eval code/g, "eval").replace(/(\(eval at [^()]*)|(,.*$)/g, "");
|
1055
|
+
}
|
1014
1056
|
let sanitizedLine = line.replace(/^\s+/, "").replace(/\(eval code/g, "(").replace(/^.*?\s+/, "");
|
1015
1057
|
const location = sanitizedLine.match(/ (\(.+\)$)/);
|
1016
1058
|
sanitizedLine = location ? sanitizedLine.replace(location[0], "") : sanitizedLine;
|
1017
|
-
const [url, lineNumber, columnNumber] = extractLocation(
|
1059
|
+
const [url, lineNumber, columnNumber] = extractLocation(
|
1060
|
+
location ? location[1] : sanitizedLine
|
1061
|
+
);
|
1018
1062
|
let method = location && sanitizedLine || "";
|
1019
1063
|
let file = url && ["eval", "<anonymous>"].includes(url) ? void 0 : url;
|
1020
|
-
if (!file || !lineNumber || !columnNumber)
|
1064
|
+
if (!file || !lineNumber || !columnNumber) {
|
1021
1065
|
return null;
|
1022
|
-
|
1066
|
+
}
|
1067
|
+
if (method.startsWith("async ")) {
|
1023
1068
|
method = method.slice(6);
|
1024
|
-
|
1069
|
+
}
|
1070
|
+
if (file.startsWith("file://")) {
|
1025
1071
|
file = file.slice(7);
|
1072
|
+
}
|
1026
1073
|
file = resolve$2(file);
|
1027
|
-
if (method)
|
1074
|
+
if (method) {
|
1028
1075
|
method = method.replace(/__vite_ssr_import_\d+__\./g, "");
|
1076
|
+
}
|
1029
1077
|
return {
|
1030
1078
|
method,
|
1031
1079
|
file,
|
@@ -1036,17 +1084,25 @@ function parseSingleV8Stack(raw) {
|
|
1036
1084
|
function parseStacktrace(stack, options = {}) {
|
1037
1085
|
const { ignoreStackEntries = stackIgnorePatterns } = options;
|
1038
1086
|
let stacks = !CHROME_IE_STACK_REGEXP.test(stack) ? parseFFOrSafariStackTrace(stack) : parseV8Stacktrace(stack);
|
1039
|
-
if (ignoreStackEntries.length)
|
1040
|
-
stacks = stacks.filter(
|
1087
|
+
if (ignoreStackEntries.length) {
|
1088
|
+
stacks = stacks.filter(
|
1089
|
+
(stack2) => !ignoreStackEntries.some((p) => stack2.file.match(p))
|
1090
|
+
);
|
1091
|
+
}
|
1041
1092
|
return stacks.map((stack2) => {
|
1042
1093
|
var _a;
|
1094
|
+
if (options.getFileName) {
|
1095
|
+
stack2.file = options.getFileName(stack2.file);
|
1096
|
+
}
|
1043
1097
|
const map = (_a = options.getSourceMap) == null ? void 0 : _a.call(options, stack2.file);
|
1044
|
-
if (!map || typeof map !== "object" || !map.version)
|
1098
|
+
if (!map || typeof map !== "object" || !map.version) {
|
1045
1099
|
return stack2;
|
1100
|
+
}
|
1046
1101
|
const traceMap = new TraceMap(map);
|
1047
1102
|
const { line, column } = originalPositionFor(traceMap, stack2);
|
1048
|
-
if (line != null && column != null)
|
1103
|
+
if (line != null && column != null) {
|
1049
1104
|
return { ...stack2, line, column };
|
1105
|
+
}
|
1050
1106
|
return stack2;
|
1051
1107
|
});
|
1052
1108
|
}
|
@@ -1057,14 +1113,19 @@ function parseV8Stacktrace(stack) {
|
|
1057
1113
|
return stack.split("\n").map((line) => parseSingleV8Stack(line)).filter(notNullish);
|
1058
1114
|
}
|
1059
1115
|
function parseErrorStacktrace(e, options = {}) {
|
1060
|
-
if (!e || isPrimitive(e))
|
1116
|
+
if (!e || isPrimitive(e)) {
|
1061
1117
|
return [];
|
1062
|
-
|
1118
|
+
}
|
1119
|
+
if (e.stacks) {
|
1063
1120
|
return e.stacks;
|
1121
|
+
}
|
1064
1122
|
const stackStr = e.stack || e.stackStr || "";
|
1065
1123
|
let stackFrames = parseStacktrace(stackStr, options);
|
1066
|
-
if (options.frameFilter)
|
1067
|
-
stackFrames = stackFrames.filter(
|
1124
|
+
if (options.frameFilter) {
|
1125
|
+
stackFrames = stackFrames.filter(
|
1126
|
+
(f) => options.frameFilter(e, f) !== false
|
1127
|
+
);
|
1128
|
+
}
|
1068
1129
|
e.stacks = stackFrames;
|
1069
1130
|
return stackFrames;
|
1070
1131
|
}
|
@@ -1072,29 +1133,34 @@ function parseErrorStacktrace(e, options = {}) {
|
|
1072
1133
|
async function saveInlineSnapshots(environment, snapshots) {
|
1073
1134
|
const MagicString = (await import('magic-string')).default;
|
1074
1135
|
const files = new Set(snapshots.map((i) => i.file));
|
1075
|
-
await Promise.all(
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1136
|
+
await Promise.all(
|
1137
|
+
Array.from(files).map(async (file) => {
|
1138
|
+
const snaps = snapshots.filter((i) => i.file === file);
|
1139
|
+
const code = await environment.readSnapshotFile(file);
|
1140
|
+
const s = new MagicString(code);
|
1141
|
+
for (const snap of snaps) {
|
1142
|
+
const index = positionToOffset(code, snap.line, snap.column);
|
1143
|
+
replaceInlineSnap(code, s, index, snap.snapshot);
|
1144
|
+
}
|
1145
|
+
const transformed = s.toString();
|
1146
|
+
if (transformed !== code) {
|
1147
|
+
await environment.saveSnapshotFile(file, transformed);
|
1148
|
+
}
|
1149
|
+
})
|
1150
|
+
);
|
1087
1151
|
}
|
1088
1152
|
const startObjectRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\s\S]*\*\/\s*|\/\/.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]))*\{/;
|
1089
1153
|
function replaceObjectSnap(code, s, index, newSnap) {
|
1090
1154
|
let _code = code.slice(index);
|
1091
1155
|
const startMatch = startObjectRegex.exec(_code);
|
1092
|
-
if (!startMatch)
|
1156
|
+
if (!startMatch) {
|
1093
1157
|
return false;
|
1158
|
+
}
|
1094
1159
|
_code = _code.slice(startMatch.index);
|
1095
1160
|
let callEnd = getCallLastIndex(_code);
|
1096
|
-
if (callEnd === null)
|
1161
|
+
if (callEnd === null) {
|
1097
1162
|
return false;
|
1163
|
+
}
|
1098
1164
|
callEnd += index + startMatch.index;
|
1099
1165
|
const shapeStart = index + startMatch.index + startMatch[0].length;
|
1100
1166
|
const shapeEnd = getObjectShapeEndIndex(code, shapeStart);
|
@@ -1111,10 +1177,11 @@ function getObjectShapeEndIndex(code, index) {
|
|
1111
1177
|
let endBraces = 0;
|
1112
1178
|
while (startBraces !== endBraces && index < code.length) {
|
1113
1179
|
const s = code[index++];
|
1114
|
-
if (s === "{")
|
1180
|
+
if (s === "{") {
|
1115
1181
|
startBraces++;
|
1116
|
-
else if (s === "}")
|
1182
|
+
} else if (s === "}") {
|
1117
1183
|
endBraces++;
|
1184
|
+
}
|
1118
1185
|
}
|
1119
1186
|
return index;
|
1120
1187
|
}
|
@@ -1126,19 +1193,45 @@ function prepareSnapString(snap, source, index) {
|
|
1126
1193
|
const lines = snap.trim().replace(/\\/g, "\\\\").split(/\n/g);
|
1127
1194
|
const isOneline = lines.length <= 1;
|
1128
1195
|
const quote = "`";
|
1129
|
-
if (isOneline)
|
1196
|
+
if (isOneline) {
|
1130
1197
|
return `${quote}${lines.join("\n").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")}${quote}`;
|
1198
|
+
}
|
1131
1199
|
return `${quote}
|
1132
1200
|
${lines.map((i) => i ? indentNext + i : "").join("\n").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")}
|
1133
1201
|
${indent}${quote}`;
|
1134
1202
|
}
|
1203
|
+
const toMatchInlineName = "toMatchInlineSnapshot";
|
1204
|
+
const toThrowErrorMatchingInlineName = "toThrowErrorMatchingInlineSnapshot";
|
1205
|
+
function getCodeStartingAtIndex(code, index) {
|
1206
|
+
const indexInline = index - toMatchInlineName.length;
|
1207
|
+
if (code.slice(indexInline, index) === toMatchInlineName) {
|
1208
|
+
return {
|
1209
|
+
code: code.slice(indexInline),
|
1210
|
+
index: indexInline
|
1211
|
+
};
|
1212
|
+
}
|
1213
|
+
const indexThrowInline = index - toThrowErrorMatchingInlineName.length;
|
1214
|
+
if (code.slice(index - indexThrowInline, index) === toThrowErrorMatchingInlineName) {
|
1215
|
+
return {
|
1216
|
+
code: code.slice(index - indexThrowInline),
|
1217
|
+
index: index - indexThrowInline
|
1218
|
+
};
|
1219
|
+
}
|
1220
|
+
return {
|
1221
|
+
code: code.slice(index),
|
1222
|
+
index
|
1223
|
+
};
|
1224
|
+
}
|
1135
1225
|
const startRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\s\S]*\*\/\s*|\/\/.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]))*[\w$]*(['"`)])/;
|
1136
|
-
function replaceInlineSnap(code, s,
|
1137
|
-
const codeStartingAtIndex = code
|
1226
|
+
function replaceInlineSnap(code, s, currentIndex, newSnap) {
|
1227
|
+
const { code: codeStartingAtIndex, index } = getCodeStartingAtIndex(code, currentIndex);
|
1138
1228
|
const startMatch = startRegex.exec(codeStartingAtIndex);
|
1139
|
-
const firstKeywordMatch = /toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot/.exec(
|
1140
|
-
|
1229
|
+
const firstKeywordMatch = /toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot/.exec(
|
1230
|
+
codeStartingAtIndex
|
1231
|
+
);
|
1232
|
+
if (!startMatch || startMatch.index !== (firstKeywordMatch == null ? void 0 : firstKeywordMatch.index)) {
|
1141
1233
|
return replaceObjectSnap(code, s, index, newSnap);
|
1234
|
+
}
|
1142
1235
|
const quote = startMatch[1];
|
1143
1236
|
const startIndex = index + startMatch.index + startMatch[0].length;
|
1144
1237
|
const snapString = prepareSnapString(newSnap, code, index);
|
@@ -1148,8 +1241,9 @@ function replaceInlineSnap(code, s, index, newSnap) {
|
|
1148
1241
|
}
|
1149
1242
|
const quoteEndRE = new RegExp(`(?:^|[^\\\\])${quote}`);
|
1150
1243
|
const endMatch = quoteEndRE.exec(code.slice(startIndex));
|
1151
|
-
if (!endMatch)
|
1244
|
+
if (!endMatch) {
|
1152
1245
|
return false;
|
1246
|
+
}
|
1153
1247
|
const endIndex = startIndex + endMatch.index + endMatch[0].length;
|
1154
1248
|
s.overwrite(startIndex - 1, endIndex, snapString);
|
1155
1249
|
return true;
|
@@ -1182,20 +1276,20 @@ function stripSnapshotIndentation(inlineSnapshot) {
|
|
1182
1276
|
}
|
1183
1277
|
|
1184
1278
|
async function saveRawSnapshots(environment, snapshots) {
|
1185
|
-
await Promise.all(
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1279
|
+
await Promise.all(
|
1280
|
+
snapshots.map(async (snap) => {
|
1281
|
+
if (!snap.readonly) {
|
1282
|
+
await environment.saveSnapshotFile(snap.file, snap.snapshot);
|
1283
|
+
}
|
1284
|
+
})
|
1285
|
+
);
|
1189
1286
|
}
|
1190
1287
|
|
1191
1288
|
class SnapshotState {
|
1192
1289
|
constructor(testFilePath, snapshotPath, snapshotContent, options) {
|
1193
1290
|
this.testFilePath = testFilePath;
|
1194
1291
|
this.snapshotPath = snapshotPath;
|
1195
|
-
const { data, dirty } = getSnapshotData(
|
1196
|
-
snapshotContent,
|
1197
|
-
options
|
1198
|
-
);
|
1292
|
+
const { data, dirty } = getSnapshotData(snapshotContent, options);
|
1199
1293
|
this._fileExists = snapshotContent != null;
|
1200
1294
|
this._initialData = data;
|
1201
1295
|
this._snapshotData = data;
|
@@ -1234,8 +1328,12 @@ class SnapshotState {
|
|
1234
1328
|
unmatched;
|
1235
1329
|
updated;
|
1236
1330
|
static async create(testFilePath, options) {
|
1237
|
-
const snapshotPath = await options.snapshotEnvironment.resolvePath(
|
1238
|
-
|
1331
|
+
const snapshotPath = await options.snapshotEnvironment.resolvePath(
|
1332
|
+
testFilePath
|
1333
|
+
);
|
1334
|
+
const content = await options.snapshotEnvironment.readSnapshotFile(
|
1335
|
+
snapshotPath
|
1336
|
+
);
|
1239
1337
|
return new SnapshotState(testFilePath, snapshotPath, content, options);
|
1240
1338
|
}
|
1241
1339
|
get environment() {
|
@@ -1243,28 +1341,42 @@ class SnapshotState {
|
|
1243
1341
|
}
|
1244
1342
|
markSnapshotsAsCheckedForTest(testName) {
|
1245
1343
|
this._uncheckedKeys.forEach((uncheckedKey) => {
|
1246
|
-
if (keyToTestName(uncheckedKey) === testName)
|
1344
|
+
if (keyToTestName(uncheckedKey) === testName) {
|
1247
1345
|
this._uncheckedKeys.delete(uncheckedKey);
|
1346
|
+
}
|
1248
1347
|
});
|
1249
1348
|
}
|
1250
1349
|
_inferInlineSnapshotStack(stacks) {
|
1251
|
-
const promiseIndex = stacks.findIndex(
|
1252
|
-
|
1350
|
+
const promiseIndex = stacks.findIndex(
|
1351
|
+
(i) => i.method.match(/__VITEST_(RESOLVES|REJECTS)__/)
|
1352
|
+
);
|
1353
|
+
if (promiseIndex !== -1) {
|
1253
1354
|
return stacks[promiseIndex + 3];
|
1254
|
-
|
1355
|
+
}
|
1356
|
+
const stackIndex = stacks.findIndex(
|
1357
|
+
(i) => i.method.includes("__INLINE_SNAPSHOT__")
|
1358
|
+
);
|
1255
1359
|
return stackIndex !== -1 ? stacks[stackIndex + 2] : null;
|
1256
1360
|
}
|
1257
1361
|
_addSnapshot(key, receivedSerialized, options) {
|
1362
|
+
var _a, _b;
|
1258
1363
|
this._dirty = true;
|
1259
1364
|
if (options.isInline) {
|
1260
|
-
const
|
1261
|
-
const
|
1262
|
-
|
1365
|
+
const error = options.error || new Error("snapshot");
|
1366
|
+
const stacks = parseErrorStacktrace(
|
1367
|
+
error,
|
1368
|
+
{ ignoreStackEntries: [] }
|
1369
|
+
);
|
1370
|
+
const _stack = this._inferInlineSnapshotStack(stacks);
|
1371
|
+
if (!_stack) {
|
1263
1372
|
throw new Error(
|
1264
1373
|
`@vitest/snapshot: Couldn't infer stack frame for inline snapshot.
|
1265
|
-
${JSON.stringify(
|
1374
|
+
${JSON.stringify(
|
1375
|
+
stacks
|
1376
|
+
)}`
|
1266
1377
|
);
|
1267
1378
|
}
|
1379
|
+
const stack = ((_b = (_a = this.environment).processStackTrace) == null ? void 0 : _b.call(_a, _stack)) || _stack;
|
1268
1380
|
stack.column--;
|
1269
1381
|
this._inlineSnapshots.push({
|
1270
1382
|
snapshot: receivedSerialized,
|
@@ -1299,13 +1411,19 @@ ${JSON.stringify(stacks)}`
|
|
1299
1411
|
};
|
1300
1412
|
if ((this._dirty || this._uncheckedKeys.size) && !isEmpty) {
|
1301
1413
|
if (hasExternalSnapshots) {
|
1302
|
-
await saveSnapshotFile(
|
1414
|
+
await saveSnapshotFile(
|
1415
|
+
this._environment,
|
1416
|
+
this._snapshotData,
|
1417
|
+
this.snapshotPath
|
1418
|
+
);
|
1303
1419
|
this._fileExists = true;
|
1304
1420
|
}
|
1305
|
-
if (hasInlineSnapshots)
|
1421
|
+
if (hasInlineSnapshots) {
|
1306
1422
|
await saveInlineSnapshots(this._environment, this._inlineSnapshots);
|
1307
|
-
|
1423
|
+
}
|
1424
|
+
if (hasRawSnapshots) {
|
1308
1425
|
await saveRawSnapshots(this._environment, this._rawSnapshots);
|
1426
|
+
}
|
1309
1427
|
status.saved = true;
|
1310
1428
|
} else if (!hasExternalSnapshots && this._fileExists) {
|
1311
1429
|
if (this._updateSnapshot === "all") {
|
@@ -1340,16 +1458,20 @@ ${JSON.stringify(stacks)}`
|
|
1340
1458
|
}) {
|
1341
1459
|
this._counters.set(testName, (this._counters.get(testName) || 0) + 1);
|
1342
1460
|
const count = Number(this._counters.get(testName));
|
1343
|
-
if (!key)
|
1461
|
+
if (!key) {
|
1344
1462
|
key = testNameToKey(testName, count);
|
1345
|
-
|
1463
|
+
}
|
1464
|
+
if (!(isInline && this._snapshotData[key] !== void 0)) {
|
1346
1465
|
this._uncheckedKeys.delete(key);
|
1466
|
+
}
|
1347
1467
|
let receivedSerialized = rawSnapshot && typeof received === "string" ? received : serialize(received, void 0, this._snapshotFormat);
|
1348
|
-
if (!rawSnapshot)
|
1468
|
+
if (!rawSnapshot) {
|
1349
1469
|
receivedSerialized = addExtraLineBreaks(receivedSerialized);
|
1470
|
+
}
|
1350
1471
|
if (rawSnapshot) {
|
1351
|
-
if (rawSnapshot.content && rawSnapshot.content.match(/\r\n/) && !receivedSerialized.match(/\r\n/))
|
1472
|
+
if (rawSnapshot.content && rawSnapshot.content.match(/\r\n/) && !receivedSerialized.match(/\r\n/)) {
|
1352
1473
|
rawSnapshot.content = normalizeNewlines(rawSnapshot.content);
|
1474
|
+
}
|
1353
1475
|
}
|
1354
1476
|
const expected = isInline ? inlineSnapshot : rawSnapshot ? rawSnapshot.content : this._snapshotData[key];
|
1355
1477
|
const expectedTrimmed = prepareExpected(expected);
|
@@ -1362,16 +1484,25 @@ ${JSON.stringify(stacks)}`
|
|
1362
1484
|
if (hasSnapshot && this._updateSnapshot === "all" || (!hasSnapshot || !snapshotIsPersisted) && (this._updateSnapshot === "new" || this._updateSnapshot === "all")) {
|
1363
1485
|
if (this._updateSnapshot === "all") {
|
1364
1486
|
if (!pass) {
|
1365
|
-
if (hasSnapshot)
|
1487
|
+
if (hasSnapshot) {
|
1366
1488
|
this.updated++;
|
1367
|
-
else
|
1489
|
+
} else {
|
1368
1490
|
this.added++;
|
1369
|
-
|
1491
|
+
}
|
1492
|
+
this._addSnapshot(key, receivedSerialized, {
|
1493
|
+
error,
|
1494
|
+
isInline,
|
1495
|
+
rawSnapshot
|
1496
|
+
});
|
1370
1497
|
} else {
|
1371
1498
|
this.matched++;
|
1372
1499
|
}
|
1373
1500
|
} else {
|
1374
|
-
this._addSnapshot(key, receivedSerialized, {
|
1501
|
+
this._addSnapshot(key, receivedSerialized, {
|
1502
|
+
error,
|
1503
|
+
isInline,
|
1504
|
+
rawSnapshot
|
1505
|
+
});
|
1375
1506
|
this.added++;
|
1376
1507
|
}
|
1377
1508
|
return {
|
@@ -1416,8 +1547,9 @@ ${JSON.stringify(stacks)}`
|
|
1416
1547
|
};
|
1417
1548
|
const uncheckedCount = this.getUncheckedCount();
|
1418
1549
|
const uncheckedKeys = this.getUncheckedKeys();
|
1419
|
-
if (uncheckedCount)
|
1550
|
+
if (uncheckedCount) {
|
1420
1551
|
this.removeUncheckedKeys();
|
1552
|
+
}
|
1421
1553
|
const status = await this.save();
|
1422
1554
|
snapshot.fileDeleted = status.deleted;
|
1423
1555
|
snapshot.added = this.added;
|
@@ -1464,10 +1596,7 @@ class SnapshotClient {
|
|
1464
1596
|
if (!this.getSnapshotState(filepath)) {
|
1465
1597
|
this.snapshotStateMap.set(
|
1466
1598
|
filepath,
|
1467
|
-
await SnapshotState.create(
|
1468
|
-
filepath,
|
1469
|
-
options
|
1470
|
-
)
|
1599
|
+
await SnapshotState.create(filepath, options)
|
1471
1600
|
);
|
1472
1601
|
}
|
1473
1602
|
this.snapshotState = this.getSnapshotState(filepath);
|
@@ -1498,26 +1627,33 @@ class SnapshotClient {
|
|
1498
1627
|
rawSnapshot
|
1499
1628
|
} = options;
|
1500
1629
|
let { received } = options;
|
1501
|
-
if (!filepath)
|
1630
|
+
if (!filepath) {
|
1502
1631
|
throw new Error("Snapshot cannot be used outside of test");
|
1632
|
+
}
|
1503
1633
|
if (typeof properties === "object") {
|
1504
|
-
if (typeof received !== "object" || !received)
|
1505
|
-
throw new Error(
|
1634
|
+
if (typeof received !== "object" || !received) {
|
1635
|
+
throw new Error(
|
1636
|
+
"Received value must be an object when the matcher has properties"
|
1637
|
+
);
|
1638
|
+
}
|
1506
1639
|
try {
|
1507
1640
|
const pass2 = ((_b = (_a = this.options).isEqual) == null ? void 0 : _b.call(_a, received, properties)) ?? false;
|
1508
|
-
if (!pass2)
|
1509
|
-
throw createMismatchError(
|
1510
|
-
|
1641
|
+
if (!pass2) {
|
1642
|
+
throw createMismatchError(
|
1643
|
+
"Snapshot properties mismatched",
|
1644
|
+
(_c = this.snapshotState) == null ? void 0 : _c.expand,
|
1645
|
+
received,
|
1646
|
+
properties
|
1647
|
+
);
|
1648
|
+
} else {
|
1511
1649
|
received = deepMergeSnapshot(received, properties);
|
1650
|
+
}
|
1512
1651
|
} catch (err) {
|
1513
1652
|
err.message = errorMessage || "Snapshot mismatched";
|
1514
1653
|
throw err;
|
1515
1654
|
}
|
1516
1655
|
}
|
1517
|
-
const testName = [
|
1518
|
-
name,
|
1519
|
-
...message ? [message] : []
|
1520
|
-
].join(" > ");
|
1656
|
+
const testName = [name, ...message ? [message] : []].join(" > ");
|
1521
1657
|
const snapshotState = this.getSnapshotState(filepath);
|
1522
1658
|
const { actual, expected, key, pass } = snapshotState.match({
|
1523
1659
|
testName,
|
@@ -1527,29 +1663,38 @@ class SnapshotClient {
|
|
1527
1663
|
inlineSnapshot,
|
1528
1664
|
rawSnapshot
|
1529
1665
|
});
|
1530
|
-
if (!pass)
|
1531
|
-
throw createMismatchError(
|
1666
|
+
if (!pass) {
|
1667
|
+
throw createMismatchError(
|
1668
|
+
`Snapshot \`${key || "unknown"}\` mismatched`,
|
1669
|
+
(_d = this.snapshotState) == null ? void 0 : _d.expand,
|
1670
|
+
actual == null ? void 0 : actual.trim(),
|
1671
|
+
expected == null ? void 0 : expected.trim()
|
1672
|
+
);
|
1673
|
+
}
|
1532
1674
|
}
|
1533
1675
|
async assertRaw(options) {
|
1534
|
-
if (!options.rawSnapshot)
|
1676
|
+
if (!options.rawSnapshot) {
|
1535
1677
|
throw new Error("Raw snapshot is required");
|
1536
|
-
|
1537
|
-
|
1538
|
-
rawSnapshot
|
1539
|
-
} = options;
|
1678
|
+
}
|
1679
|
+
const { filepath = this.filepath, rawSnapshot } = options;
|
1540
1680
|
if (rawSnapshot.content == null) {
|
1541
|
-
if (!filepath)
|
1681
|
+
if (!filepath) {
|
1542
1682
|
throw new Error("Snapshot cannot be used outside of test");
|
1683
|
+
}
|
1543
1684
|
const snapshotState = this.getSnapshotState(filepath);
|
1544
1685
|
options.filepath || (options.filepath = filepath);
|
1545
|
-
rawSnapshot.file = await snapshotState.environment.resolveRawPath(
|
1546
|
-
|
1686
|
+
rawSnapshot.file = await snapshotState.environment.resolveRawPath(
|
1687
|
+
filepath,
|
1688
|
+
rawSnapshot.file
|
1689
|
+
);
|
1690
|
+
rawSnapshot.content = await snapshotState.environment.readSnapshotFile(rawSnapshot.file) ?? void 0;
|
1547
1691
|
}
|
1548
1692
|
return this.assert(options);
|
1549
1693
|
}
|
1550
1694
|
async finishCurrentRun() {
|
1551
|
-
if (!this.snapshotState)
|
1695
|
+
if (!this.snapshotState) {
|
1552
1696
|
return null;
|
1697
|
+
}
|
1553
1698
|
const result = await this.snapshotState.pack();
|
1554
1699
|
this.snapshotState = void 0;
|
1555
1700
|
return result;
|
package/dist/manager.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import { S as SnapshotStateOptions, f as SnapshotSummary, b as SnapshotResult } from './index-
|
1
|
+
import { S as SnapshotStateOptions, f as SnapshotSummary, b as SnapshotResult } from './index-reKGmLsM.js';
|
2
2
|
import 'pretty-format';
|
3
|
-
import './environment-
|
3
|
+
import './environment-Ddx0EDtY.js';
|
4
4
|
|
5
5
|
declare class SnapshotManager {
|
6
6
|
options: Omit<SnapshotStateOptions, 'snapshotEnvironment'>;
|
package/dist/manager.js
CHANGED
@@ -16,10 +16,7 @@ class SnapshotManager {
|
|
16
16
|
resolvePath(testPath) {
|
17
17
|
const resolver = this.options.resolveSnapshotPath || (() => {
|
18
18
|
return join(
|
19
|
-
join(
|
20
|
-
dirname(testPath),
|
21
|
-
"__snapshots__"
|
22
|
-
),
|
19
|
+
join(dirname(testPath), "__snapshots__"),
|
23
20
|
`${basename(testPath)}${this.extension}`
|
24
21
|
);
|
25
22
|
});
|
@@ -50,14 +47,18 @@ function emptySummary(options) {
|
|
50
47
|
return summary;
|
51
48
|
}
|
52
49
|
function addSnapshotResult(summary, result) {
|
53
|
-
if (result.added)
|
50
|
+
if (result.added) {
|
54
51
|
summary.filesAdded++;
|
55
|
-
|
52
|
+
}
|
53
|
+
if (result.fileDeleted) {
|
56
54
|
summary.filesRemoved++;
|
57
|
-
|
55
|
+
}
|
56
|
+
if (result.unmatched) {
|
58
57
|
summary.filesUnmatched++;
|
59
|
-
|
58
|
+
}
|
59
|
+
if (result.updated) {
|
60
60
|
summary.filesUpdated++;
|
61
|
+
}
|
61
62
|
summary.added += result.added;
|
62
63
|
summary.matched += result.matched;
|
63
64
|
summary.unchecked += result.unchecked;
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vitest/snapshot",
|
3
3
|
"type": "module",
|
4
|
-
"version": "2.0.0
|
4
|
+
"version": "2.0.0",
|
5
5
|
"description": "Vitest snapshot manager",
|
6
6
|
"license": "MIT",
|
7
7
|
"funding": "https://opencollective.com/vitest",
|
@@ -45,7 +45,7 @@
|
|
45
45
|
"devDependencies": {
|
46
46
|
"@types/natural-compare": "^1.4.3",
|
47
47
|
"natural-compare": "^1.4.0",
|
48
|
-
"@vitest/utils": "2.0.0
|
48
|
+
"@vitest/utils": "2.0.0"
|
49
49
|
},
|
50
50
|
"scripts": {
|
51
51
|
"build": "rimraf dist && rollup -c",
|