Skip to content

Commit e145d57

Browse files
mayranghi-ogawa
andauthored
fix(snapshot): treat empty string as valid snapshot (#10188)
Co-authored-by: Hiroshi Ogawa <hi.ogawa.zz@gmail.com>
1 parent 9229f2e commit e145d57

16 files changed

Lines changed: 248 additions & 22 deletions

File tree

packages/snapshot/src/client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export class SnapshotClient {
241241
inlineSnapshot,
242242
})
243243
expectedSnapshot.markAsChecked()
244-
const matchResult = expectedSnapshot.data
244+
const matchResult = expectedSnapshot.data !== undefined
245245
? adapter.match(captured, adapter.parseExpected(expectedSnapshot.data))
246246
: undefined
247247
const { actual, expected, key, pass } = snapshotState.processDomainSnapshot({
@@ -291,7 +291,7 @@ export class SnapshotClient {
291291
inlineSnapshot,
292292
})
293293

294-
const reference = expectedSnapshot.data && snapshotState.snapshotUpdateState !== 'all'
294+
const reference = expectedSnapshot.data !== undefined && snapshotState.snapshotUpdateState !== 'all'
295295
? adapter.parseExpected(expectedSnapshot.data)
296296
: undefined
297297
const timedOut = timeout > 0
@@ -309,7 +309,7 @@ export class SnapshotClient {
309309

310310
expectedSnapshot.markAsChecked()
311311

312-
if (!stableResult?.rendered) {
312+
if (stableResult?.rendered === undefined) {
313313
// the original caller `expect.poll` later manipulates error via `throwWithCause`,
314314
// so here we can directly throw `lastPollError` if exists.
315315
if (stableResult?.lastPollError) {
@@ -324,7 +324,7 @@ export class SnapshotClient {
324324
// TODO: should `all` mode ignore parse error?
325325
// Sielently hiding the error and creating snaphsot full scratch isn't good either.
326326
// Users can fix or purge the broken snapshot manually and that decision affects how domain snapshot gets updated.
327-
const matchResult = expectedSnapshot.data
327+
const matchResult = expectedSnapshot.data !== undefined
328328
? adapter.match(stableResult.captured, adapter.parseExpected(expectedSnapshot.data))
329329
: undefined
330330
const { actual, expected, key, pass } = snapshotState.processDomainSnapshot({

packages/snapshot/src/port/state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ export default class SnapshotState {
580580
key: expectedSnapshot.key,
581581
count: expectedSnapshot.count,
582582
pass: matchResult?.pass ?? false,
583-
hasSnapshot: !!expectedSnapshot.data,
583+
hasSnapshot: expectedSnapshot.data !== undefined,
584584
snapshotIsPersisted: isInline ? true : this._fileExists,
585585
addValue: actualResolved,
586586
actualDisplay: removeExtraLineBreaks(actualResolved),

packages/vitest/src/integrations/snapshot/chai.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ function toMatchDomainSnapshotImpl(opts: {
196196
const test = getTest(assertion)
197197

198198
let { inlineSnapshot } = opts
199-
if (inlineSnapshot) {
199+
if (inlineSnapshot !== undefined) {
200200
inlineSnapshot = stripSnapshotIndentation(inlineSnapshot)
201201
}
202202

test/snapshots/test/custom-serializers.test.ts

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
13
import { expect, test } from 'vitest'
2-
import { runVitest } from '../../test-utils'
4+
import { editFile, runVitest } from '../../test-utils'
5+
import { readInlineSnapshots } from './utils'
36

47
test('it should pass', async () => {
58
const { stdout, stderr } = await runVitest({
@@ -9,3 +12,126 @@ test('it should pass', async () => {
912
expect(stdout).toContain('✓ custom-serializers.test.ts')
1013
expect(stderr).toBe('')
1114
})
15+
16+
test('empty serializer output', async () => {
17+
const root = path.join(import.meta.dirname, 'fixtures/custom-serializers-empty')
18+
const testFile = path.join(root, 'basic.test.ts')
19+
const snapshotFile = path.join(root, '__snapshots__/basic.test.ts.snap')
20+
21+
// clean slate
22+
fs.rmSync(path.join(root, '__snapshots__'), { recursive: true, force: true })
23+
editFile(testFile, s => s
24+
.replace(/toMatchInlineSnapshot\(`[^`]*`/g, 'toMatchInlineSnapshot('))
25+
26+
let result = await runVitest({
27+
root,
28+
update: 'all',
29+
})
30+
expect(result.stderr).toMatchInlineSnapshot(`""`)
31+
expect(result.errorTree()).toMatchInlineSnapshot(`
32+
Object {
33+
"basic.test.ts": Object {
34+
"file empty": "passed",
35+
"file whitespaces": "passed",
36+
"inline empty": "passed",
37+
"inline whitespaces": "passed",
38+
},
39+
}
40+
`)
41+
expect(fs.readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
42+
"// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
43+
44+
exports[\`file empty 1\`] = \`\`;
45+
46+
exports[\`file whitespaces 1\`] = \` \`;
47+
"
48+
`)
49+
expect(readInlineSnapshots(testFile)).toMatchInlineSnapshot(`
50+
"
51+
expect({ __unwrap__: "" }).toMatchInlineSnapshot(\`\`)
52+
53+
expect({ __unwrap__: " ".repeat(4) }).toMatchInlineSnapshot(\`\`)
54+
"
55+
`)
56+
57+
result = await runVitest({
58+
root,
59+
update: 'none',
60+
})
61+
expect(result.stderr).toMatchInlineSnapshot(`""`)
62+
expect(result.errorTree()).toMatchInlineSnapshot(`
63+
Object {
64+
"basic.test.ts": Object {
65+
"file empty": "passed",
66+
"file whitespaces": "passed",
67+
"inline empty": "passed",
68+
"inline whitespaces": "passed",
69+
},
70+
}
71+
`)
72+
73+
// TODO: snapshot comparison normalizes whitespaces. probably hard to fix.
74+
editFile(testFile, s => s
75+
.replace(`__unwrap__: " ".repeat(4)`, `__unwrap__: " ".repeat(8)`))
76+
result = await runVitest({
77+
root,
78+
update: 'none',
79+
})
80+
expect(result.stderr).toMatchInlineSnapshot(`""`)
81+
expect(result.errorTree()).toMatchInlineSnapshot(`
82+
Object {
83+
"basic.test.ts": Object {
84+
"file empty": "passed",
85+
"file whitespaces": "passed",
86+
"inline empty": "passed",
87+
"inline whitespaces": "passed",
88+
},
89+
}
90+
`)
91+
expect(fs.readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
92+
"// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
93+
94+
exports[\`file empty 1\`] = \`\`;
95+
96+
exports[\`file whitespaces 1\`] = \` \`;
97+
"
98+
`)
99+
expect(readInlineSnapshots(testFile)).toMatchInlineSnapshot(`
100+
"
101+
expect({ __unwrap__: "" }).toMatchInlineSnapshot(\`\`)
102+
103+
expect({ __unwrap__: " ".repeat(4) }).toMatchInlineSnapshot(\`\`)
104+
"
105+
`)
106+
107+
result = await runVitest({
108+
root,
109+
update: 'all',
110+
})
111+
expect(result.stderr).toMatchInlineSnapshot(`""`)
112+
expect(result.errorTree()).toMatchInlineSnapshot(`
113+
Object {
114+
"basic.test.ts": Object {
115+
"file empty": "passed",
116+
"file whitespaces": "passed",
117+
"inline empty": "passed",
118+
"inline whitespaces": "passed",
119+
},
120+
}
121+
`)
122+
expect(fs.readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
123+
"// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
124+
125+
exports[\`file empty 1\`] = \`\`;
126+
127+
exports[\`file whitespaces 1\`] = \` \`;
128+
"
129+
`)
130+
expect(readInlineSnapshots(testFile)).toMatchInlineSnapshot(`
131+
"
132+
expect({ __unwrap__: "" }).toMatchInlineSnapshot(\`\`)
133+
134+
expect({ __unwrap__: " ".repeat(4) }).toMatchInlineSnapshot(\`\`)
135+
"
136+
`)
137+
})

test/snapshots/test/domain-inline.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ test('domain inline snapshot', async () => {
1818
Object {
1919
"basic.test.ts": Object {
2020
"all literal": "passed",
21+
"empty snapshot": "passed",
2122
"with regex": "passed",
2223
},
2324
}
@@ -34,6 +35,8 @@ test('domain inline snapshot', async () => {
3435
score=999
3536
status=active
3637
\`)
38+
39+
expect({}).toMatchKvInlineSnapshot(\`\`)
3740
"
3841
`)
3942

@@ -49,6 +52,7 @@ test('domain inline snapshot', async () => {
4952
Object {
5053
"basic.test.ts": Object {
5154
"all literal": "passed",
55+
"empty snapshot": "passed",
5256
"with regex": "passed",
5357
},
5458
}
@@ -93,6 +97,7 @@ test('domain inline snapshot', async () => {
9397
Object {
9498
"basic.test.ts": Object {
9599
"all literal": "passed",
100+
"empty snapshot": "passed",
96101
"with regex": Array [
97102
"Snapshot \`with regex 1\` mismatched",
98103
],
@@ -108,6 +113,7 @@ test('domain inline snapshot', async () => {
108113
Object {
109114
"basic.test.ts": Object {
110115
"all literal": "passed",
116+
"empty snapshot": "passed",
111117
"with regex": "passed",
112118
},
113119
}
@@ -127,6 +133,8 @@ test('domain inline snapshot', async () => {
127133
score=/\\\\d+/
128134
status=inactive
129135
\`)
136+
137+
expect({}).toMatchKvInlineSnapshot(\`\`)
130138
"
131139
`)
132140
})

test/snapshots/test/domain-poll-inline.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ test('domain inline snapshot with poll', async () => {
1717
expect(result.errorTree()).toMatchInlineSnapshot(`
1818
Object {
1919
"basic.test.ts": Object {
20+
"empty snapshot": "passed",
2021
"multiple poll snapshots": "passed",
2122
"non-poll alongside poll": "passed",
2223
"stable": "passed",
@@ -67,6 +68,10 @@ test('domain inline snapshot with poll', async () => {
6768
}, { interval: 10 }).toMatchKvInlineSnapshot(\`polled=value\`)
6869
6970
expect({ another: 'static' }).toMatchKvInlineSnapshot(\`another=static\`)
71+
72+
expect.poll(() => {
73+
return {}
74+
}, { interval: 10 }).toMatchKvInlineSnapshot(\`\`)
7075
"
7176
`)
7277

@@ -76,6 +81,7 @@ test('domain inline snapshot with poll', async () => {
7681
expect(result.errorTree()).toMatchInlineSnapshot(`
7782
Object {
7883
"basic.test.ts": Object {
84+
"empty snapshot": "passed",
7985
"multiple poll snapshots": "passed",
8086
"non-poll alongside poll": "passed",
8187
"stable": "passed",
@@ -122,6 +128,7 @@ test('domain inline snapshot with poll', async () => {
122128
expect(result.errorTree()).toMatchInlineSnapshot(`
123129
Object {
124130
"basic.test.ts": Object {
131+
"empty snapshot": "passed",
125132
"multiple poll snapshots": "passed",
126133
"non-poll alongside poll": "passed",
127134
"stable": Array [
@@ -139,6 +146,7 @@ test('domain inline snapshot with poll', async () => {
139146
expect(result.errorTree()).toMatchInlineSnapshot(`
140147
Object {
141148
"basic.test.ts": Object {
149+
"empty snapshot": "passed",
142150
"multiple poll snapshots": "passed",
143151
"non-poll alongside poll": "passed",
144152
"stable": "passed",
@@ -189,6 +197,10 @@ test('domain inline snapshot with poll', async () => {
189197
}, { interval: 10 }).toMatchKvInlineSnapshot(\`polled=value\`)
190198
191199
expect({ another: 'static' }).toMatchKvInlineSnapshot(\`another=static\`)
200+
201+
expect.poll(() => {
202+
return {}
203+
}, { interval: 10 }).toMatchKvInlineSnapshot(\`\`)
192204
"
193205
`)
194206

@@ -201,6 +213,7 @@ test('domain inline snapshot with poll', async () => {
201213
expect(result.errorTree()).toMatchInlineSnapshot(`
202214
Object {
203215
"basic.test.ts": Object {
216+
"empty snapshot": "passed",
204217
"multiple poll snapshots": "passed",
205218
"non-poll alongside poll": "passed",
206219
"stable": "passed",
@@ -251,6 +264,10 @@ test('domain inline snapshot with poll', async () => {
251264
}, { interval: 10 }).toMatchKvInlineSnapshot(\`polled=value\`)
252265
253266
expect({ another: 'static' }).toMatchKvInlineSnapshot(\`another=static\`)
267+
268+
expect.poll(() => {
269+
return {}
270+
}, { interval: 10 }).toMatchKvInlineSnapshot(\`\`)
254271
"
255272
`)
256273
})

test/snapshots/test/domain-poll.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ test('domain snapshot with poll', async () => {
4040
expect(result.errorTree()).toMatchInlineSnapshot(`
4141
Object {
4242
"basic.test.ts": Object {
43+
"empty snapshot": "passed",
4344
"multiple poll snapshots": "passed",
4445
"non-poll alongside poll": "passed",
4546
"stable": "passed",
@@ -51,6 +52,8 @@ test('domain snapshot with poll', async () => {
5152
expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
5253
"// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
5354
55+
exports[\`empty snapshot 1\`] = \`\`;
56+
5457
exports[\`multiple poll snapshots 1\`] = \`
5558
x=1
5659
\`;
@@ -131,6 +134,7 @@ test('domain snapshot with poll', async () => {
131134
expect(result.errorTree()).toMatchInlineSnapshot(`
132135
Object {
133136
"basic.test.ts": Object {
137+
"empty snapshot": "passed",
134138
"multiple poll snapshots": "passed",
135139
"non-poll alongside poll": "passed",
136140
"stable": Array [
@@ -149,6 +153,7 @@ test('domain snapshot with poll', async () => {
149153
expect(result.errorTree()).toMatchInlineSnapshot(`
150154
Object {
151155
"basic.test.ts": Object {
156+
"empty snapshot": "passed",
152157
"multiple poll snapshots": "passed",
153158
"non-poll alongside poll": "passed",
154159
"stable": "passed",
@@ -160,6 +165,8 @@ test('domain snapshot with poll', async () => {
160165
expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
161166
"// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
162167
168+
exports[\`empty snapshot 1\`] = \`\`;
169+
163170
exports[\`multiple poll snapshots 1\`] = \`
164171
x=1
165172
\`;
@@ -206,6 +213,7 @@ test('domain snapshot with poll', async () => {
206213
expect(result.errorTree()).toMatchInlineSnapshot(`
207214
Object {
208215
"basic.test.ts": Object {
216+
"empty snapshot": "passed",
209217
"multiple poll snapshots": "passed",
210218
"non-poll alongside poll": "passed",
211219
"stable": "passed",
@@ -217,6 +225,8 @@ test('domain snapshot with poll', async () => {
217225
expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
218226
"// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
219227
228+
exports[\`empty snapshot 1\`] = \`\`;
229+
220230
exports[\`multiple poll snapshots 1\`] = \`
221231
x=1
222232
\`;

0 commit comments

Comments
 (0)