diff --git a/packages/snapshot/src/port/inlineSnapshot.ts b/packages/snapshot/src/port/inlineSnapshot.ts index 59d6c5116239..fcdfb9fb22c4 100644 --- a/packages/snapshot/src/port/inlineSnapshot.ts +++ b/packages/snapshot/src/port/inlineSnapshot.ts @@ -50,7 +50,7 @@ export async function saveInlineSnapshots( } 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]))*\{/ + = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]))*\{/ function escapeRegExp(s: string): string { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') @@ -160,7 +160,7 @@ function getCodeStartingAtIndex(code: string, index: number, methodNames: string } 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$]*(['"`)])/ + = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]))*[\w$]*(['"`)])/ const buildStartRegex = memo((assertionName: string) => { const replaced = defaultStartRegex.source.replace( diff --git a/test/e2e/snapshots/fixtures/inline-multiple-calls/comment-directive.test.ts b/test/e2e/snapshots/fixtures/inline-multiple-calls/comment-directive.test.ts new file mode 100644 index 000000000000..d9b73dfd0153 --- /dev/null +++ b/test/e2e/snapshots/fixtures/inline-multiple-calls/comment-directive.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from 'vitest' + +test('block-comment directives, multiple calls', () => { + expect('alpha').toMatchInlineSnapshot(/* HTML */`"alpha"`) + expect('beta').toMatchInlineSnapshot(/* HTML */`"beta"`) + expect('gamma').toMatchInlineSnapshot(/* HTML */`"gamma"`) +}) diff --git a/test/e2e/snapshots/inline-comment-directive.test.ts b/test/e2e/snapshots/inline-comment-directive.test.ts new file mode 100644 index 000000000000..831add144731 --- /dev/null +++ b/test/e2e/snapshots/inline-comment-directive.test.ts @@ -0,0 +1,33 @@ +import fs from 'node:fs' +import { join } from 'pathe' +import { expect, test } from 'vitest' +import { editFile, runVitest } from '../../test-utils' + +// Regression for the greedy comment-skip in the inline-snapshot updater +// regexes (packages/snapshot/src/port/inlineSnapshot.ts). With multiple +// `toMatchInlineSnapshot(/* … */)` calls carrying block-comment directives +// in one file, the greedy `\/\*[\s\S]*\*\/` matched from the FIRST `/*` to +// the LAST `*/`, so every earlier call's write coalesced onto the last +// call's location. On update only the last snapshot was written, mangled +// (`/* HTML */`"alpha"``"beta"``"gamma"`), and the earlier calls were left +// empty — silently. The fix makes the comment-skip lazy (`[\s\S]*?`). + +test('multiple block-comment directives each get written on update', async () => { + const root = join(import.meta.dirname, 'fixtures/inline-multiple-calls') + const testFile = join(root, 'comment-directive.test.ts') + + // reset: clear each snapshot to the empty `(/* HTML */)` form that triggers + // the bug, keeping its directive + editFile(testFile, s => + s.replace(/toMatchInlineSnapshot\(\/\* HTML \*\/`[^`]*`\)/g, 'toMatchInlineSnapshot(/* HTML */)')) + + const vitest = await runVitest({ root, include: [testFile], update: true }) + expect(vitest.stderr).toBe('') + + // Each call gets its own snapshot, on its own line. Pre-fix, only the last + // (`gamma`) landed — and mangled — while `alpha`/`beta` stayed empty. + const content = fs.readFileSync(testFile, 'utf-8') + expect(content).toContain('toMatchInlineSnapshot(/* HTML */`"alpha"`)') + expect(content).toContain('toMatchInlineSnapshot(/* HTML */`"beta"`)') + expect(content).toContain('toMatchInlineSnapshot(/* HTML */`"gamma"`)') +})