@vitest/snapshot 4.1.2 → 4.1.4
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/dist/index.d.ts +26 -83
- package/dist/index.js +563 -284
- package/dist/manager.d.ts +2 -2
- package/dist/rawSnapshot.d-D_X3-62x.d.ts +205 -0
- package/package.json +3 -3
- package/dist/rawSnapshot.d-U2kJUxDr.d.ts +0 -61
package/dist/index.d.ts
CHANGED
|
@@ -1,86 +1,8 @@
|
|
|
1
|
-
import { S as
|
|
2
|
-
export { c as
|
|
3
|
-
import { ParsedStack } from '@vitest/utils';
|
|
4
|
-
import { S as SnapshotEnvironment } from './environment.d-DOJxxZV9.js';
|
|
1
|
+
import { S as SnapshotState, a as SnapshotStateOptions, b as SnapshotResult, R as RawSnapshotInfo, D as DomainSnapshotAdapter } from './rawSnapshot.d-D_X3-62x.js';
|
|
2
|
+
export { c as DomainMatchResult, d as SnapshotData, e as SnapshotMatchOptions, f as SnapshotSerializer, g as SnapshotSummary, h as SnapshotUpdateState, U as UncheckedSnapshot } from './rawSnapshot.d-D_X3-62x.js';
|
|
5
3
|
import { Plugin, Plugins } from '@vitest/pretty-format';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
|
9
|
-
*
|
|
10
|
-
* This source code is licensed under the MIT license found in the
|
|
11
|
-
* LICENSE file in the root directory of this source tree.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
declare class DefaultMap<
|
|
15
|
-
K,
|
|
16
|
-
V
|
|
17
|
-
> extends Map<K, V> {
|
|
18
|
-
private defaultFn;
|
|
19
|
-
constructor(defaultFn: (key: K) => V, entries?: Iterable<readonly [K, V]>);
|
|
20
|
-
get(key: K): V;
|
|
21
|
-
}
|
|
22
|
-
declare class CounterMap<K> extends DefaultMap<K, number> {
|
|
23
|
-
constructor();
|
|
24
|
-
_total: number | undefined;
|
|
25
|
-
valueOf(): number;
|
|
26
|
-
increment(key: K): void;
|
|
27
|
-
total(): number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SnapshotReturnOptions {
|
|
31
|
-
actual: string;
|
|
32
|
-
count: number;
|
|
33
|
-
expected?: string;
|
|
34
|
-
key: string;
|
|
35
|
-
pass: boolean;
|
|
36
|
-
}
|
|
37
|
-
interface SaveStatus {
|
|
38
|
-
deleted: boolean;
|
|
39
|
-
saved: boolean;
|
|
40
|
-
}
|
|
41
|
-
declare class SnapshotState {
|
|
42
|
-
testFilePath: string;
|
|
43
|
-
snapshotPath: string;
|
|
44
|
-
private _counters;
|
|
45
|
-
private _dirty;
|
|
46
|
-
private _updateSnapshot;
|
|
47
|
-
private _snapshotData;
|
|
48
|
-
private _initialData;
|
|
49
|
-
private _inlineSnapshots;
|
|
50
|
-
private _inlineSnapshotStacks;
|
|
51
|
-
private _testIdToKeys;
|
|
52
|
-
private _rawSnapshots;
|
|
53
|
-
private _uncheckedKeys;
|
|
54
|
-
private _snapshotFormat;
|
|
55
|
-
private _environment;
|
|
56
|
-
private _fileExists;
|
|
57
|
-
expand: boolean;
|
|
58
|
-
private _added;
|
|
59
|
-
private _matched;
|
|
60
|
-
private _unmatched;
|
|
61
|
-
private _updated;
|
|
62
|
-
get added(): CounterMap<string>;
|
|
63
|
-
set added(value: number);
|
|
64
|
-
get matched(): CounterMap<string>;
|
|
65
|
-
set matched(value: number);
|
|
66
|
-
get unmatched(): CounterMap<string>;
|
|
67
|
-
set unmatched(value: number);
|
|
68
|
-
get updated(): CounterMap<string>;
|
|
69
|
-
set updated(value: number);
|
|
70
|
-
private constructor();
|
|
71
|
-
static create(testFilePath: string, options: SnapshotStateOptions): Promise<SnapshotState>;
|
|
72
|
-
get environment(): SnapshotEnvironment;
|
|
73
|
-
markSnapshotsAsCheckedForTest(testName: string): void;
|
|
74
|
-
clearTest(testId: string): void;
|
|
75
|
-
protected _inferInlineSnapshotStack(stacks: ParsedStack[]): ParsedStack | null;
|
|
76
|
-
private _addSnapshot;
|
|
77
|
-
save(): Promise<SaveStatus>;
|
|
78
|
-
getUncheckedCount(): number;
|
|
79
|
-
getUncheckedKeys(): Array<string>;
|
|
80
|
-
removeUncheckedKeys(): void;
|
|
81
|
-
match({ testId, testName, received, key, inlineSnapshot, isInline, error, rawSnapshot }: SnapshotMatchOptions): SnapshotReturnOptions;
|
|
82
|
-
pack(): Promise<SnapshotResult>;
|
|
83
|
-
}
|
|
4
|
+
export { S as SnapshotEnvironment } from './environment.d-DOJxxZV9.js';
|
|
5
|
+
import '@vitest/utils';
|
|
84
6
|
|
|
85
7
|
interface AssertOptions {
|
|
86
8
|
received: unknown;
|
|
@@ -98,6 +20,23 @@ interface AssertOptions {
|
|
|
98
20
|
error?: Error;
|
|
99
21
|
errorMessage?: string;
|
|
100
22
|
rawSnapshot?: RawSnapshotInfo;
|
|
23
|
+
assertionName?: string;
|
|
24
|
+
}
|
|
25
|
+
interface AssertDomainOptions extends Omit<AssertOptions, "received"> {
|
|
26
|
+
received: unknown;
|
|
27
|
+
adapter: DomainSnapshotAdapter<any, any>;
|
|
28
|
+
}
|
|
29
|
+
interface AssertDomainPollOptions extends Omit<AssertDomainOptions, "received"> {
|
|
30
|
+
poll: () => Promise<unknown> | unknown;
|
|
31
|
+
timeout?: number;
|
|
32
|
+
interval?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Same shape as expect.extend custom matcher result (SyncExpectationResult from @vitest/expect) */
|
|
35
|
+
interface MatchResult {
|
|
36
|
+
pass: boolean;
|
|
37
|
+
message: () => string;
|
|
38
|
+
actual?: unknown;
|
|
39
|
+
expected?: unknown;
|
|
101
40
|
}
|
|
102
41
|
interface SnapshotClientOptions {
|
|
103
42
|
isEqual?: (received: unknown, expected: unknown) => boolean;
|
|
@@ -111,7 +50,10 @@ declare class SnapshotClient {
|
|
|
111
50
|
skipTest(filepath: string, testName: string): void;
|
|
112
51
|
clearTest(filepath: string, testId: string): void;
|
|
113
52
|
getSnapshotState(filepath: string): SnapshotState;
|
|
53
|
+
match(options: AssertOptions): MatchResult;
|
|
114
54
|
assert(options: AssertOptions): void;
|
|
55
|
+
matchDomain(options: AssertDomainOptions): MatchResult;
|
|
56
|
+
pollMatchDomain(options: AssertDomainPollOptions): Promise<MatchResult>;
|
|
115
57
|
assertRaw(options: AssertOptions): Promise<void>;
|
|
116
58
|
clear(): void;
|
|
117
59
|
}
|
|
@@ -128,4 +70,5 @@ declare function stripSnapshotIndentation(inlineSnapshot: string): string;
|
|
|
128
70
|
declare function addSerializer(plugin: Plugin): void;
|
|
129
71
|
declare function getSerializers(): Plugins;
|
|
130
72
|
|
|
131
|
-
export {
|
|
73
|
+
export { DomainSnapshotAdapter, SnapshotClient, SnapshotResult, SnapshotState, SnapshotStateOptions, addSerializer, getSerializers, stripSnapshotIndentation };
|
|
74
|
+
export type { MatchResult };
|
package/dist/index.js
CHANGED
|
@@ -1,171 +1,8 @@
|
|
|
1
1
|
import { parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
2
|
-
import {
|
|
2
|
+
import { isObject, getCallLastIndex } from '@vitest/utils/helpers';
|
|
3
3
|
import { positionToOffset, offsetToLineNumber, lineSplitRE } from '@vitest/utils/offset';
|
|
4
4
|
import { plugins, format } from '@vitest/pretty-format';
|
|
5
5
|
|
|
6
|
-
async function saveInlineSnapshots(environment, snapshots) {
|
|
7
|
-
const MagicString = (await import('magic-string')).default;
|
|
8
|
-
const files = new Set(snapshots.map((i) => i.file));
|
|
9
|
-
await Promise.all(Array.from(files).map(async (file) => {
|
|
10
|
-
const snaps = snapshots.filter((i) => i.file === file);
|
|
11
|
-
const code = await environment.readSnapshotFile(file);
|
|
12
|
-
if (code == null) {
|
|
13
|
-
throw new Error(`cannot read ${file} when saving inline snapshot`);
|
|
14
|
-
}
|
|
15
|
-
const s = new MagicString(code);
|
|
16
|
-
for (const snap of snaps) {
|
|
17
|
-
const index = positionToOffset(code, snap.line, snap.column);
|
|
18
|
-
replaceInlineSnap(code, s, index, snap.snapshot);
|
|
19
|
-
}
|
|
20
|
-
const transformed = s.toString();
|
|
21
|
-
if (transformed !== code) {
|
|
22
|
-
await environment.saveSnapshotFile(file, transformed);
|
|
23
|
-
}
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
26
|
-
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]))*\{/;
|
|
27
|
-
function replaceObjectSnap(code, s, index, newSnap) {
|
|
28
|
-
let _code = code.slice(index);
|
|
29
|
-
const startMatch = startObjectRegex.exec(_code);
|
|
30
|
-
if (!startMatch) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
_code = _code.slice(startMatch.index);
|
|
34
|
-
let callEnd = getCallLastIndex(_code);
|
|
35
|
-
if (callEnd === null) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
callEnd += index + startMatch.index;
|
|
39
|
-
const shapeStart = index + startMatch.index + startMatch[0].length;
|
|
40
|
-
const shapeEnd = getObjectShapeEndIndex(code, shapeStart);
|
|
41
|
-
const snap = `, ${prepareSnapString(newSnap, code, index)}`;
|
|
42
|
-
if (shapeEnd === callEnd) {
|
|
43
|
-
// toMatchInlineSnapshot({ foo: expect.any(String) })
|
|
44
|
-
s.appendLeft(callEnd, snap);
|
|
45
|
-
} else {
|
|
46
|
-
// toMatchInlineSnapshot({ foo: expect.any(String) }, ``)
|
|
47
|
-
s.overwrite(shapeEnd, callEnd, snap);
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
function getObjectShapeEndIndex(code, index) {
|
|
52
|
-
let startBraces = 1;
|
|
53
|
-
let endBraces = 0;
|
|
54
|
-
while (startBraces !== endBraces && index < code.length) {
|
|
55
|
-
const s = code[index++];
|
|
56
|
-
if (s === "{") {
|
|
57
|
-
startBraces++;
|
|
58
|
-
} else if (s === "}") {
|
|
59
|
-
endBraces++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return index;
|
|
63
|
-
}
|
|
64
|
-
function prepareSnapString(snap, source, index) {
|
|
65
|
-
const lineNumber = offsetToLineNumber(source, index);
|
|
66
|
-
const line = source.split(lineSplitRE)[lineNumber - 1];
|
|
67
|
-
const indent = line.match(/^\s*/)[0] || "";
|
|
68
|
-
const indentNext = indent.includes(" ") ? `${indent}\t` : `${indent} `;
|
|
69
|
-
const lines = snap.trim().replace(/\\/g, "\\\\").split(/\n/g);
|
|
70
|
-
const isOneline = lines.length <= 1;
|
|
71
|
-
const quote = "`";
|
|
72
|
-
if (isOneline) {
|
|
73
|
-
return `${quote}${lines.join("\n").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")}${quote}`;
|
|
74
|
-
}
|
|
75
|
-
return `${quote}\n${lines.map((i) => i ? indentNext + i : "").join("\n").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")}\n${indent}${quote}`;
|
|
76
|
-
}
|
|
77
|
-
const toMatchInlineName = "toMatchInlineSnapshot";
|
|
78
|
-
const toThrowErrorMatchingInlineName = "toThrowErrorMatchingInlineSnapshot";
|
|
79
|
-
// on webkit, the line number is at the end of the method, not at the start
|
|
80
|
-
function getCodeStartingAtIndex(code, index) {
|
|
81
|
-
const indexInline = index - toMatchInlineName.length;
|
|
82
|
-
if (code.slice(indexInline, index) === toMatchInlineName) {
|
|
83
|
-
return {
|
|
84
|
-
code: code.slice(indexInline),
|
|
85
|
-
index: indexInline
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
const indexThrowInline = index - toThrowErrorMatchingInlineName.length;
|
|
89
|
-
if (code.slice(index - indexThrowInline, index) === toThrowErrorMatchingInlineName) {
|
|
90
|
-
return {
|
|
91
|
-
code: code.slice(index - indexThrowInline),
|
|
92
|
-
index: index - indexThrowInline
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
code: code.slice(index),
|
|
97
|
-
index
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
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$]*(['"`)])/;
|
|
101
|
-
function replaceInlineSnap(code, s, currentIndex, newSnap) {
|
|
102
|
-
const { code: codeStartingAtIndex, index } = getCodeStartingAtIndex(code, currentIndex);
|
|
103
|
-
const startMatch = startRegex.exec(codeStartingAtIndex);
|
|
104
|
-
const firstKeywordMatch = /toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot/.exec(codeStartingAtIndex);
|
|
105
|
-
if (!startMatch || startMatch.index !== firstKeywordMatch?.index) {
|
|
106
|
-
return replaceObjectSnap(code, s, index, newSnap);
|
|
107
|
-
}
|
|
108
|
-
const quote = startMatch[1];
|
|
109
|
-
const startIndex = index + startMatch.index + startMatch[0].length;
|
|
110
|
-
const snapString = prepareSnapString(newSnap, code, index);
|
|
111
|
-
if (quote === ")") {
|
|
112
|
-
s.appendRight(startIndex - 1, snapString);
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
const quoteEndRE = new RegExp(`(?:^|[^\\\\])${quote}`);
|
|
116
|
-
const endMatch = quoteEndRE.exec(code.slice(startIndex));
|
|
117
|
-
if (!endMatch) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
const endIndex = startIndex + endMatch.index + endMatch[0].length;
|
|
121
|
-
s.overwrite(startIndex - 1, endIndex, snapString);
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
const INDENTATION_REGEX = /^([^\S\n]*)\S/m;
|
|
125
|
-
function stripSnapshotIndentation(inlineSnapshot) {
|
|
126
|
-
// Find indentation if exists.
|
|
127
|
-
const match = inlineSnapshot.match(INDENTATION_REGEX);
|
|
128
|
-
if (!match || !match[1]) {
|
|
129
|
-
// No indentation.
|
|
130
|
-
return inlineSnapshot;
|
|
131
|
-
}
|
|
132
|
-
const indentation = match[1];
|
|
133
|
-
const lines = inlineSnapshot.split(/\n/g);
|
|
134
|
-
if (lines.length <= 2) {
|
|
135
|
-
// Must be at least 3 lines.
|
|
136
|
-
return inlineSnapshot;
|
|
137
|
-
}
|
|
138
|
-
if (lines[0].trim() !== "" || lines.at(-1)?.trim() !== "") {
|
|
139
|
-
// If not blank first and last lines, abort.
|
|
140
|
-
return inlineSnapshot;
|
|
141
|
-
}
|
|
142
|
-
for (let i = 1; i < lines.length - 1; i++) {
|
|
143
|
-
if (lines[i] !== "") {
|
|
144
|
-
if (lines[i].indexOf(indentation) !== 0) {
|
|
145
|
-
// All lines except first and last should either be blank or have the same
|
|
146
|
-
// indent as the first line (or more). If this isn't the case we don't
|
|
147
|
-
// want to touch the snapshot at all.
|
|
148
|
-
return inlineSnapshot;
|
|
149
|
-
}
|
|
150
|
-
lines[i] = lines[i].substring(indentation.length);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Last line is a special case because it won't have the same indent as others
|
|
154
|
-
// but may still have been given some indent to line up.
|
|
155
|
-
lines[lines.length - 1] = "";
|
|
156
|
-
// Return inline snapshot, now at indent 0.
|
|
157
|
-
inlineSnapshot = lines.join("\n");
|
|
158
|
-
return inlineSnapshot;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function saveRawSnapshots(environment, snapshots) {
|
|
162
|
-
await Promise.all(snapshots.map(async (snap) => {
|
|
163
|
-
if (!snap.readonly) {
|
|
164
|
-
await environment.saveSnapshotFile(snap.file, snap.snapshot);
|
|
165
|
-
}
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
6
|
function getDefaultExportFromCjs(x) {
|
|
170
7
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
171
8
|
}
|
|
@@ -448,6 +285,188 @@ class CounterMap extends DefaultMap {
|
|
|
448
285
|
return total;
|
|
449
286
|
}
|
|
450
287
|
}
|
|
288
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
289
|
+
function memo(fn) {
|
|
290
|
+
const cache = new Map();
|
|
291
|
+
return (arg) => {
|
|
292
|
+
if (!cache.has(arg)) {
|
|
293
|
+
cache.set(arg, fn(arg));
|
|
294
|
+
}
|
|
295
|
+
return cache.get(arg);
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function saveInlineSnapshots(environment, snapshots) {
|
|
300
|
+
const MagicString = (await import('magic-string')).default;
|
|
301
|
+
const files = new Set(snapshots.map((i) => i.file));
|
|
302
|
+
await Promise.all(Array.from(files).map(async (file) => {
|
|
303
|
+
const snaps = snapshots.filter((i) => i.file === file);
|
|
304
|
+
const code = await environment.readSnapshotFile(file);
|
|
305
|
+
if (code == null) {
|
|
306
|
+
throw new Error(`cannot read ${file} when saving inline snapshot`);
|
|
307
|
+
}
|
|
308
|
+
const s = new MagicString(code);
|
|
309
|
+
for (const snap of snaps) {
|
|
310
|
+
const index = positionToOffset(code, snap.line, snap.column);
|
|
311
|
+
replaceInlineSnap(code, s, index, snap.snapshot, snap.assertionName);
|
|
312
|
+
}
|
|
313
|
+
const transformed = s.toString();
|
|
314
|
+
if (transformed !== code) {
|
|
315
|
+
await environment.saveSnapshotFile(file, transformed);
|
|
316
|
+
}
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
const defaultStartObjectRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\s\S]*\*\/\s*|\/\/.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]))*\{/;
|
|
320
|
+
function escapeRegExp(s) {
|
|
321
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
322
|
+
}
|
|
323
|
+
const buildStartObjectRegex = memo((assertionName) => {
|
|
324
|
+
const replaced = defaultStartObjectRegex.source.replace("toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot", escapeRegExp(assertionName));
|
|
325
|
+
return new RegExp(replaced);
|
|
326
|
+
});
|
|
327
|
+
function replaceObjectSnap(code, s, index, newSnap, assertionName) {
|
|
328
|
+
let _code = code.slice(index);
|
|
329
|
+
const regex = assertionName ? buildStartObjectRegex(assertionName) : defaultStartObjectRegex;
|
|
330
|
+
const startMatch = regex.exec(_code);
|
|
331
|
+
if (!startMatch) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
_code = _code.slice(startMatch.index);
|
|
335
|
+
let callEnd = getCallLastIndex(_code);
|
|
336
|
+
if (callEnd === null) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
callEnd += index + startMatch.index;
|
|
340
|
+
const shapeStart = index + startMatch.index + startMatch[0].length;
|
|
341
|
+
const shapeEnd = getObjectShapeEndIndex(code, shapeStart);
|
|
342
|
+
const snap = `, ${prepareSnapString(newSnap, code, index)}`;
|
|
343
|
+
if (shapeEnd === callEnd) {
|
|
344
|
+
// toMatchInlineSnapshot({ foo: expect.any(String) })
|
|
345
|
+
s.appendLeft(callEnd, snap);
|
|
346
|
+
} else {
|
|
347
|
+
// toMatchInlineSnapshot({ foo: expect.any(String) }, ``)
|
|
348
|
+
s.overwrite(shapeEnd, callEnd, snap);
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
function getObjectShapeEndIndex(code, index) {
|
|
353
|
+
let startBraces = 1;
|
|
354
|
+
let endBraces = 0;
|
|
355
|
+
while (startBraces !== endBraces && index < code.length) {
|
|
356
|
+
const s = code[index++];
|
|
357
|
+
if (s === "{") {
|
|
358
|
+
startBraces++;
|
|
359
|
+
} else if (s === "}") {
|
|
360
|
+
endBraces++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return index;
|
|
364
|
+
}
|
|
365
|
+
function prepareSnapString(snap, source, index) {
|
|
366
|
+
const lineNumber = offsetToLineNumber(source, index);
|
|
367
|
+
const line = source.split(lineSplitRE)[lineNumber - 1];
|
|
368
|
+
const indent = line.match(/^\s*/)[0] || "";
|
|
369
|
+
const indentNext = indent.includes(" ") ? `${indent}\t` : `${indent} `;
|
|
370
|
+
const lines = snap.trim().replace(/\\/g, "\\\\").split(/\n/g);
|
|
371
|
+
const isOneline = lines.length <= 1;
|
|
372
|
+
const quote = "`";
|
|
373
|
+
if (isOneline) {
|
|
374
|
+
return `${quote}${lines.join("\n").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")}${quote}`;
|
|
375
|
+
}
|
|
376
|
+
return `${quote}\n${lines.map((i) => i ? indentNext + i : "").join("\n").replace(/`/g, "\\`").replace(/\$\{/g, "\\${")}\n${indent}${quote}`;
|
|
377
|
+
}
|
|
378
|
+
const defaultMethodNames = ["toMatchInlineSnapshot", "toThrowErrorMatchingInlineSnapshot"];
|
|
379
|
+
// on webkit, the line number is at the end of the method, not at the start
|
|
380
|
+
function getCodeStartingAtIndex(code, index, methodNames) {
|
|
381
|
+
for (const name of methodNames) {
|
|
382
|
+
const adjusted = index - name.length;
|
|
383
|
+
if (adjusted >= 0 && code.slice(adjusted, index) === name) {
|
|
384
|
+
return {
|
|
385
|
+
code: code.slice(adjusted),
|
|
386
|
+
index: adjusted
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
code: code.slice(index),
|
|
392
|
+
index
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const defaultStartRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\s\S]*\*\/\s*|\/\/.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]))*[\w$]*(['"`)])/;
|
|
396
|
+
const buildStartRegex = memo((assertionName) => {
|
|
397
|
+
const replaced = defaultStartRegex.source.replace("toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot", escapeRegExp(assertionName));
|
|
398
|
+
return new RegExp(replaced);
|
|
399
|
+
});
|
|
400
|
+
function replaceInlineSnap(code, s, currentIndex, newSnap, assertionName) {
|
|
401
|
+
const methodNames = assertionName ? [assertionName] : defaultMethodNames;
|
|
402
|
+
const { code: codeStartingAtIndex, index } = getCodeStartingAtIndex(code, currentIndex, methodNames);
|
|
403
|
+
const startRegex = assertionName ? buildStartRegex(assertionName) : defaultStartRegex;
|
|
404
|
+
const startMatch = startRegex.exec(codeStartingAtIndex);
|
|
405
|
+
const keywordRegex = assertionName ? new RegExp(escapeRegExp(assertionName)) : /toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot/;
|
|
406
|
+
const firstKeywordMatch = keywordRegex.exec(codeStartingAtIndex);
|
|
407
|
+
if (!startMatch || startMatch.index !== firstKeywordMatch?.index) {
|
|
408
|
+
return replaceObjectSnap(code, s, index, newSnap, assertionName);
|
|
409
|
+
}
|
|
410
|
+
const quote = startMatch[1];
|
|
411
|
+
const startIndex = index + startMatch.index + startMatch[0].length;
|
|
412
|
+
const snapString = prepareSnapString(newSnap, code, index);
|
|
413
|
+
if (quote === ")") {
|
|
414
|
+
s.appendRight(startIndex - 1, snapString);
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
const quoteEndRE = new RegExp(`(?:^|[^\\\\])${quote}`);
|
|
418
|
+
const endMatch = quoteEndRE.exec(code.slice(startIndex));
|
|
419
|
+
if (!endMatch) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const endIndex = startIndex + endMatch.index + endMatch[0].length;
|
|
423
|
+
s.overwrite(startIndex - 1, endIndex, snapString);
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
const INDENTATION_REGEX = /^([^\S\n]*)\S/m;
|
|
427
|
+
function stripSnapshotIndentation(inlineSnapshot) {
|
|
428
|
+
// Find indentation if exists.
|
|
429
|
+
const match = inlineSnapshot.match(INDENTATION_REGEX);
|
|
430
|
+
if (!match || !match[1]) {
|
|
431
|
+
// No indentation.
|
|
432
|
+
return inlineSnapshot;
|
|
433
|
+
}
|
|
434
|
+
const indentation = match[1];
|
|
435
|
+
const lines = inlineSnapshot.split(/\n/g);
|
|
436
|
+
if (lines.length <= 2) {
|
|
437
|
+
// Must be at least 3 lines.
|
|
438
|
+
return inlineSnapshot;
|
|
439
|
+
}
|
|
440
|
+
if (lines[0].trim() !== "" || lines.at(-1)?.trim() !== "") {
|
|
441
|
+
// If not blank first and last lines, abort.
|
|
442
|
+
return inlineSnapshot;
|
|
443
|
+
}
|
|
444
|
+
for (let i = 1; i < lines.length - 1; i++) {
|
|
445
|
+
if (lines[i] !== "") {
|
|
446
|
+
if (lines[i].indexOf(indentation) !== 0) {
|
|
447
|
+
// All lines except first and last should either be blank or have the same
|
|
448
|
+
// indent as the first line (or more). If this isn't the case we don't
|
|
449
|
+
// want to touch the snapshot at all.
|
|
450
|
+
return inlineSnapshot;
|
|
451
|
+
}
|
|
452
|
+
lines[i] = lines[i].substring(indentation.length);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Last line is a special case because it won't have the same indent as others
|
|
456
|
+
// but may still have been given some indent to line up.
|
|
457
|
+
lines[lines.length - 1] = "";
|
|
458
|
+
// Return inline snapshot, now at indent 0.
|
|
459
|
+
inlineSnapshot = lines.join("\n");
|
|
460
|
+
return inlineSnapshot;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function saveRawSnapshots(environment, snapshots) {
|
|
464
|
+
await Promise.all(snapshots.map(async (snap) => {
|
|
465
|
+
if (!snap.readonly) {
|
|
466
|
+
await environment.saveSnapshotFile(snap.file, snap.snapshot);
|
|
467
|
+
}
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
451
470
|
|
|
452
471
|
function isSameStackPosition(x, y) {
|
|
453
472
|
return x.file === y.file && x.column === y.column && x.line === y.line;
|
|
@@ -523,6 +542,9 @@ class SnapshotState {
|
|
|
523
542
|
const content = await options.snapshotEnvironment.readSnapshotFile(snapshotPath);
|
|
524
543
|
return new SnapshotState(testFilePath, snapshotPath, content, options);
|
|
525
544
|
}
|
|
545
|
+
get snapshotUpdateState() {
|
|
546
|
+
return this._updateSnapshot;
|
|
547
|
+
}
|
|
526
548
|
get environment() {
|
|
527
549
|
return this._environment;
|
|
528
550
|
}
|
|
@@ -564,6 +586,11 @@ class SnapshotState {
|
|
|
564
586
|
if (promiseIndex !== -1) {
|
|
565
587
|
return stacks[promiseIndex + 3];
|
|
566
588
|
}
|
|
589
|
+
// support poll + inline snapshot
|
|
590
|
+
const pollChainIndex = stacks.findIndex((i) => i.method.match(/__VITEST_POLL_CHAIN__/));
|
|
591
|
+
if (pollChainIndex !== -1) {
|
|
592
|
+
return stacks[pollChainIndex + 1];
|
|
593
|
+
}
|
|
567
594
|
// inline snapshot function can be named __INLINE_SNAPSHOT_OFFSET_<n>__
|
|
568
595
|
// to specify a custom stack offset
|
|
569
596
|
for (let i = 0; i < stacks.length; i++) {
|
|
@@ -572,6 +599,12 @@ class SnapshotState {
|
|
|
572
599
|
return stacks[i + Number(match[1])] ?? null;
|
|
573
600
|
}
|
|
574
601
|
}
|
|
602
|
+
// custom matcher registered via expect.extend() — the wrapper function
|
|
603
|
+
// in jest-extend.ts is named __VITEST_EXTEND_ASSERTION__
|
|
604
|
+
const customMatcherIndex = stacks.findIndex((i) => i.method.includes("__VITEST_EXTEND_ASSERTION__"));
|
|
605
|
+
if (customMatcherIndex !== -1) {
|
|
606
|
+
return stacks[customMatcherIndex + 3] ?? null;
|
|
607
|
+
}
|
|
575
608
|
// inline snapshot function is called __INLINE_SNAPSHOT__
|
|
576
609
|
// in integrations/snapshot/chai.ts
|
|
577
610
|
const stackIndex = stacks.findIndex((i) => i.method.includes("__INLINE_SNAPSHOT__"));
|
|
@@ -581,9 +614,10 @@ class SnapshotState {
|
|
|
581
614
|
this._dirty = true;
|
|
582
615
|
if (options.stack) {
|
|
583
616
|
this._inlineSnapshots.push({
|
|
617
|
+
...options.stack,
|
|
584
618
|
snapshot: receivedSerialized,
|
|
585
619
|
testId: options.testId,
|
|
586
|
-
|
|
620
|
+
assertionName: options.assertionName
|
|
587
621
|
});
|
|
588
622
|
} else if (options.rawSnapshot) {
|
|
589
623
|
this._rawSnapshots.push({
|
|
@@ -594,6 +628,114 @@ class SnapshotState {
|
|
|
594
628
|
this._snapshotData[key] = receivedSerialized;
|
|
595
629
|
}
|
|
596
630
|
}
|
|
631
|
+
_resolveKey(testId, testName, key) {
|
|
632
|
+
this._counters.increment(testName);
|
|
633
|
+
const count = this._counters.get(testName);
|
|
634
|
+
if (!key) {
|
|
635
|
+
key = testNameToKey(testName, count);
|
|
636
|
+
}
|
|
637
|
+
this._testIdToKeys.get(testId).push(key);
|
|
638
|
+
return {
|
|
639
|
+
key,
|
|
640
|
+
count
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
_resolveInlineStack(options) {
|
|
644
|
+
const { testId, snapshot, assertionName, error } = options;
|
|
645
|
+
const stacks = parseErrorStacktrace(error, { ignoreStackEntries: [] });
|
|
646
|
+
const _stack = this._inferInlineSnapshotStack(stacks);
|
|
647
|
+
if (!_stack) {
|
|
648
|
+
const message = stacks.map((s) => ` ${s.file}:${s.line}:${s.column}${s.method ? ` (${s.method})` : ""}`).join("\n");
|
|
649
|
+
throw new Error(`@vitest/snapshot: Couldn't infer stack frame for inline snapshot.\n${message}`);
|
|
650
|
+
}
|
|
651
|
+
const stack = this.environment.processStackTrace?.(_stack) || _stack;
|
|
652
|
+
// removing 1 column, because source map points to the wrong
|
|
653
|
+
// location for js files, but `column-1` points to the same in both js/ts
|
|
654
|
+
// https://github.com/vitejs/vite/issues/8657
|
|
655
|
+
stack.column--;
|
|
656
|
+
// reject multiple inline snapshots at the same location if snapshot is different
|
|
657
|
+
const snapshotsWithSameStack = this._inlineSnapshotStacks.filter((s) => isSameStackPosition(s, stack));
|
|
658
|
+
if (snapshotsWithSameStack.length > 0) {
|
|
659
|
+
// ensure only one snapshot will be written at the same location
|
|
660
|
+
this._inlineSnapshots = this._inlineSnapshots.filter((s) => !isSameStackPosition(s, stack));
|
|
661
|
+
const differentSnapshot = snapshotsWithSameStack.find((s) => s.snapshot !== snapshot);
|
|
662
|
+
if (differentSnapshot) {
|
|
663
|
+
throw Object.assign(new Error(`${assertionName} with different snapshots cannot be called at the same location`), {
|
|
664
|
+
actual: snapshot,
|
|
665
|
+
expected: differentSnapshot.snapshot
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
this._inlineSnapshotStacks.push({
|
|
670
|
+
...stack,
|
|
671
|
+
testId,
|
|
672
|
+
snapshot
|
|
673
|
+
});
|
|
674
|
+
return stack;
|
|
675
|
+
}
|
|
676
|
+
_reconcile(opts) {
|
|
677
|
+
// These are the conditions on when to write snapshots:
|
|
678
|
+
// * There's no snapshot file in a non-CI environment.
|
|
679
|
+
// * There is a snapshot file and we decided to update the snapshot.
|
|
680
|
+
// * There is a snapshot file, but it doesn't have this snapshot.
|
|
681
|
+
// These are the conditions on when not to write snapshots:
|
|
682
|
+
// * The update flag is set to 'none'.
|
|
683
|
+
// * There's no snapshot file or a file without this snapshot on a CI environment.
|
|
684
|
+
if (opts.hasSnapshot && this._updateSnapshot === "all" || (!opts.hasSnapshot || !opts.snapshotIsPersisted) && (this._updateSnapshot === "new" || this._updateSnapshot === "all")) {
|
|
685
|
+
if (this._updateSnapshot === "all") {
|
|
686
|
+
if (!opts.pass) {
|
|
687
|
+
if (opts.hasSnapshot) {
|
|
688
|
+
this.updated.increment(opts.testId);
|
|
689
|
+
} else {
|
|
690
|
+
this.added.increment(opts.testId);
|
|
691
|
+
}
|
|
692
|
+
this._addSnapshot(opts.key, opts.addValue, {
|
|
693
|
+
stack: opts.stack,
|
|
694
|
+
testId: opts.testId,
|
|
695
|
+
rawSnapshot: opts.rawSnapshot,
|
|
696
|
+
assertionName: opts.assertionName
|
|
697
|
+
});
|
|
698
|
+
} else {
|
|
699
|
+
this.matched.increment(opts.testId);
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
this._addSnapshot(opts.key, opts.addValue, {
|
|
703
|
+
stack: opts.stack,
|
|
704
|
+
testId: opts.testId,
|
|
705
|
+
rawSnapshot: opts.rawSnapshot,
|
|
706
|
+
assertionName: opts.assertionName
|
|
707
|
+
});
|
|
708
|
+
this.added.increment(opts.testId);
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
actual: "",
|
|
712
|
+
count: opts.count,
|
|
713
|
+
expected: "",
|
|
714
|
+
key: opts.key,
|
|
715
|
+
pass: true
|
|
716
|
+
};
|
|
717
|
+
} else {
|
|
718
|
+
if (!opts.pass) {
|
|
719
|
+
this.unmatched.increment(opts.testId);
|
|
720
|
+
return {
|
|
721
|
+
actual: opts.actualDisplay,
|
|
722
|
+
count: opts.count,
|
|
723
|
+
expected: opts.expectedDisplay,
|
|
724
|
+
key: opts.key,
|
|
725
|
+
pass: false
|
|
726
|
+
};
|
|
727
|
+
} else {
|
|
728
|
+
this.matched.increment(opts.testId);
|
|
729
|
+
return {
|
|
730
|
+
actual: "",
|
|
731
|
+
count: opts.count,
|
|
732
|
+
expected: "",
|
|
733
|
+
key: opts.key,
|
|
734
|
+
pass: true
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
597
739
|
async save() {
|
|
598
740
|
const hasExternalSnapshots = Object.keys(this._snapshotData).length;
|
|
599
741
|
const hasInlineSnapshots = this._inlineSnapshots.length;
|
|
@@ -637,14 +779,24 @@ class SnapshotState {
|
|
|
637
779
|
this._uncheckedKeys.clear();
|
|
638
780
|
}
|
|
639
781
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
782
|
+
probeExpectedSnapshot(options) {
|
|
783
|
+
const count = this._counters.get(options.testName) + 1;
|
|
784
|
+
const key = testNameToKey(options.testName, count);
|
|
785
|
+
return {
|
|
786
|
+
key,
|
|
787
|
+
count,
|
|
788
|
+
data: options?.isInline ? options.inlineSnapshot : this._snapshotData[key],
|
|
789
|
+
markAsChecked: () => {
|
|
790
|
+
this._counters.increment(options.testName);
|
|
791
|
+
this._testIdToKeys.get(options.testId).push(key);
|
|
792
|
+
this._uncheckedKeys.delete(key);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
match({ testId, testName, received, key, inlineSnapshot, isInline, error, rawSnapshot, assertionName }) {
|
|
797
|
+
const resolved = this._resolveKey(testId, testName, key);
|
|
798
|
+
key = resolved.key;
|
|
799
|
+
const count = resolved.count;
|
|
648
800
|
// Do not mark the snapshot as "checked" if the snapshot is inline and
|
|
649
801
|
// there's an external snapshot. This way the external snapshot can be
|
|
650
802
|
// removed with `--updateSnapshot`.
|
|
@@ -667,105 +819,57 @@ class SnapshotState {
|
|
|
667
819
|
const hasSnapshot = expected !== undefined;
|
|
668
820
|
const snapshotIsPersisted = isInline || this._fileExists || rawSnapshot && rawSnapshot.content != null;
|
|
669
821
|
if (pass && !isInline && !rawSnapshot) {
|
|
670
|
-
//
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
//
|
|
822
|
+
// When the file is re-saved (because other snapshots changed), the JS
|
|
823
|
+
// round-trip can lose proper escaping. Refresh in-memory data with the
|
|
824
|
+
// freshly serialized string so the file is written correctly.
|
|
825
|
+
// _reconcile does not write _snapshotData on pass, so this is the only
|
|
826
|
+
// place it gets refreshed. Domain snapshots skip this because the stored
|
|
827
|
+
// value may contain match patterns that differ from the received output.
|
|
676
828
|
this._snapshotData[key] = receivedSerialized;
|
|
677
829
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
this.updated.increment(testId);
|
|
722
|
-
} else {
|
|
723
|
-
this.added.increment(testId);
|
|
724
|
-
}
|
|
725
|
-
this._addSnapshot(key, receivedSerialized, {
|
|
726
|
-
stack,
|
|
727
|
-
testId,
|
|
728
|
-
rawSnapshot
|
|
729
|
-
});
|
|
730
|
-
} else {
|
|
731
|
-
this.matched.increment(testId);
|
|
732
|
-
}
|
|
733
|
-
} else {
|
|
734
|
-
this._addSnapshot(key, receivedSerialized, {
|
|
735
|
-
stack,
|
|
736
|
-
testId,
|
|
737
|
-
rawSnapshot
|
|
738
|
-
});
|
|
739
|
-
this.added.increment(testId);
|
|
740
|
-
}
|
|
741
|
-
return {
|
|
742
|
-
actual: "",
|
|
743
|
-
count,
|
|
744
|
-
expected: "",
|
|
745
|
-
key,
|
|
746
|
-
pass: true
|
|
747
|
-
};
|
|
748
|
-
} else {
|
|
749
|
-
if (!pass) {
|
|
750
|
-
this.unmatched.increment(testId);
|
|
751
|
-
return {
|
|
752
|
-
actual: rawSnapshot ? receivedSerialized : removeExtraLineBreaks(receivedSerialized),
|
|
753
|
-
count,
|
|
754
|
-
expected: expectedTrimmed !== undefined ? rawSnapshot ? expectedTrimmed : removeExtraLineBreaks(expectedTrimmed) : undefined,
|
|
755
|
-
key,
|
|
756
|
-
pass: false
|
|
757
|
-
};
|
|
758
|
-
} else {
|
|
759
|
-
this.matched.increment(testId);
|
|
760
|
-
return {
|
|
761
|
-
actual: "",
|
|
762
|
-
count,
|
|
763
|
-
expected: "",
|
|
764
|
-
key,
|
|
765
|
-
pass: true
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
}
|
|
830
|
+
const stack = isInline ? this._resolveInlineStack({
|
|
831
|
+
testId,
|
|
832
|
+
snapshot: receivedSerialized,
|
|
833
|
+
assertionName: assertionName || "toMatchInlineSnapshot",
|
|
834
|
+
error: error || new Error("snapshot")
|
|
835
|
+
}) : undefined;
|
|
836
|
+
return this._reconcile({
|
|
837
|
+
testId,
|
|
838
|
+
key,
|
|
839
|
+
count,
|
|
840
|
+
pass,
|
|
841
|
+
hasSnapshot,
|
|
842
|
+
snapshotIsPersisted: !!snapshotIsPersisted,
|
|
843
|
+
addValue: receivedSerialized,
|
|
844
|
+
actualDisplay: rawSnapshot ? receivedSerialized : removeExtraLineBreaks(receivedSerialized),
|
|
845
|
+
expectedDisplay: expectedTrimmed !== undefined ? rawSnapshot ? expectedTrimmed : removeExtraLineBreaks(expectedTrimmed) : undefined,
|
|
846
|
+
stack,
|
|
847
|
+
rawSnapshot,
|
|
848
|
+
assertionName
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
processDomainSnapshot({ testId, received, expectedSnapshot, matchResult, isInline, error, assertionName }) {
|
|
852
|
+
const stack = isInline ? this._resolveInlineStack({
|
|
853
|
+
testId,
|
|
854
|
+
snapshot: received,
|
|
855
|
+
assertionName,
|
|
856
|
+
error: error || new Error("STACK_TRACE_ERROR")
|
|
857
|
+
}) : undefined;
|
|
858
|
+
const actualResolved = matchResult?.resolved ?? received;
|
|
859
|
+
const expectedResolved = matchResult?.expected ?? expectedSnapshot.data;
|
|
860
|
+
return this._reconcile({
|
|
861
|
+
testId,
|
|
862
|
+
key: expectedSnapshot.key,
|
|
863
|
+
count: expectedSnapshot.count,
|
|
864
|
+
pass: matchResult?.pass ?? false,
|
|
865
|
+
hasSnapshot: !!expectedSnapshot.data,
|
|
866
|
+
snapshotIsPersisted: isInline ? true : this._fileExists,
|
|
867
|
+
addValue: actualResolved,
|
|
868
|
+
actualDisplay: removeExtraLineBreaks(actualResolved),
|
|
869
|
+
expectedDisplay: expectedResolved !== undefined ? removeExtraLineBreaks(expectedResolved) : undefined,
|
|
870
|
+
stack,
|
|
871
|
+
assertionName
|
|
872
|
+
});
|
|
769
873
|
}
|
|
770
874
|
async pack() {
|
|
771
875
|
const snapshot = {
|
|
@@ -844,31 +948,44 @@ class SnapshotClient {
|
|
|
844
948
|
}
|
|
845
949
|
return state;
|
|
846
950
|
}
|
|
847
|
-
|
|
848
|
-
const { filepath, name, testId = name, message, isInline = false, properties, inlineSnapshot, error, errorMessage, rawSnapshot } = options;
|
|
951
|
+
match(options) {
|
|
952
|
+
const { filepath, name, testId = name, message, isInline = false, properties, inlineSnapshot, error, errorMessage, rawSnapshot, assertionName } = options;
|
|
849
953
|
let { received } = options;
|
|
850
954
|
if (!filepath) {
|
|
851
955
|
throw new Error("Snapshot cannot be used outside of test");
|
|
852
956
|
}
|
|
853
957
|
const snapshotState = this.getSnapshotState(filepath);
|
|
958
|
+
const testName = [name, ...message ? [message] : []].join(" > ");
|
|
959
|
+
// Probe first so we can mark as checked even on early return
|
|
960
|
+
const expectedSnapshot = snapshotState.probeExpectedSnapshot({
|
|
961
|
+
testName,
|
|
962
|
+
testId,
|
|
963
|
+
isInline,
|
|
964
|
+
inlineSnapshot
|
|
965
|
+
});
|
|
854
966
|
if (typeof properties === "object") {
|
|
855
967
|
if (typeof received !== "object" || !received) {
|
|
968
|
+
expectedSnapshot.markAsChecked();
|
|
856
969
|
throw new Error("Received value must be an object when the matcher has properties");
|
|
857
970
|
}
|
|
971
|
+
let propertiesPass;
|
|
858
972
|
try {
|
|
859
|
-
|
|
860
|
-
// const pass = equals(received, properties, [iterableEquality, subsetEquality])
|
|
861
|
-
if (!pass) {
|
|
862
|
-
throw createMismatchError("Snapshot properties mismatched", snapshotState.expand, received, properties);
|
|
863
|
-
} else {
|
|
864
|
-
received = deepMergeSnapshot(received, properties);
|
|
865
|
-
}
|
|
973
|
+
propertiesPass = this.options.isEqual?.(received, properties) ?? false;
|
|
866
974
|
} catch (err) {
|
|
867
|
-
|
|
975
|
+
expectedSnapshot.markAsChecked();
|
|
868
976
|
throw err;
|
|
869
977
|
}
|
|
978
|
+
if (!propertiesPass) {
|
|
979
|
+
expectedSnapshot.markAsChecked();
|
|
980
|
+
return {
|
|
981
|
+
pass: false,
|
|
982
|
+
message: () => errorMessage || "Snapshot properties mismatched",
|
|
983
|
+
actual: received,
|
|
984
|
+
expected: properties
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
received = deepMergeSnapshot(received, properties);
|
|
870
988
|
}
|
|
871
|
-
const testName = [name, ...message ? [message] : []].join(" > ");
|
|
872
989
|
const { actual, expected, key, pass } = snapshotState.match({
|
|
873
990
|
testId,
|
|
874
991
|
testName,
|
|
@@ -876,11 +993,109 @@ class SnapshotClient {
|
|
|
876
993
|
isInline,
|
|
877
994
|
error,
|
|
878
995
|
inlineSnapshot,
|
|
879
|
-
rawSnapshot
|
|
996
|
+
rawSnapshot,
|
|
997
|
+
assertionName
|
|
880
998
|
});
|
|
881
|
-
|
|
882
|
-
|
|
999
|
+
return {
|
|
1000
|
+
pass,
|
|
1001
|
+
message: () => `Snapshot \`${key || "unknown"}\` mismatched`,
|
|
1002
|
+
actual: rawSnapshot ? actual : actual?.trim(),
|
|
1003
|
+
expected: rawSnapshot ? expected : expected?.trim()
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
assert(options) {
|
|
1007
|
+
const result = this.match(options);
|
|
1008
|
+
if (!result.pass) {
|
|
1009
|
+
const snapshotState = this.getSnapshotState(options.filepath);
|
|
1010
|
+
throw createMismatchError(result.message(), snapshotState.expand, result.actual, result.expected);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
matchDomain(options) {
|
|
1014
|
+
const { received, filepath, name, testId = name, message, adapter, isInline = false, inlineSnapshot, error } = options;
|
|
1015
|
+
if (!filepath) {
|
|
1016
|
+
throw new Error("Snapshot cannot be used outside of test");
|
|
1017
|
+
}
|
|
1018
|
+
const captured = adapter.capture(received);
|
|
1019
|
+
const rendered = adapter.render(captured);
|
|
1020
|
+
const snapshotState = this.getSnapshotState(filepath);
|
|
1021
|
+
const testName = [name, ...message ? [message] : []].join(" > ");
|
|
1022
|
+
const expectedSnapshot = snapshotState.probeExpectedSnapshot({
|
|
1023
|
+
testName,
|
|
1024
|
+
testId,
|
|
1025
|
+
isInline,
|
|
1026
|
+
inlineSnapshot
|
|
1027
|
+
});
|
|
1028
|
+
expectedSnapshot.markAsChecked();
|
|
1029
|
+
const matchResult = expectedSnapshot.data ? adapter.match(captured, adapter.parseExpected(expectedSnapshot.data)) : undefined;
|
|
1030
|
+
const { actual, expected, key, pass } = snapshotState.processDomainSnapshot({
|
|
1031
|
+
testId,
|
|
1032
|
+
received: rendered,
|
|
1033
|
+
expectedSnapshot,
|
|
1034
|
+
matchResult,
|
|
1035
|
+
isInline,
|
|
1036
|
+
error,
|
|
1037
|
+
assertionName: options.assertionName
|
|
1038
|
+
});
|
|
1039
|
+
return {
|
|
1040
|
+
pass,
|
|
1041
|
+
message: () => `Snapshot \`${key}\` mismatched`,
|
|
1042
|
+
actual: actual?.trim(),
|
|
1043
|
+
expected: expected?.trim()
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
async pollMatchDomain(options) {
|
|
1047
|
+
const { poll, filepath, name, testId = name, message, adapter, isInline = false, inlineSnapshot, error, timeout = 1e3, interval = 50 } = options;
|
|
1048
|
+
if (!filepath) {
|
|
1049
|
+
throw new Error("Snapshot cannot be used outside of test");
|
|
1050
|
+
}
|
|
1051
|
+
const snapshotState = this.getSnapshotState(filepath);
|
|
1052
|
+
const testName = [name, ...message ? [message] : []].join(" > ");
|
|
1053
|
+
const expectedSnapshot = snapshotState.probeExpectedSnapshot({
|
|
1054
|
+
testName,
|
|
1055
|
+
testId,
|
|
1056
|
+
isInline,
|
|
1057
|
+
inlineSnapshot
|
|
1058
|
+
});
|
|
1059
|
+
const reference = expectedSnapshot.data && snapshotState.snapshotUpdateState !== "all" ? adapter.parseExpected(expectedSnapshot.data) : undefined;
|
|
1060
|
+
const timedOut = timeout > 0 ? new Promise((r) => setTimeout(r, timeout)) : undefined;
|
|
1061
|
+
const stableResult = await getStableSnapshot({
|
|
1062
|
+
adapter,
|
|
1063
|
+
poll,
|
|
1064
|
+
interval,
|
|
1065
|
+
timedOut,
|
|
1066
|
+
match: reference ? (captured) => adapter.match(captured, reference).pass : undefined
|
|
1067
|
+
});
|
|
1068
|
+
expectedSnapshot.markAsChecked();
|
|
1069
|
+
if (!stableResult?.rendered) {
|
|
1070
|
+
// the original caller `expect.poll` later manipulates error via `throwWithCause`,
|
|
1071
|
+
// so here we can directly throw `lastPollError` if exists.
|
|
1072
|
+
if (stableResult?.lastPollError) {
|
|
1073
|
+
throw stableResult.lastPollError;
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
pass: false,
|
|
1077
|
+
message: () => `poll() did not produce a stable snapshot within the timeout`
|
|
1078
|
+
};
|
|
883
1079
|
}
|
|
1080
|
+
// TODO: should `all` mode ignore parse error?
|
|
1081
|
+
// Sielently hiding the error and creating snaphsot full scratch isn't good either.
|
|
1082
|
+
// Users can fix or purge the broken snapshot manually and that decision affects how domain snapshot gets updated.
|
|
1083
|
+
const matchResult = expectedSnapshot.data ? adapter.match(stableResult.captured, adapter.parseExpected(expectedSnapshot.data)) : undefined;
|
|
1084
|
+
const { actual, expected, key, pass } = snapshotState.processDomainSnapshot({
|
|
1085
|
+
testId,
|
|
1086
|
+
received: stableResult.rendered,
|
|
1087
|
+
expectedSnapshot,
|
|
1088
|
+
matchResult,
|
|
1089
|
+
isInline,
|
|
1090
|
+
error,
|
|
1091
|
+
assertionName: options.assertionName
|
|
1092
|
+
});
|
|
1093
|
+
return {
|
|
1094
|
+
pass,
|
|
1095
|
+
message: () => `Snapshot \`${key}\` mismatched`,
|
|
1096
|
+
actual: actual?.trim(),
|
|
1097
|
+
expected: expected?.trim()
|
|
1098
|
+
};
|
|
884
1099
|
}
|
|
885
1100
|
async assertRaw(options) {
|
|
886
1101
|
if (!options.rawSnapshot) {
|
|
@@ -904,5 +1119,69 @@ class SnapshotClient {
|
|
|
904
1119
|
this.snapshotStateMap.clear();
|
|
905
1120
|
}
|
|
906
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Polls repeatedly until the value reaches a stable state.
|
|
1124
|
+
*
|
|
1125
|
+
* Compares consecutive rendered outputs from the current session —
|
|
1126
|
+
* when two consecutive polls produce the same rendered string,
|
|
1127
|
+
* the value is considered stable.
|
|
1128
|
+
*
|
|
1129
|
+
* Every `await` (poll call, interval delay) races against `timedOut`
|
|
1130
|
+
* so that hanging polls and delays are interrupted.
|
|
1131
|
+
*/
|
|
1132
|
+
async function getStableSnapshot({ adapter, poll, interval, timedOut, match }) {
|
|
1133
|
+
let lastRendered;
|
|
1134
|
+
let lastPollError;
|
|
1135
|
+
let lastStable;
|
|
1136
|
+
while (true) {
|
|
1137
|
+
try {
|
|
1138
|
+
const pollResult = await raceWith(Promise.resolve(poll()), timedOut);
|
|
1139
|
+
if (!pollResult.ok) {
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
const captured = adapter.capture(pollResult.value);
|
|
1143
|
+
const rendered = adapter.render(captured);
|
|
1144
|
+
if (lastRendered !== undefined && rendered === lastRendered) {
|
|
1145
|
+
lastStable = {
|
|
1146
|
+
captured,
|
|
1147
|
+
rendered
|
|
1148
|
+
};
|
|
1149
|
+
if (!match || match(captured)) {
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
} else {
|
|
1153
|
+
lastRendered = rendered;
|
|
1154
|
+
lastStable = undefined;
|
|
1155
|
+
}
|
|
1156
|
+
} catch (pollError) {
|
|
1157
|
+
// poll() threw — reset stability baseline and retry
|
|
1158
|
+
lastRendered = undefined;
|
|
1159
|
+
lastStable = undefined;
|
|
1160
|
+
lastPollError = pollError;
|
|
1161
|
+
}
|
|
1162
|
+
const delayed = await raceWith(new Promise((r) => setTimeout(r, interval)), timedOut);
|
|
1163
|
+
if (!delayed.ok) {
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return {
|
|
1168
|
+
...lastStable,
|
|
1169
|
+
lastPollError
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
/** Type-safe `Promise.race` — tells you which promise won. */
|
|
1173
|
+
function raceWith(promise, other) {
|
|
1174
|
+
const left = promise.then((value) => ({
|
|
1175
|
+
ok: true,
|
|
1176
|
+
value
|
|
1177
|
+
}));
|
|
1178
|
+
if (!other) {
|
|
1179
|
+
return left;
|
|
1180
|
+
}
|
|
1181
|
+
return Promise.race([left, other.then((value) => ({
|
|
1182
|
+
ok: false,
|
|
1183
|
+
value
|
|
1184
|
+
}))]);
|
|
1185
|
+
}
|
|
907
1186
|
|
|
908
1187
|
export { SnapshotClient, SnapshotState, addSerializer, getSerializers, stripSnapshotIndentation };
|
package/dist/manager.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as SnapshotStateOptions, g as SnapshotSummary, b as SnapshotResult } from './rawSnapshot.d-D_X3-62x.js';
|
|
2
2
|
import '@vitest/pretty-format';
|
|
3
|
-
import './environment.d-DOJxxZV9.js';
|
|
4
3
|
import '@vitest/utils';
|
|
4
|
+
import './environment.d-DOJxxZV9.js';
|
|
5
5
|
|
|
6
6
|
declare class SnapshotManager {
|
|
7
7
|
options: Omit<SnapshotStateOptions, "snapshotEnvironment">;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { OptionsReceived, Plugin } from '@vitest/pretty-format';
|
|
2
|
+
import { ParsedStack } from '@vitest/utils';
|
|
3
|
+
import { S as SnapshotEnvironment } from './environment.d-DOJxxZV9.js';
|
|
4
|
+
|
|
5
|
+
interface DomainMatchResult {
|
|
6
|
+
pass: boolean;
|
|
7
|
+
message?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The captured value viewed through the template's lens.
|
|
10
|
+
*
|
|
11
|
+
* Where the template uses patterns (e.g. regexes) or omits details,
|
|
12
|
+
* the resolved string adopts those patterns. Where the template doesn't
|
|
13
|
+
* match, the resolved string uses literal captured values instead.
|
|
14
|
+
*
|
|
15
|
+
* Used for two purposes:
|
|
16
|
+
* - **Diff display** (actual side): compared against `expected`
|
|
17
|
+
* so the diff highlights only genuine mismatches, not pattern-vs-literal noise.
|
|
18
|
+
* - **Snapshot update** (`--update`): written as the new snapshot content,
|
|
19
|
+
* preserving user-edited patterns from matched regions while incorporating
|
|
20
|
+
* actual values for mismatched regions.
|
|
21
|
+
*
|
|
22
|
+
* When omitted, falls back to `render(capture(received))` (the raw rendered value).
|
|
23
|
+
*/
|
|
24
|
+
resolved?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The stored template re-rendered as a string, representing what the user
|
|
27
|
+
* originally wrote or last saved.
|
|
28
|
+
*
|
|
29
|
+
* Used as the expected side in diff display.
|
|
30
|
+
*
|
|
31
|
+
* When omitted, falls back to the raw snapshot string from the snap file
|
|
32
|
+
* or inline snapshot.
|
|
33
|
+
*/
|
|
34
|
+
expected?: string;
|
|
35
|
+
}
|
|
36
|
+
interface DomainSnapshotAdapter<
|
|
37
|
+
Captured = unknown,
|
|
38
|
+
Expected = unknown
|
|
39
|
+
> {
|
|
40
|
+
name: string;
|
|
41
|
+
capture: (received: unknown) => Captured;
|
|
42
|
+
render: (captured: Captured) => string;
|
|
43
|
+
parseExpected: (input: string) => Expected;
|
|
44
|
+
match: (captured: Captured, expected: Expected) => DomainMatchResult;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
|
49
|
+
*
|
|
50
|
+
* This source code is licensed under the MIT license found in the
|
|
51
|
+
* LICENSE file in the root directory of this source tree.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
declare class DefaultMap<
|
|
55
|
+
K,
|
|
56
|
+
V
|
|
57
|
+
> extends Map<K, V> {
|
|
58
|
+
private defaultFn;
|
|
59
|
+
constructor(defaultFn: (key: K) => V, entries?: Iterable<readonly [K, V]>);
|
|
60
|
+
get(key: K): V;
|
|
61
|
+
}
|
|
62
|
+
declare class CounterMap<K> extends DefaultMap<K, number> {
|
|
63
|
+
constructor();
|
|
64
|
+
_total: number | undefined;
|
|
65
|
+
valueOf(): number;
|
|
66
|
+
increment(key: K): void;
|
|
67
|
+
total(): number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface SnapshotReturnOptions {
|
|
71
|
+
actual: string;
|
|
72
|
+
count: number;
|
|
73
|
+
expected?: string;
|
|
74
|
+
key: string;
|
|
75
|
+
pass: boolean;
|
|
76
|
+
}
|
|
77
|
+
interface SaveStatus {
|
|
78
|
+
deleted: boolean;
|
|
79
|
+
saved: boolean;
|
|
80
|
+
}
|
|
81
|
+
interface ExpectedSnapshot {
|
|
82
|
+
key: string;
|
|
83
|
+
count: number;
|
|
84
|
+
data?: string;
|
|
85
|
+
markAsChecked: () => void;
|
|
86
|
+
}
|
|
87
|
+
declare class SnapshotState {
|
|
88
|
+
testFilePath: string;
|
|
89
|
+
snapshotPath: string;
|
|
90
|
+
private _counters;
|
|
91
|
+
private _dirty;
|
|
92
|
+
private _updateSnapshot;
|
|
93
|
+
private _snapshotData;
|
|
94
|
+
private _initialData;
|
|
95
|
+
private _inlineSnapshots;
|
|
96
|
+
private _inlineSnapshotStacks;
|
|
97
|
+
private _testIdToKeys;
|
|
98
|
+
private _rawSnapshots;
|
|
99
|
+
private _uncheckedKeys;
|
|
100
|
+
private _snapshotFormat;
|
|
101
|
+
private _environment;
|
|
102
|
+
private _fileExists;
|
|
103
|
+
expand: boolean;
|
|
104
|
+
private _added;
|
|
105
|
+
private _matched;
|
|
106
|
+
private _unmatched;
|
|
107
|
+
private _updated;
|
|
108
|
+
get added(): CounterMap<string>;
|
|
109
|
+
set added(value: number);
|
|
110
|
+
get matched(): CounterMap<string>;
|
|
111
|
+
set matched(value: number);
|
|
112
|
+
get unmatched(): CounterMap<string>;
|
|
113
|
+
set unmatched(value: number);
|
|
114
|
+
get updated(): CounterMap<string>;
|
|
115
|
+
set updated(value: number);
|
|
116
|
+
private constructor();
|
|
117
|
+
static create(testFilePath: string, options: SnapshotStateOptions): Promise<SnapshotState>;
|
|
118
|
+
get snapshotUpdateState(): SnapshotUpdateState;
|
|
119
|
+
get environment(): SnapshotEnvironment;
|
|
120
|
+
markSnapshotsAsCheckedForTest(testName: string): void;
|
|
121
|
+
clearTest(testId: string): void;
|
|
122
|
+
protected _inferInlineSnapshotStack(stacks: ParsedStack[]): ParsedStack | null;
|
|
123
|
+
private _addSnapshot;
|
|
124
|
+
private _resolveKey;
|
|
125
|
+
private _resolveInlineStack;
|
|
126
|
+
private _reconcile;
|
|
127
|
+
save(): Promise<SaveStatus>;
|
|
128
|
+
getUncheckedCount(): number;
|
|
129
|
+
getUncheckedKeys(): Array<string>;
|
|
130
|
+
removeUncheckedKeys(): void;
|
|
131
|
+
probeExpectedSnapshot(options: Pick<SnapshotMatchOptions, "testName" | "testId" | "isInline" | "inlineSnapshot">): ExpectedSnapshot;
|
|
132
|
+
match({ testId, testName, received, key, inlineSnapshot, isInline, error, rawSnapshot, assertionName }: SnapshotMatchOptions): SnapshotReturnOptions;
|
|
133
|
+
processDomainSnapshot({ testId, received, expectedSnapshot, matchResult, isInline, error, assertionName }: ProcessDomainSnapshotOptions): SnapshotReturnOptions;
|
|
134
|
+
pack(): Promise<SnapshotResult>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type SnapshotData = Record<string, string>;
|
|
138
|
+
type SnapshotUpdateState = "all" | "new" | "none";
|
|
139
|
+
type SnapshotSerializer = Plugin;
|
|
140
|
+
interface SnapshotStateOptions {
|
|
141
|
+
updateSnapshot: SnapshotUpdateState;
|
|
142
|
+
snapshotEnvironment: SnapshotEnvironment;
|
|
143
|
+
expand?: boolean;
|
|
144
|
+
snapshotFormat?: OptionsReceived;
|
|
145
|
+
resolveSnapshotPath?: (path: string, extension: string, context?: any) => string;
|
|
146
|
+
}
|
|
147
|
+
interface SnapshotMatchOptions {
|
|
148
|
+
testId: string;
|
|
149
|
+
testName: string;
|
|
150
|
+
received: unknown;
|
|
151
|
+
key?: string;
|
|
152
|
+
inlineSnapshot?: string;
|
|
153
|
+
isInline: boolean;
|
|
154
|
+
error?: Error;
|
|
155
|
+
rawSnapshot?: RawSnapshotInfo;
|
|
156
|
+
assertionName?: string;
|
|
157
|
+
}
|
|
158
|
+
interface ProcessDomainSnapshotOptions {
|
|
159
|
+
testId: string;
|
|
160
|
+
received: string;
|
|
161
|
+
expectedSnapshot: ExpectedSnapshot;
|
|
162
|
+
matchResult?: DomainMatchResult;
|
|
163
|
+
isInline?: boolean;
|
|
164
|
+
assertionName?: string;
|
|
165
|
+
error?: Error;
|
|
166
|
+
}
|
|
167
|
+
interface SnapshotResult {
|
|
168
|
+
filepath: string;
|
|
169
|
+
added: number;
|
|
170
|
+
fileDeleted: boolean;
|
|
171
|
+
matched: number;
|
|
172
|
+
unchecked: number;
|
|
173
|
+
uncheckedKeys: Array<string>;
|
|
174
|
+
unmatched: number;
|
|
175
|
+
updated: number;
|
|
176
|
+
}
|
|
177
|
+
interface UncheckedSnapshot {
|
|
178
|
+
filePath: string;
|
|
179
|
+
keys: Array<string>;
|
|
180
|
+
}
|
|
181
|
+
interface SnapshotSummary {
|
|
182
|
+
added: number;
|
|
183
|
+
didUpdate: boolean;
|
|
184
|
+
failure: boolean;
|
|
185
|
+
filesAdded: number;
|
|
186
|
+
filesRemoved: number;
|
|
187
|
+
filesRemovedList: Array<string>;
|
|
188
|
+
filesUnmatched: number;
|
|
189
|
+
filesUpdated: number;
|
|
190
|
+
matched: number;
|
|
191
|
+
total: number;
|
|
192
|
+
unchecked: number;
|
|
193
|
+
uncheckedKeysByFile: Array<UncheckedSnapshot>;
|
|
194
|
+
unmatched: number;
|
|
195
|
+
updated: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface RawSnapshotInfo {
|
|
199
|
+
file: string;
|
|
200
|
+
readonly?: boolean;
|
|
201
|
+
content?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { SnapshotState as S };
|
|
205
|
+
export type { DomainSnapshotAdapter as D, RawSnapshotInfo as R, UncheckedSnapshot as U, SnapshotStateOptions as a, SnapshotResult as b, DomainMatchResult as c, SnapshotData as d, SnapshotMatchOptions as e, SnapshotSerializer as f, SnapshotSummary as g, SnapshotUpdateState as h };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest/snapshot",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.1.
|
|
4
|
+
"version": "4.1.4",
|
|
5
5
|
"description": "Vitest snapshot manager",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://opencollective.com/vitest",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"magic-string": "^0.30.21",
|
|
47
47
|
"pathe": "^2.0.3",
|
|
48
|
-
"@vitest/pretty-format": "4.1.
|
|
49
|
-
"@vitest/utils": "4.1.
|
|
48
|
+
"@vitest/pretty-format": "4.1.4",
|
|
49
|
+
"@vitest/utils": "4.1.4"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/natural-compare": "^1.4.3",
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { OptionsReceived, Plugin } from '@vitest/pretty-format';
|
|
2
|
-
import { S as SnapshotEnvironment } from './environment.d-DOJxxZV9.js';
|
|
3
|
-
|
|
4
|
-
type SnapshotData = Record<string, string>;
|
|
5
|
-
type SnapshotUpdateState = "all" | "new" | "none";
|
|
6
|
-
type SnapshotSerializer = Plugin;
|
|
7
|
-
interface SnapshotStateOptions {
|
|
8
|
-
updateSnapshot: SnapshotUpdateState;
|
|
9
|
-
snapshotEnvironment: SnapshotEnvironment;
|
|
10
|
-
expand?: boolean;
|
|
11
|
-
snapshotFormat?: OptionsReceived;
|
|
12
|
-
resolveSnapshotPath?: (path: string, extension: string, context?: any) => string;
|
|
13
|
-
}
|
|
14
|
-
interface SnapshotMatchOptions {
|
|
15
|
-
testId: string;
|
|
16
|
-
testName: string;
|
|
17
|
-
received: unknown;
|
|
18
|
-
key?: string;
|
|
19
|
-
inlineSnapshot?: string;
|
|
20
|
-
isInline: boolean;
|
|
21
|
-
error?: Error;
|
|
22
|
-
rawSnapshot?: RawSnapshotInfo;
|
|
23
|
-
}
|
|
24
|
-
interface SnapshotResult {
|
|
25
|
-
filepath: string;
|
|
26
|
-
added: number;
|
|
27
|
-
fileDeleted: boolean;
|
|
28
|
-
matched: number;
|
|
29
|
-
unchecked: number;
|
|
30
|
-
uncheckedKeys: Array<string>;
|
|
31
|
-
unmatched: number;
|
|
32
|
-
updated: number;
|
|
33
|
-
}
|
|
34
|
-
interface UncheckedSnapshot {
|
|
35
|
-
filePath: string;
|
|
36
|
-
keys: Array<string>;
|
|
37
|
-
}
|
|
38
|
-
interface SnapshotSummary {
|
|
39
|
-
added: number;
|
|
40
|
-
didUpdate: boolean;
|
|
41
|
-
failure: boolean;
|
|
42
|
-
filesAdded: number;
|
|
43
|
-
filesRemoved: number;
|
|
44
|
-
filesRemovedList: Array<string>;
|
|
45
|
-
filesUnmatched: number;
|
|
46
|
-
filesUpdated: number;
|
|
47
|
-
matched: number;
|
|
48
|
-
total: number;
|
|
49
|
-
unchecked: number;
|
|
50
|
-
uncheckedKeysByFile: Array<UncheckedSnapshot>;
|
|
51
|
-
unmatched: number;
|
|
52
|
-
updated: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface RawSnapshotInfo {
|
|
56
|
-
file: string;
|
|
57
|
-
readonly?: boolean;
|
|
58
|
-
content?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export type { RawSnapshotInfo as R, SnapshotStateOptions as S, UncheckedSnapshot as U, SnapshotMatchOptions as a, SnapshotResult as b, SnapshotData as c, SnapshotSerializer as d, SnapshotSummary as e, SnapshotUpdateState as f };
|