mirror of
https://github.com/hanzoai/vitest.git
synced 2026-09-24 17:04:25 +00:00
497 lines
14 KiB
TypeScript
497 lines
14 KiB
TypeScript
/**
|
||
* 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\`)
|
||
})
|
||
"
|
||
`)
|
||
})
|