Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194import type { Test } from '../../vitest/src/runtime/runner/types'import type { Assertion, ChaiPlugin, ExpectStatic, MatchersObject, MatcherState, SyncExpectationResult,} from './types'import { use, util } from 'chai'import { ASYMMETRIC_MATCHERS_OBJECT, JEST_MATCHERS_OBJECT } from './constants'import { AsymmetricMatcher } from './jest-asymmetric-matchers'import { diff, getCustomEqualityTesters, getMatcherUtils, stringify } from './jest-matcher-utils'import { equals, iterableEquality, subsetEquality } from './jest-utils'import { getState } from './state'import { wrapAssertion } from './utils'
function getMatcherState(assertion: Chai.AssertionStatic & Assertion, expect: ExpectStatic) { const obj = assertion._obj const isNot = util.flag(assertion, 'negate') as boolean const promise = util.flag(assertion, 'promise') || '' const customMessage = util.flag(assertion, 'message') as string | undefined const jestUtils = { ...getMatcherUtils(), diff, stringify, iterableEquality, subsetEquality, } let task: Test | undefined = util.flag(assertion, 'vitest-test') const currentTestName = task?.fullTestName ?? ''
if (task?.type !== 'test') { task = undefined }
const matcherState: MatcherState = { ...getState(expect), currentTestName, customTesters: getCustomEqualityTesters(), isNot, utils: jestUtils, promise, equals, // needed for built-in jest-snapshots, but we don't use it suppressedErrors: [], soft: util.flag(assertion, 'soft') as boolean | undefined, poll: util.flag(assertion, 'poll') as boolean | undefined, assertion: assertion as any, } Object.assign(matcherState, { task })
return { state: matcherState, isNot, obj, customMessage, }}
interface VitestErrorContext { assertionName: string meta?: object}
class JestExtendError extends Error { constructor( message: string, public actual?: any, public expected?: any, /** @internal */ public __vitest_error_context__?: VitestErrorContext, ) { super(message) }}
function JestExtendPlugin( c: Chai.ChaiStatic, expect: ExpectStatic, matchers: MatchersObject,): ChaiPlugin { return (_, utils) => { Object.entries(matchers).forEach(([expectAssertionName, expectAssertion]) => { function __VITEST_EXTEND_ASSERTION__(this: Chai.AssertionStatic & Assertion, ...args: any[]) { const { state, isNot, obj, customMessage } = getMatcherState(this, expect)
const result = expectAssertion.call(state, obj, ...args)
if (result && typeof result === 'object' && typeof (result as any).then === 'function') { const thenable = result as PromiseLike<SyncExpectationResult> return thenable.then(({ pass, message, actual, expected, meta }) => { if ((pass && isNot) || (!pass && !isNot)) { const errorMessage = (customMessage ? `${customMessage}: ` : '') + message() throw new JestExtendError(errorMessage, actual, expected, { assertionName: expectAssertionName, meta, }) } }) }
const { pass, message, actual, expected, meta } = result as SyncExpectationResult
if ((pass && isNot) || (!pass && !isNot)) { const errorMessage = (customMessage ? `${customMessage}: ` : '') + message() throw new JestExtendError(errorMessage, actual, expected, { assertionName: expectAssertionName, meta, }) } }
const softWrapper = wrapAssertion(utils, expectAssertionName, __VITEST_EXTEND_ASSERTION__) utils.addMethod( (globalThis as any)[JEST_MATCHERS_OBJECT].matchers, expectAssertionName, softWrapper, ) utils.addMethod(c.Assertion.prototype, expectAssertionName, softWrapper)
// `expect.poll()` inspects the installed Chai assertion method, // so copy the internal marker from the original matcher function. // this is only for domain snapshot matchers for now. if ((expectAssertion as any).__vitest_poll_takeover__) { const addedMethod = (c.Assertion.prototype as any)[expectAssertionName] Object.defineProperty(addedMethod, '__vitest_poll_takeover__', { value: true, }) }
class CustomMatcher extends AsymmetricMatcher<[unknown, ...unknown[]]> { constructor(inverse = false, ...sample: [unknown, ...unknown[]]) { super(sample, inverse) }
asymmetricMatch(other: unknown) { const { pass } = expectAssertion.call( this.getMatcherContext(expect), other, ...this.sample, ) as SyncExpectationResult
return this.inverse ? !pass : pass }
toString() { return `${this.inverse ? 'not.' : ''}${expectAssertionName}` }
getExpectedType() { return 'any' }
toAsymmetricMatcher() { return `${this.toString()}<${this.sample.map((item) => stringify(item)).join(', ')}>` } }
const customMatcher = (...sample: [unknown, ...unknown[]]) => new CustomMatcher(false, ...sample)
Object.defineProperty(expect, expectAssertionName, { configurable: true, enumerable: true, value: customMatcher, writable: true, })
Object.defineProperty(expect.not, expectAssertionName, { configurable: true, enumerable: true, value: (...sample: [unknown, ...unknown[]]) => new CustomMatcher(true, ...sample), writable: true, })
// keep track of asymmetric matchers on global so that it can be copied over to local context's `expect`. // note that the negated variant is automatically shared since it's assigned on the single `expect.not` object. Object.defineProperty((globalThis as any)[ASYMMETRIC_MATCHERS_OBJECT], expectAssertionName, { configurable: true, enumerable: true, value: customMatcher, writable: true, }) }) }}
export const JestExtend: ChaiPlugin = (chai, utils) => { utils.addMethod(chai.expect, 'extend', (expect: ExpectStatic, expects: MatchersObject) => { use(JestExtendPlugin(chai, expect, expects)) })}