inline snapshots have double indent
Describe the bug
snapshot strings have double indent in line 2 and following
a
b
c
i want "string snapshots" like in #856 but this bug-feature produces ugly snapshots
Reproduction
https://stackblitz.com/edit/vitest-dev-vitest-kfybto?file=test/basic.test.ts
demo.test.js
import { assert, describe, expect, it } from 'vitest';
// dont escape string snapshots
const stringSnapshotSerializer = {
serialize(val) {
return val
},
test(val) {
return (typeof val == "string")
},
}
describe('suite name', () => {
// expected
it('string snapshot expected', () => {
expect.addSnapshotSerializer(stringSnapshotSerializer)
expect(`
a
b
c
`).toMatchInlineSnapshot(`
a
b
c
`);
});
// actual: default serializer
it('snapshot', () => {
// value indent: 6 spaces
// sshot indent: 12 spaces
expect(`
a
b
c
`).toMatchInlineSnapshot(`
"
a
b
c
"
`);
});
// actual: string serializer
it('string snapshot actual', () => {
expect.addSnapshotSerializer(stringSnapshotSerializer)
// value indent: 6 spaces
// sshot indent: 12 spaces
expect(`
a
b
c
`).toMatchInlineSnapshot(`
a
b
c
`);
});
{
it('string snapshot actual', () => {
expect.addSnapshotSerializer(stringSnapshotSerializer)
// value indent: 8 spaces
// sshot indent: 16 spaces
expect(`
a
b
c
`).toMatchInlineSnapshot(`
a
b
c
`);
});
}
});
Workaround
add a prefix to the string
// dont escape string snapshots
const stringSnapshotSerializer = {
serialize(val) {
//return val
return "string:" + val
},
test(val) {
return (typeof val == "string")
},
}
describe('suite name', () => {
it('string snapshot expected', () => {
expect.addSnapshotSerializer(stringSnapshotSerializer)
expect(`
a
b
c
`).toMatchInlineSnapshot(`
string:
a
b
c
`);
});
Fix
blame: stripSnapshotIndentation, prepareSnapString @ inlineSnapshot.ts
the "wrong first line" is caused by snap.trim()
the "double indent" is caused by lines.map((i) => i ? indentNext + i : "")
the serialized snapshot is also modified by
addExtraLineBreaks(serialize(received and
prepareExpected( and
expect(actual.trim()).equals(expected ? expected.trim() : "");
which is not desired in my case
these could be made optional
// dont escape string snapshots
const stringSnapshotSerializer = {
serialize(val) {
return val
//return "string:" + val
},
test(val) {
return (typeof val == "string")
},
trim: false,
indent: false,
addExtraLineBreaks: false,
}
or add a new method toMatchStringSnapshot
patches/vitest+0.25.2.patch
diff --git a/node_modules/vitest/dist/chunk-runtime-chain.a0b441dc.js b/node_modules/vitest/dist/chunk-runtime-chain.a0b441dc.js
index 4323980..07ba202 100644
--- a/node_modules/vitest/dist/chunk-runtime-chain.a0b441dc.js
+++ b/node_modules/vitest/dist/chunk-runtime-chain.a0b441dc.js
@@ -1,3 +1,5 @@
+const debugSnaps = false;
+
import util$1 from 'util';
import { i as isObject, b as getCallLastIndex, s as slash, g as getWorkerState, c as getNames, d as assertTypes, e as getFullName, n as noop, f as isRunningInTest, h as isRunningInBenchmark } from './chunk-typecheck-constants.4891f22f.js';
import * as chai$2 from 'chai';
@@ -502,10 +504,18 @@ const removeExtraLineBreaks = (string) => string.length > 2 && string.startsWith
const escapeRegex = true;
const printFunctionName = false;
function serialize(val, indent = 2, formatOverrides = {}) {
+
+ debugSnaps && console.dir({
+ f: "serialize",
+ val,
+ plugins: getSerializers(),
+ })
+
return normalizeNewlines(
format_1(val, {
escapeRegex,
indent,
+ // expect.addSnapshotSerializer
plugins: getSerializers(),
printFunctionName,
...formatOverrides
@@ -547,6 +557,8 @@ ${snapshots.join("\n\n")}
));
}
function prepareExpected(expected) {
+ // dont prepare
+ return expected
function findStartIndent() {
var _a, _b;
const matchObject = /^( +)}\s+$/m.exec(expected || "");
@@ -607,6 +619,12 @@ async function saveInlineSnapshots(snapshots) {
const code = await promises.readFile(file, "utf8");
const s = new MagicString(code);
for (const snap of snaps) {
+
+ debugSnaps && console.dir({
+ f: "saveInlineSnapshots",
+ snap,
+ })
+
const index = posToNumber(code, snap);
replaceInlineSnap(code, s, index, snap.snapshot);
}
@@ -629,22 +647,52 @@ function replaceObjectSnap(code, s, index, newSnap) {
return true;
}
function prepareSnapString(snap, source, index) {
+
const lineIndex = numberToPos(source, index).line;
const line = source.split(lineSplitRE)[lineIndex - 1];
const indent = line.match(/^\s*/)[0] || "";
const indentNext = indent.includes(" ") ? `${indent} ` : `${indent} `;
- const lines = snap.trim().replace(/\\/g, "\\\\").split(/\n/g);
+ //const lines = snap.trim().replace(/\\/g, "\\\\").split(/\n/g);
+ // dont trim
+ const lines = snap.replace(/\\/g, "\\\\").split(/\n/g);
const isOneline = lines.length <= 1;
const quote = isOneline ? "'" : "`";
+
+ debugSnaps && console.dir({
+ f: "prepareSnapString",
+ snap,
+ line,
+ indent,
+ indentNext,
+ isOneline,
+ lines,
+ //source, index // test file source
+ })
+
+// add indentNext
+//${lines.map((i) => i ? indentNext + i : "").join("\n").replace(/`/g, "\\`").replace(/\${/g, "\\${")}
+// dont add indentNext
+
+// dont wrap
+return `${quote}${snap.replace(/`/g, "\\`").replace(/\${/g, "\\${")}${quote}`
+
+
if (isOneline)
return `'${lines.join("\n").replace(/'/g, "\\'")}'`;
else
return `${quote}
-${lines.map((i) => i ? indentNext + i : "").join("\n").replace(/`/g, "\\`").replace(/\${/g, "\\${")}
+${lines.join("\n").replace(/`/g, "\\`").replace(/\${/g, "\\${")}
${indent}${quote}`;
}
+
const startRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\S\s]*\*\/\s*|\/\/.*\s+)*\s*[\w_$]*(['"`\)])/m;
function replaceInlineSnap(code, s, index, newSnap) {
+
+ debugSnaps && console.dir({
+ f: "replaceInlineSnap",
+ newSnap,
+ })
+
const startMatch = startRegex.exec(code.slice(index));
if (!startMatch)
return replaceObjectSnap(code, s, index, newSnap);
@@ -742,6 +790,12 @@ ${JSON.stringify(stacks)}`
);
}
stack.column--;
+
+ debugSnaps && console.dir({
+ f: "SnapshotState._addSnapshot",
+ receivedSerialized,
+ })
+
this._inlineSnapshots.push({
snapshot: receivedSerialized,
...stack
@@ -770,8 +824,15 @@ ${JSON.stringify(stacks)}`
if ((this._dirty || this._uncheckedKeys.size) && !isEmpty) {
if (hasExternalSnapshots)
await saveSnapshotFile(this._snapshotData, this.snapshotPath);
- if (hasInlineSnapshots)
+ if (hasInlineSnapshots) {
+
+ debugSnaps && console.dir({
+ f: "SnapshotState.save",
+ _inlineSnapshots: this._inlineSnapshots,
+ })
+
await saveInlineSnapshots(this._inlineSnapshots);
+ }
status.saved = true;
} else if (!hasExternalSnapshots && fs.existsSync(this.snapshotPath)) {
if (this._updateSnapshot === "all")
@@ -807,7 +868,36 @@ ${JSON.stringify(stacks)}`
key = testNameToKey(testName, count);
if (!(isInline && this._snapshotData[key] !== void 0))
this._uncheckedKeys.delete(key);
- const receivedSerialized = addExtraLineBreaks(serialize(received, void 0, this._snapshotFormat));
+
+ debugSnaps && console.dir({
+ f: "sstate.match",
+ received,
+ })
+
+ /*
+ const serializer = getSerializers().find(s => s.test(received))
+ //const receivedSerialized = addExtraLineBreaks(serialize(received, void 0, this._snapshotFormat));
+ const receivedSerialized = serialize(received, void 0, this._snapshotFormat);
+ if (serializer?.addExtraLineBreaks != false) {
+ receivedSerialized = addExtraLineBreaks(receivedSerialized);
+ }
+ const expected = isInline ? inlineSnapshot : this._snapshotData[key];
+ //const expectedTrimmed = prepareExpected(expected);
+ let expectedTrimmed = expected;
+ if (serializer?.prepareExpected != false) {
+ expectedTrimmed = prepareExpected(expectedTrimmed);
+ }
+ //const pass = expectedTrimmed === prepareExpected(receivedSerialized);
+ let receivedSerializedTrimmed = receivedSerialized;
+ if (serializer?.prepareExpected != false) {
+ receivedSerializedTrimmed = prepareExpected(receivedSerializedTrimmed);
+ }
+ const pass = expectedTrimmed === receivedSerializedTrimmed;
+ */
+
+ //const receivedSerialized = addExtraLineBreaks(serialize(received, void 0, this._snapshotFormat));
+ const receivedSerialized = (serialize(received, void 0, this._snapshotFormat));
+
const expected = isInline ? inlineSnapshot : this._snapshotData[key];
const expectedTrimmed = prepareExpected(expected);
const pass = expectedTrimmed === prepareExpected(receivedSerialized);
@@ -931,6 +1021,13 @@ class SnapshotClient {
errorMessage
} = options;
let { received } = options;
+
+ debugSnaps && console.dir({
+ f: "SnapshotClient.assert",
+ received,
+ inlineSnapshot,
+ })
+
if (!test)
throw new Error("Snapshot cannot be used outside of test");
if (typeof properties === "object") {
@@ -960,13 +1057,19 @@ class SnapshotClient {
inlineSnapshot
});
if (!pass) {
+ debugSnaps && console.log(`SnapshotClient.assert: pass == false`)
try {
- expect(actual.trim()).equals(expected ? expected.trim() : "");
+ //expect(actual.trim()).equals(expected ? expected.trim() : "");
+ // dont trim
+ expect(actual).equals(expected ? expected : "");
} catch (error2) {
error2.message = errorMessage || `Snapshot \`${key || "unknown"}\` mismatched`;
throw error2;
}
}
+ else {
+ debugSnaps && console.log(`SnapshotClient.assert: pass == true`)
+ }
}
async saveCurrent() {
if (!this.snapshotState)
@@ -1040,8 +1143,16 @@ const SnapshotPlugin = (chai, utils) => {
inlineSnapshot = properties;
properties = void 0;
}
+ /*
if (inlineSnapshot)
inlineSnapshot = stripSnapshotIndentation(inlineSnapshot);
+ */
+ debugSnaps && console.dir({
+ f: "toMatchInlineSnapshot",
+ expected,
+ inlineSnapshot,
+ })
+
const errorMessage = utils.flag(this, "message");
getSnapshotClient().assert({
received: expected,
System Info
vitest: ^0.25.2 => 0.25.2
Used Package Manager
pnpm
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [X] The provided reproduction is a minimal reproducible example of the bug.
You can disable this in the pretty format options with escapeString
// vitest.config.ts
test: {
snapshotFormat: {
escapeString: false,
},
},
no, i still get
❯ test/basic.test.ts (3)
❯ suite name (3)
✓ snapshot
✓ string snapshot actual
× string snapshot expected
expected:
- "string snapshot actual" should fail
- "string snapshot expected" should pass
i tried both vite.config.ts and vitest.config.ts