Skip to content

Commit

Permalink
refactor: define matchers in separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed May 9, 2024
1 parent 8c96607 commit 7339029
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 24 deletions.
5 changes: 5 additions & 0 deletions packages/expect/src/assertions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import toMatchObject from './toMatchObject'

export default [
toMatchObject,
]
27 changes: 27 additions & 0 deletions packages/expect/src/assertions/toMatchObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { AssertionError, util } from 'chai'
import { defineAssertion } from '../utils'
import { getObjectSubset, iterableEquality, equals as jestEquals, subsetEquality } from '../jest-utils'
import { getCustomEqualityTesters } from '../jest-matcher-utils'

export default defineAssertion('toMatchObject', function (expected: unknown) {
const customTesters = getCustomEqualityTesters()
const actual = this._obj
const pass = jestEquals(actual, expected, [...customTesters, iterableEquality, subsetEquality])
const isNot = util.flag(this, 'negate') as boolean
const { subset: actualSubset, stripped } = getObjectSubset(actual, expected)
if ((pass && isNot) || (!pass && !isNot)) {
const msg = util.getMessage(
this,
[
pass,
'expected #{this} to match object #{exp}',
'expected #{this} to not match object #{exp}',
expected,
actualSubset,
false,
],
)
const message = stripped === 0 ? msg : `${msg}\n(${stripped} matching ${stripped === 1 ? 'property' : 'properties'} omitted from actual)`
throw new AssertionError(message, { showDiff: true, expected, actual: actualSubset })
}
})
31 changes: 9 additions & 22 deletions packages/expect/src/jest-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import type { MockInstance } from '@vitest/spy'
import { isMockFunction } from '@vitest/spy'
import type { Test } from '@vitest/runner'
import type { Assertion, ChaiPlugin } from './types'
import { arrayBufferEquality, generateToBeMessage, getObjectSubset, iterableEquality, equals as jestEquals, sparseArrayEquality, subsetEquality, typeEquality } from './jest-utils'
import { arrayBufferEquality, generateToBeMessage, iterableEquality, equals as jestEquals, sparseArrayEquality, typeEquality } from './jest-utils'
import type { AsymmetricMatcher } from './jest-asymmetric-matchers'
import { diff, getCustomEqualityTesters, stringify } from './jest-matcher-utils'
import { JEST_MATCHERS_OBJECT } from './constants'
import { recordAsyncExpect, wrapSoft } from './utils'

import matchers from './assertions/index'

// polyfill globals because expect can be used in node environment
declare class Node {
contains(item: unknown): boolean
Expand All @@ -19,12 +21,18 @@ declare class DOMTokenList {
contains(item: unknown): boolean
}

const defineJestMatchers: ChaiPlugin = (chai, utils) => {
matchers.forEach(define => define(chai, utils))
}

// Jest Expect Compact
export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
const { AssertionError } = chai
const c = () => getColors()
const customTesters = getCustomEqualityTesters()

defineJestMatchers(chai, utils)

function def(name: keyof Assertion | (keyof Assertion)[], fn: ((this: Chai.AssertionStatic & Assertion, ...args: any[]) => any)) {
const addMethod = (n: keyof Assertion) => {
const softWrapper = wrapSoft(utils, fn)
Expand Down Expand Up @@ -159,27 +167,6 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
actual,
)
})
def('toMatchObject', function (expected) {
const actual = this._obj
const pass = jestEquals(actual, expected, [...customTesters, iterableEquality, subsetEquality])
const isNot = utils.flag(this, 'negate') as boolean
const { subset: actualSubset, stripped } = getObjectSubset(actual, expected)
if ((pass && isNot) || (!pass && !isNot)) {
const msg = utils.getMessage(
this,
[
pass,
'expected #{this} to match object #{exp}',
'expected #{this} to not match object #{exp}',
expected,
actualSubset,
false,
],
)
const message = stripped === 0 ? msg : `${msg}\n(${stripped} matching ${stripped === 1 ? 'property' : 'properties'} omitted from actual)`
throw new AssertionError(message, { showDiff: true, expected, actual: actualSubset })
}
})
def('toMatch', function (expected: string | RegExp) {
const actual = this._obj as string
if (typeof actual !== 'string')
Expand Down
19 changes: 17 additions & 2 deletions packages/expect/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { processError } from '@vitest/utils/error'
import type { Test } from '@vitest/runner/types'
import { GLOBAL_EXPECT } from './constants'
import { GLOBAL_EXPECT, JEST_MATCHERS_OBJECT } from './constants'
import { getState } from './state'
import type { Assertion, MatcherState } from './types'
import type { Assertion, ChaiPlugin, MatcherState } from './types'

export function recordAsyncExpect(test: any, promise: Promise<any> | PromiseLike<any>) {
// record promise for test, that resolves before test ends
Expand Down Expand Up @@ -49,3 +49,18 @@ export function wrapSoft(utils: Chai.ChaiUtils, fn: (this: Chai.AssertionStatic
}
}
}

export function defineAssertion<T>(name: keyof Assertion, fn: (this: Omit<Chai.AssertionStatic & Assertion, '_obj'> & { _obj: T }, ...args: any) => any): ChaiPlugin {
return (chai, utils) => {
const addMethod = (n: keyof Assertion) => {
const softWrapper = wrapSoft(utils, fn)
utils.addMethod(chai.Assertion.prototype, n, softWrapper)
utils.addMethod((globalThis as any)[JEST_MATCHERS_OBJECT].matchers, n, softWrapper)
}

if (Array.isArray(name))
name.forEach(n => addMethod(n))
else
addMethod(name)
}
}

0 comments on commit 7339029

Please sign in to comment.