/** * pollAssertDomain behavior matrix * * Stability requirement: poll must produce two consecutive identical renders. * When a reference exists (and not `update: all`), the stable value must also * pass `adapter.match` — otherwise the baseline resets and polling continues * through intermediate stable states. * * Poll behavior | no reference | has reference (match) | has reference (mismatch) * -----------------------|----------------------|------------------------|------------------------- * stable immediately | new: pass (creates) | none/new/all: pass | none/new: fail (diff) * | none: fail (missing) | | all: pass (rewrites) * | all: pass (creates) | | * throw then stable | same as above | pass (throw resets | same as above * | | baseline, re-polls) | * transitional → stable | same as above | pass (rides through | same as above * | | intermediate states) | * stable wrong → right | n/a (no ref to | pass (match rejects | n/a * | reject against) | wrong, rides through)| * never stabilizes | fail (unstable) | fail (unstable) | fail (unstable) * always throws | fail (unstable, | fail (unstable, | fail (unstable, * | cause: poll error) | cause: poll error) | cause: poll error) * poll hangs | fail (unstable) | fail (unstable) | fail (unstable) */ import fs, { readFileSync } from 'node:fs' import { join } from 'node:path' import { expect, test } from 'vitest' import { editFile, runInlineTests, runVitest } from '../../test-utils' test('domain snapshot with poll', async () => { const root = join(import.meta.dirname, 'fixtures/domain-poll') const snapshotFile = join(root, '__snapshots__/basic.test.ts.snap') // clean slate fs.rmSync(join(root, '__snapshots__'), { recursive: true, force: true }) // --- create snapshots (update: new) --- let result = await runVitest({ root, update: 'new' }) expect(result.stderr).toMatchInlineSnapshot(`""`) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "empty snapshot": "passed", "multiple poll snapshots": "passed", "non-poll alongside poll": "passed", "stable": "passed", "throw then stable": "passed", "unstable then stable": "passed", }, } `) expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(` "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[\`empty snapshot 1\`] = \`\`; exports[\`multiple poll snapshots 1\`] = \` x=1 \`; exports[\`multiple poll snapshots 2\`] = \` y=2 \`; exports[\`non-poll alongside poll 1\`] = \` static=value \`; exports[\`non-poll alongside poll 2\`] = \` polled=value \`; exports[\`non-poll alongside poll 3\`] = \` another=static \`; exports[\`stable 1\`] = \` name=a age=23 \`; exports[\`throw then stable 1\`] = \` name=b age=23 \`; exports[\`unstable then stable 1\`] = \` status=done \`; " `) // --- re-run unchanged (update: none) --- const result2 = await runVitest({ root, update: 'none' }) expect(result2.stderr).toBe(result.stderr) expect(result2.errorTree()).toEqual(result.errorTree()) // --- mismatch — stable on wrong value --- // Edit reference so "stable" poll stabilizes but doesn't match // Should produce a mismatch error with diff, not "unstable" error editFile(snapshotFile, s => s .replace('name=a\n', 'name=a-changed\n')) result = await runVitest({ root, update: 'none' }) expect(result.stderr).toMatchInlineSnapshot(` " ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ FAIL basic.test.ts > stable Error: Snapshot \`stable 1\` mismatched - Expected + Received - name=a-changed + name=a age=23 ❯ basic.test.ts:9:24 7| trial++ 8| return { name: 'a', age: '23' } 9| }, { interval: 10 }).toMatchKvSnapshot() | ^ 10| expect(trial).toBe(2) 11| }) Caused by: Error: Matcher did not succeed in time. ❯ basic.test.ts:6:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ " `) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "empty snapshot": "passed", "multiple poll snapshots": "passed", "non-poll alongside poll": "passed", "stable": [ "Snapshot \`stable 1\` mismatched", ], "throw then stable": "passed", "unstable then stable": "passed", }, } `) // --- update mode (update: all) --- // Rewrite snapshots with current stable values result = await runVitest({ root, update: 'all' }) expect(result.stderr).toMatchInlineSnapshot(`""`) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "empty snapshot": "passed", "multiple poll snapshots": "passed", "non-poll alongside poll": "passed", "stable": "passed", "throw then stable": "passed", "unstable then stable": "passed", }, } `) expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(` "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[\`empty snapshot 1\`] = \`\`; exports[\`multiple poll snapshots 1\`] = \` x=1 \`; exports[\`multiple poll snapshots 2\`] = \` y=2 \`; exports[\`non-poll alongside poll 1\`] = \` static=value \`; exports[\`non-poll alongside poll 2\`] = \` polled=value \`; exports[\`non-poll alongside poll 3\`] = \` another=static \`; exports[\`stable 1\`] = \` name=a age=23 \`; exports[\`throw then stable 1\`] = \` name=b age=23 \`; exports[\`unstable then stable 1\`] = \` status=done \`; " `) // --- pattern-preserving update --- // Inject regex pattern into snapshot, verify --update preserves it editFile(snapshotFile, s => s .replace('name=a\n', 'name=/\\\\w/\n')) result = await runVitest({ root, update: 'all' }) expect(result.stderr).toMatchInlineSnapshot(`""`) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "empty snapshot": "passed", "multiple poll snapshots": "passed", "non-poll alongside poll": "passed", "stable": "passed", "throw then stable": "passed", "unstable then stable": "passed", }, } `) expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(` "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[\`empty snapshot 1\`] = \`\`; exports[\`multiple poll snapshots 1\`] = \` x=1 \`; exports[\`multiple poll snapshots 2\`] = \` y=2 \`; exports[\`non-poll alongside poll 1\`] = \` static=value \`; exports[\`non-poll alongside poll 2\`] = \` polled=value \`; exports[\`non-poll alongside poll 3\`] = \` another=static \`; exports[\`stable 1\`] = \` name=/\\\\w/ age=23 \`; exports[\`throw then stable 1\`] = \` name=b age=23 \`; exports[\`unstable then stable 1\`] = \` status=done \`; " `) }) test('poll until stable match when "none"', async () => { const result = await runInlineTests({ '__snapshots__/basic.test.ts.snap': `\ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[\`stable wrong then right 1\`] = \` phase=complete \`; `, 'basic.test.ts': ` import { expect, test } from 'vitest' import '../snapshots/fixtures/domain/basic-extend' test('stable wrong then right', async () => { let trial = 0 await expect.poll(() => { trial++ if (trial <= 4) return { phase: 'pending' } return { phase: 'complete' } }, { interval: 10 }).toMatchKvSnapshot() expect(trial).toBe(6) }) `, }, { update: 'none', }) expect(result.stderr).toMatchInlineSnapshot(`""`) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "stable wrong then right": "passed", }, } `) }) test('poll until stable when "all"', async () => { const result = await runInlineTests({ '__snapshots__/basic.test.ts.snap': `\ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[\`stable wrong then right 1\`] = \` phase=complete \`; `, 'basic.test.ts': ` import { expect, test } from 'vitest' import '../snapshots/fixtures/domain/basic-extend' test('stable wrong then right', async () => { let trial = 0 await expect.poll(() => { trial++ if (trial <= 4) return { phase: 'pending' } return { phase: 'complete' } }, { interval: 10 }).toMatchKvSnapshot() expect(trial).toBe(2) }) `, }, { update: 'all', }) expect(result.stderr).toMatchInlineSnapshot(`""`) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "stable wrong then right": "passed", }, } `) expect(result.fs.readFile('__snapshots__/basic.test.ts.snap')).toMatchInlineSnapshot(` "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[\`stable wrong then right 1\`] = \` phase=pending \`; " `) }) test('errors', async () => { const result = await runInlineTests({ 'basic.test.ts': ` import { expect, test } from 'vitest' import '../snapshots/fixtures/domain/basic-extend' test('unstable', async () => { let trial = 0 await expect.poll(() => { trial++ return { name: 'x', counter: String(trial) } }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() }) test('hanging', async () => { let trial = 0 await expect.poll(() => { trial++ return new Promise(() => {}) }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() }) test('throwing', async () => { let trial = 0 await expect.poll(() => { trial++ throw new Error("ALWAYS_THROWS") }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() }) `, }, { update: 'all', }) expect(result.stderr).toMatchInlineSnapshot(` " ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯ FAIL basic.test.ts > unstable Error: poll() did not produce a stable snapshot within the timeout ❯ basic.test.ts:10:38 8| trial++ 9| return { name: 'x', counter: String(trial) } 10| }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() | ^ 11| }) 12| Caused by: Error: Matcher did not succeed in time. ❯ basic.test.ts:7:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯ FAIL basic.test.ts > hanging Error: poll() did not produce a stable snapshot within the timeout ❯ basic.test.ts:18:38 16| trial++ 17| return new Promise(() => {}) 18| }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() | ^ 19| }) 20| Caused by: Error: Matcher did not succeed in time. ❯ basic.test.ts:15:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯ FAIL basic.test.ts > throwing Error: ALWAYS_THROWS ❯ basic.test.ts:26:38 24| trial++ 25| throw new Error("ALWAYS_THROWS") 26| }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() | ^ 27| }) 28| Caused by: Error: Matcher did not succeed in time. ❯ basic.test.ts:23:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯ " `) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "hanging": [ "poll() did not produce a stable snapshot within the timeout", ], "throwing": [ "ALWAYS_THROWS", ], "unstable": [ "poll() did not produce a stable snapshot within the timeout", ], }, } `) }) test('signal', async () => { const result = await runInlineTests({ 'basic.test.ts': ` import '../snapshots/fixtures/domain/basic-extend' test('signal', async () => { let aborted = false await expect( expect.poll(({ signal }) => { signal.addEventListener('abort', () => { aborted = true }) return new Promise(() => {}) }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() ).rejects.toThrowErrorMatchingInlineSnapshot() expect(aborted).toMatchInlineSnapshot() }) `, }, { globals: true, update: 'all', }) expect(result.stderr).toMatchInlineSnapshot(`""`) expect(result.errorTree()).toMatchInlineSnapshot(` { "basic.test.ts": { "signal": "passed", }, } `) expect(result.fs.readFile('basic.test.ts')).toMatchInlineSnapshot(` " import '../snapshots/fixtures/domain/basic-extend' test('signal', async () => { let aborted = false await expect( expect.poll(({ signal }) => { signal.addEventListener('abort', () => { aborted = true }) return new Promise(() => {}) }, { timeout: 100, interval: 10 }).toMatchKvSnapshot() ).rejects.toThrowErrorMatchingInlineSnapshot(\`[Error: poll() did not produce a stable snapshot within the timeout]\`) expect(aborted).toMatchInlineSnapshot(\`true\`) }) " `) })