Files
2026-05-11 17:46:13 +02:00

81 lines
2.6 KiB
TypeScript

import type { DomainMatchResult, DomainSnapshotAdapter } from 'vitest'
// Key-value domain adapter: each snapshot is multiple lines of `key=value`.
// Values can be literal strings or `/regex/` patterns in the stored snapshot.
// On match, each line is checked independently — regex lines use RegExp.test().
// On partial match, `mergedExpected` preserves regex for matched lines
// and substitutes literal rendered values for unmatched lines.
type KVCaptured = Record<string, string>
type KVExpected = Record<string, string | RegExp>
function renderKV(obj: Record<string, unknown>) {
const result = Object.entries(obj).map(([k, v]) => `${k}=${v}`).join('\n');
return result ? `\n${result}\n` : ''
}
export const kvAdapter: DomainSnapshotAdapter<KVCaptured, KVExpected> = {
name: 'kv',
capture(received: unknown): KVCaptured {
if (received && typeof received === 'object') {
return Object.fromEntries(Object.entries(received).map(([k, v]) => [k, String(v)]))
}
throw new TypeError('kv adapter expects a plain object')
},
render(captured: KVCaptured): string {
return renderKV(captured)
},
parseExpected(input: string): KVExpected {
if (!input.trim()) {
return {}
}
const entries = input.trim().split('\n').map((line) => {
const eq = line.indexOf('=')
if (eq === -1) {
throw new Error(`Invalid KV Format: '${line}'`)
}
const key = line.slice(0, eq)
const raw = line.slice(eq + 1)
const value = (raw.startsWith('/') && raw.endsWith('/') && raw.length > 1)
? new RegExp(raw.slice(1, -1))
: raw
return [key, value]
})
return Object.fromEntries(entries)
},
match(captured: KVCaptured, expected: KVExpected): DomainMatchResult {
const resolvedLines: string[] = []
let pass = true
// iterate on `expected` side so extra key on `actual` side
// is ignored and works as subset match
for (const [key, expectedValue] of Object.entries(expected)) {
const actualValue = captured[key]
if (actualValue === undefined) {
pass = false
continue
}
// preserve matched pattern for normalized error diff and partial update
if (expectedValue instanceof RegExp && expectedValue.test(actualValue)) {
resolvedLines.push(`${key}=/${expectedValue.source}/`)
continue
}
resolvedLines.push(`${key}=${actualValue}`)
pass &&= actualValue === expectedValue
}
return {
pass,
message: pass ? undefined : 'KV entries do not match',
resolved: `\n${resolvedLines.join('\n')}\n`,
expected: `\n${renderKV(expected)}\n`,
}
},
}