Permalink
Cannot retrieve contributors at this time
| /** | |
| * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. | |
| * | |
| * This source code is licensed under the MIT license found in the | |
| * LICENSE file in the root directory of this source tree. | |
| * | |
| */ | |
| /* eslint-disable local/ban-types-eventually */ | |
| import getType = require('jest-get-type'); | |
| import { | |
| DIM_COLOR, | |
| EXPECTED_COLOR, | |
| MatcherHintOptions, | |
| RECEIVED_COLOR, | |
| SUGGEST_TO_CONTAIN_EQUAL, | |
| ensureExpectedIsNonNegativeInteger, | |
| ensureNoExpected, | |
| ensureNumbers, | |
| getLabelPrinter, | |
| matcherErrorMessage, | |
| matcherHint, | |
| printDiffOrStringify, | |
| printExpected, | |
| printReceived, | |
| printWithType, | |
| stringify, | |
| } from 'jest-matcher-utils'; | |
| import {equals} from './jasmineUtils'; | |
| import { | |
| printCloseTo, | |
| printExpectedConstructorName, | |
| printExpectedConstructorNameNot, | |
| printReceivedArrayContainExpectedItem, | |
| printReceivedConstructorName, | |
| printReceivedConstructorNameNot, | |
| printReceivedStringContainExpectedResult, | |
| printReceivedStringContainExpectedSubstring, | |
| } from './print'; | |
| import type {MatcherState, MatchersObject} from './types'; | |
| import { | |
| getObjectSubset, | |
| getPath, | |
| iterableEquality, | |
| sparseArrayEquality, | |
| subsetEquality, | |
| typeEquality, | |
| } from './utils'; | |
| // Omit colon and one or more spaces, so can call getLabelPrinter. | |
| const EXPECTED_LABEL = 'Expected'; | |
| const RECEIVED_LABEL = 'Received'; | |
| const EXPECTED_VALUE_LABEL = 'Expected value'; | |
| const RECEIVED_VALUE_LABEL = 'Received value'; | |
| // The optional property of matcher context is true if undefined. | |
| const isExpand = (expand?: boolean): boolean => expand !== false; | |
| const toStrictEqualTesters = [ | |
| iterableEquality, | |
| typeEquality, | |
| sparseArrayEquality, | |
| ]; | |
| type ContainIterable = | |
| | Array<unknown> | |
| | Set<unknown> | |
| | NodeListOf<Node> | |
| | DOMTokenList | |
| | HTMLCollectionOf<any>; | |
| const matchers: MatchersObject = { | |
| toBe(this: MatcherState, received: unknown, expected: unknown) { | |
| const matcherName = 'toBe'; | |
| const options: MatcherHintOptions = { | |
| comment: 'Object.is equality', | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| const pass = Object.is(received, expected); | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected: not ${printExpected(expected)}` | |
| : () => { | |
| const expectedType = getType(expected); | |
| let deepEqualityName = null; | |
| if (expectedType !== 'map' && expectedType !== 'set') { | |
| // If deep equality passes when referential identity fails, | |
| // but exclude map and set until review of their equality logic. | |
| if (equals(received, expected, toStrictEqualTesters, true)) { | |
| deepEqualityName = 'toStrictEqual'; | |
| } else if (equals(received, expected, [iterableEquality])) { | |
| deepEqualityName = 'toEqual'; | |
| } | |
| } | |
| return ( | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| (deepEqualityName !== null | |
| ? DIM_COLOR( | |
| `If it should pass with deep equality, replace "${matcherName}" with "${deepEqualityName}"`, | |
| ) + '\n\n' | |
| : '') + | |
| printDiffOrStringify( | |
| expected, | |
| received, | |
| EXPECTED_LABEL, | |
| RECEIVED_LABEL, | |
| isExpand(this.expand), | |
| ) | |
| ); | |
| }; | |
| // Passing the actual and expected objects so that a custom reporter | |
| // could access them, for example in order to display a custom visual diff, | |
| // or create a different error message | |
| return {actual: received, expected, message, name: matcherName, pass}; | |
| }, | |
| toBeCloseTo( | |
| this: MatcherState, | |
| received: number, | |
| expected: number, | |
| precision: number = 2, | |
| ) { | |
| const matcherName = 'toBeCloseTo'; | |
| const secondArgument = arguments.length === 3 ? 'precision' : undefined; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| isNot, | |
| promise: this.promise, | |
| secondArgument, | |
| secondArgumentColor: (arg: string) => arg, | |
| }; | |
| if (typeof expected !== 'number') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${EXPECTED_COLOR('expected')} value must be a number`, | |
| printWithType('Expected', expected, printExpected), | |
| ), | |
| ); | |
| } | |
| if (typeof received !== 'number') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${RECEIVED_COLOR('received')} value must be a number`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| let pass = false; | |
| let expectedDiff = 0; | |
| let receivedDiff = 0; | |
| if (received === Infinity && expected === Infinity) { | |
| pass = true; // Infinity - Infinity is NaN | |
| } else if (received === -Infinity && expected === -Infinity) { | |
| pass = true; // -Infinity - -Infinity is NaN | |
| } else { | |
| expectedDiff = Math.pow(10, -precision) / 2; | |
| receivedDiff = Math.abs(expected - received); | |
| pass = receivedDiff < expectedDiff; | |
| } | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected: not ${printExpected(expected)}\n` + | |
| (receivedDiff === 0 | |
| ? '' | |
| : `Received: ${printReceived(received)}\n` + | |
| '\n' + | |
| printCloseTo(receivedDiff, expectedDiff, precision, isNot)) | |
| : () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected: ${printExpected(expected)}\n` + | |
| `Received: ${printReceived(received)}\n` + | |
| '\n' + | |
| printCloseTo(receivedDiff, expectedDiff, precision, isNot); | |
| return {message, pass}; | |
| }, | |
| toBeDefined(this: MatcherState, received: unknown, expected: void) { | |
| const matcherName = 'toBeDefined'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNoExpected(expected, matcherName, options); | |
| const pass = received !== void 0; | |
| const message = () => | |
| matcherHint(matcherName, undefined, '', options) + | |
| '\n\n' + | |
| `Received: ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeFalsy(this: MatcherState, received: unknown, expected: void) { | |
| const matcherName = 'toBeFalsy'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNoExpected(expected, matcherName, options); | |
| const pass = !received; | |
| const message = () => | |
| matcherHint(matcherName, undefined, '', options) + | |
| '\n\n' + | |
| `Received: ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeGreaterThan( | |
| this: MatcherState, | |
| received: number | bigint, | |
| expected: number | bigint, | |
| ) { | |
| const matcherName = 'toBeGreaterThan'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNumbers(received, expected, matcherName, options); | |
| const pass = received > expected; | |
| const message = () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected:${isNot ? ' not' : ''} > ${printExpected(expected)}\n` + | |
| `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeGreaterThanOrEqual( | |
| this: MatcherState, | |
| received: number | bigint, | |
| expected: number | bigint, | |
| ) { | |
| const matcherName = 'toBeGreaterThanOrEqual'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNumbers(received, expected, matcherName, options); | |
| const pass = received >= expected; | |
| const message = () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected:${isNot ? ' not' : ''} >= ${printExpected(expected)}\n` + | |
| `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeInstanceOf(this: MatcherState, received: any, expected: Function) { | |
| const matcherName = 'toBeInstanceOf'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| if (typeof expected !== 'function') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${EXPECTED_COLOR('expected')} value must be a function`, | |
| printWithType('Expected', expected, printExpected), | |
| ), | |
| ); | |
| } | |
| const pass = received instanceof expected; | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| printExpectedConstructorNameNot('Expected constructor', expected) + | |
| (typeof received.constructor === 'function' && | |
| received.constructor !== expected | |
| ? printReceivedConstructorNameNot( | |
| 'Received constructor', | |
| received.constructor, | |
| expected, | |
| ) | |
| : '') | |
| : () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| printExpectedConstructorName('Expected constructor', expected) + | |
| (getType.isPrimitive(received) || | |
| Object.getPrototypeOf(received) === null | |
| ? `\nReceived value has no prototype\nReceived value: ${printReceived( | |
| received, | |
| )}` | |
| : typeof received.constructor !== 'function' | |
| ? `\nReceived value: ${printReceived(received)}` | |
| : printReceivedConstructorName( | |
| 'Received constructor', | |
| received.constructor, | |
| )); | |
| return {message, pass}; | |
| }, | |
| toBeLessThan( | |
| this: MatcherState, | |
| received: number | bigint, | |
| expected: number | bigint, | |
| ) { | |
| const matcherName = 'toBeLessThan'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNumbers(received, expected, matcherName, options); | |
| const pass = received < expected; | |
| const message = () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected:${isNot ? ' not' : ''} < ${printExpected(expected)}\n` + | |
| `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeLessThanOrEqual( | |
| this: MatcherState, | |
| received: number | bigint, | |
| expected: number | bigint, | |
| ) { | |
| const matcherName = 'toBeLessThanOrEqual'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNumbers(received, expected, matcherName, options); | |
| const pass = received <= expected; | |
| const message = () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected:${isNot ? ' not' : ''} <= ${printExpected(expected)}\n` + | |
| `Received:${isNot ? ' ' : ''} ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeNaN(this: MatcherState, received: any, expected: void) { | |
| const matcherName = 'toBeNaN'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNoExpected(expected, matcherName, options); | |
| const pass = Number.isNaN(received); | |
| const message = () => | |
| matcherHint(matcherName, undefined, '', options) + | |
| '\n\n' + | |
| `Received: ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeNull(this: MatcherState, received: unknown, expected: void) { | |
| const matcherName = 'toBeNull'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNoExpected(expected, matcherName, options); | |
| const pass = received === null; | |
| const message = () => | |
| matcherHint(matcherName, undefined, '', options) + | |
| '\n\n' + | |
| `Received: ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeTruthy(this: MatcherState, received: unknown, expected: void) { | |
| const matcherName = 'toBeTruthy'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNoExpected(expected, matcherName, options); | |
| const pass = !!received; | |
| const message = () => | |
| matcherHint(matcherName, undefined, '', options) + | |
| '\n\n' + | |
| `Received: ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toBeUndefined(this: MatcherState, received: unknown, expected: void) { | |
| const matcherName = 'toBeUndefined'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| ensureNoExpected(expected, matcherName, options); | |
| const pass = received === void 0; | |
| const message = () => | |
| matcherHint(matcherName, undefined, '', options) + | |
| '\n\n' + | |
| `Received: ${printReceived(received)}`; | |
| return {message, pass}; | |
| }, | |
| toContain( | |
| this: MatcherState, | |
| received: ContainIterable | string, | |
| expected: unknown, | |
| ) { | |
| const matcherName = 'toContain'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| comment: 'indexOf', | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| if (received == null) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${RECEIVED_COLOR('received')} value must not be null nor undefined`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| if (typeof received === 'string') { | |
| const wrongTypeErrorMessage = `${EXPECTED_COLOR( | |
| 'expected', | |
| )} value must be a string if ${RECEIVED_COLOR( | |
| 'received', | |
| )} value is a string`; | |
| if (typeof expected !== 'string') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, received, String(expected), options), | |
| wrongTypeErrorMessage, | |
| printWithType('Expected', expected, printExpected) + | |
| '\n' + | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| const index = received.indexOf(String(expected)); | |
| const pass = index !== -1; | |
| const message = () => { | |
| const labelExpected = `Expected ${ | |
| typeof expected === 'string' ? 'substring' : 'value' | |
| }`; | |
| const labelReceived = 'Received string'; | |
| const printLabel = getLabelPrinter(labelExpected, labelReceived); | |
| return ( | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( | |
| expected, | |
| )}\n` + | |
| `${printLabel(labelReceived)}${isNot ? ' ' : ''}${ | |
| isNot | |
| ? printReceivedStringContainExpectedSubstring( | |
| received, | |
| index, | |
| String(expected).length, | |
| ) | |
| : printReceived(received) | |
| }` | |
| ); | |
| }; | |
| return {message, pass}; | |
| } | |
| const indexable = Array.from(received); | |
| const index = indexable.indexOf(expected); | |
| const pass = index !== -1; | |
| const message = () => { | |
| const labelExpected = 'Expected value'; | |
| const labelReceived = `Received ${getType(received)}`; | |
| const printLabel = getLabelPrinter(labelExpected, labelReceived); | |
| return ( | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( | |
| expected, | |
| )}\n` + | |
| `${printLabel(labelReceived)}${isNot ? ' ' : ''}${ | |
| isNot && Array.isArray(received) | |
| ? printReceivedArrayContainExpectedItem(received, index) | |
| : printReceived(received) | |
| }` + | |
| (!isNot && | |
| indexable.findIndex(item => | |
| equals(item, expected, [iterableEquality]), | |
| ) !== -1 | |
| ? `\n\n${SUGGEST_TO_CONTAIN_EQUAL}` | |
| : '') | |
| ); | |
| }; | |
| return {message, pass}; | |
| }, | |
| toContainEqual( | |
| this: MatcherState, | |
| received: ContainIterable, | |
| expected: unknown, | |
| ) { | |
| const matcherName = 'toContainEqual'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| comment: 'deep equality', | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| if (received == null) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${RECEIVED_COLOR('received')} value must not be null nor undefined`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| const index = Array.from(received).findIndex(item => | |
| equals(item, expected, [iterableEquality]), | |
| ); | |
| const pass = index !== -1; | |
| const message = () => { | |
| const labelExpected = 'Expected value'; | |
| const labelReceived = `Received ${getType(received)}`; | |
| const printLabel = getLabelPrinter(labelExpected, labelReceived); | |
| return ( | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( | |
| expected, | |
| )}\n` + | |
| `${printLabel(labelReceived)}${isNot ? ' ' : ''}${ | |
| isNot && Array.isArray(received) | |
| ? printReceivedArrayContainExpectedItem(received, index) | |
| : printReceived(received) | |
| }` | |
| ); | |
| }; | |
| return {message, pass}; | |
| }, | |
| toEqual(this: MatcherState, received: unknown, expected: unknown) { | |
| const matcherName = 'toEqual'; | |
| const options: MatcherHintOptions = { | |
| comment: 'deep equality', | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| const pass = equals(received, expected, [iterableEquality]); | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected: not ${printExpected(expected)}\n` + | |
| (stringify(expected) !== stringify(received) | |
| ? `Received: ${printReceived(received)}` | |
| : '') | |
| : () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| printDiffOrStringify( | |
| expected, | |
| received, | |
| EXPECTED_LABEL, | |
| RECEIVED_LABEL, | |
| isExpand(this.expand), | |
| ); | |
| // Passing the actual and expected objects so that a custom reporter | |
| // could access them, for example in order to display a custom visual diff, | |
| // or create a different error message | |
| return {actual: received, expected, message, name: matcherName, pass}; | |
| }, | |
| toHaveLength(this: MatcherState, received: any, expected: number) { | |
| const matcherName = 'toHaveLength'; | |
| const isNot = this.isNot; | |
| const options: MatcherHintOptions = { | |
| isNot, | |
| promise: this.promise, | |
| }; | |
| if (typeof received?.length !== 'number') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${RECEIVED_COLOR( | |
| 'received', | |
| )} value must have a length property whose value must be a number`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| ensureExpectedIsNonNegativeInteger(expected, matcherName, options); | |
| const pass = received.length === expected; | |
| const message = () => { | |
| const labelExpected = 'Expected length'; | |
| const labelReceivedLength = 'Received length'; | |
| const labelReceivedValue = `Received ${getType(received)}`; | |
| const printLabel = getLabelPrinter( | |
| labelExpected, | |
| labelReceivedLength, | |
| labelReceivedValue, | |
| ); | |
| return ( | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `${printLabel(labelExpected)}${isNot ? 'not ' : ''}${printExpected( | |
| expected, | |
| )}\n` + | |
| (isNot | |
| ? '' | |
| : `${printLabel(labelReceivedLength)}${printReceived( | |
| received.length, | |
| )}\n`) + | |
| `${printLabel(labelReceivedValue)}${isNot ? ' ' : ''}${printReceived( | |
| received, | |
| )}` | |
| ); | |
| }; | |
| return {message, pass}; | |
| }, | |
| toHaveProperty( | |
| this: MatcherState, | |
| received: object, | |
| expectedPath: string | Array<string>, | |
| expectedValue?: unknown, | |
| ) { | |
| const matcherName = 'toHaveProperty'; | |
| const expectedArgument = 'path'; | |
| const hasValue = arguments.length === 3; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| secondArgument: hasValue ? 'value' : '', | |
| }; | |
| if (received === null || received === undefined) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, expectedArgument, options), | |
| `${RECEIVED_COLOR('received')} value must not be null nor undefined`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| const expectedPathType = getType(expectedPath); | |
| if (expectedPathType !== 'string' && expectedPathType !== 'array') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, expectedArgument, options), | |
| `${EXPECTED_COLOR('expected')} path must be a string or array`, | |
| printWithType('Expected', expectedPath, printExpected), | |
| ), | |
| ); | |
| } | |
| const expectedPathLength = | |
| typeof expectedPath === 'string' | |
| ? expectedPath.split('.').length | |
| : expectedPath.length; | |
| if (expectedPathType === 'array' && expectedPathLength === 0) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, expectedArgument, options), | |
| `${EXPECTED_COLOR('expected')} path must not be an empty array`, | |
| printWithType('Expected', expectedPath, printExpected), | |
| ), | |
| ); | |
| } | |
| const result = getPath(received, expectedPath); | |
| const {lastTraversedObject, hasEndProp} = result; | |
| const receivedPath = result.traversedPath; | |
| const hasCompletePath = receivedPath.length === expectedPathLength; | |
| const receivedValue = hasCompletePath ? result.value : lastTraversedObject; | |
| const pass = hasValue | |
| ? equals(result.value, expectedValue, [iterableEquality]) | |
| : Boolean(hasEndProp); // theoretically undefined if empty path | |
| // Remove type cast if we rewrite getPath as iterative algorithm. | |
| // Delete this unique report if future breaking change | |
| // removes the edge case that expected value undefined | |
| // also matches absence of a property with the key path. | |
| if (pass && !hasCompletePath) { | |
| const message = () => | |
| matcherHint(matcherName, undefined, expectedArgument, options) + | |
| '\n\n' + | |
| `Expected path: ${printExpected(expectedPath)}\n` + | |
| `Received path: ${printReceived( | |
| expectedPathType === 'array' || receivedPath.length === 0 | |
| ? receivedPath | |
| : receivedPath.join('.'), | |
| )}\n\n` + | |
| `Expected value: not ${printExpected(expectedValue)}\n` + | |
| `Received value: ${printReceived(receivedValue)}\n\n` + | |
| DIM_COLOR( | |
| 'Because a positive assertion passes for expected value undefined if the property does not exist, this negative assertion fails unless the property does exist and has a defined value', | |
| ); | |
| return {message, pass}; | |
| } | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, expectedArgument, options) + | |
| '\n\n' + | |
| (hasValue | |
| ? `Expected path: ${printExpected(expectedPath)}\n\n` + | |
| `Expected value: not ${printExpected(expectedValue)}` + | |
| (stringify(expectedValue) !== stringify(receivedValue) | |
| ? `\nReceived value: ${printReceived(receivedValue)}` | |
| : '') | |
| : `Expected path: not ${printExpected(expectedPath)}\n\n` + | |
| `Received value: ${printReceived(receivedValue)}`) | |
| : () => | |
| matcherHint(matcherName, undefined, expectedArgument, options) + | |
| '\n\n' + | |
| `Expected path: ${printExpected(expectedPath)}\n` + | |
| (hasCompletePath | |
| ? '\n' + | |
| printDiffOrStringify( | |
| expectedValue, | |
| receivedValue, | |
| EXPECTED_VALUE_LABEL, | |
| RECEIVED_VALUE_LABEL, | |
| isExpand(this.expand), | |
| ) | |
| : `Received path: ${printReceived( | |
| expectedPathType === 'array' || receivedPath.length === 0 | |
| ? receivedPath | |
| : receivedPath.join('.'), | |
| )}\n\n` + | |
| (hasValue | |
| ? `Expected value: ${printExpected(expectedValue)}\n` | |
| : '') + | |
| `Received value: ${printReceived(receivedValue)}`); | |
| return {message, pass}; | |
| }, | |
| toMatch(this: MatcherState, received: string, expected: string | RegExp) { | |
| const matcherName = 'toMatch'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| if (typeof received !== 'string') { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${RECEIVED_COLOR('received')} value must be a string`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| if ( | |
| !(typeof expected === 'string') && | |
| !(expected && typeof expected.test === 'function') | |
| ) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${EXPECTED_COLOR( | |
| 'expected', | |
| )} value must be a string or regular expression`, | |
| printWithType('Expected', expected, printExpected), | |
| ), | |
| ); | |
| } | |
| const pass = | |
| typeof expected === 'string' | |
| ? received.includes(expected) | |
| : new RegExp(expected).test(received); | |
| const message = pass | |
| ? () => | |
| typeof expected === 'string' | |
| ? matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected substring: not ${printExpected(expected)}\n` + | |
| `Received string: ${printReceivedStringContainExpectedSubstring( | |
| received, | |
| received.indexOf(expected), | |
| expected.length, | |
| )}` | |
| : matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected pattern: not ${printExpected(expected)}\n` + | |
| `Received string: ${printReceivedStringContainExpectedResult( | |
| received, | |
| typeof expected.exec === 'function' | |
| ? expected.exec(received) | |
| : null, | |
| )}` | |
| : () => { | |
| const labelExpected = `Expected ${ | |
| typeof expected === 'string' ? 'substring' : 'pattern' | |
| }`; | |
| const labelReceived = 'Received string'; | |
| const printLabel = getLabelPrinter(labelExpected, labelReceived); | |
| return ( | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `${printLabel(labelExpected)}${printExpected(expected)}\n` + | |
| `${printLabel(labelReceived)}${printReceived(received)}` | |
| ); | |
| }; | |
| return {message, pass}; | |
| }, | |
| toMatchObject(this: MatcherState, received: object, expected: object) { | |
| const matcherName = 'toMatchObject'; | |
| const options: MatcherHintOptions = { | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| if (typeof received !== 'object' || received === null) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${RECEIVED_COLOR('received')} value must be a non-null object`, | |
| printWithType('Received', received, printReceived), | |
| ), | |
| ); | |
| } | |
| if (typeof expected !== 'object' || expected === null) { | |
| throw new Error( | |
| matcherErrorMessage( | |
| matcherHint(matcherName, undefined, undefined, options), | |
| `${EXPECTED_COLOR('expected')} value must be a non-null object`, | |
| printWithType('Expected', expected, printExpected), | |
| ), | |
| ); | |
| } | |
| const pass = equals(received, expected, [iterableEquality, subsetEquality]); | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected: not ${printExpected(expected)}` + | |
| (stringify(expected) !== stringify(received) | |
| ? `\nReceived: ${printReceived(received)}` | |
| : '') | |
| : () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| printDiffOrStringify( | |
| expected, | |
| getObjectSubset(received, expected), | |
| EXPECTED_LABEL, | |
| RECEIVED_LABEL, | |
| isExpand(this.expand), | |
| ); | |
| return {message, pass}; | |
| }, | |
| toStrictEqual(this: MatcherState, received: unknown, expected: unknown) { | |
| const matcherName = 'toStrictEqual'; | |
| const options: MatcherHintOptions = { | |
| comment: 'deep equality', | |
| isNot: this.isNot, | |
| promise: this.promise, | |
| }; | |
| const pass = equals(received, expected, toStrictEqualTesters, true); | |
| const message = pass | |
| ? () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| `Expected: not ${printExpected(expected)}\n` + | |
| (stringify(expected) !== stringify(received) | |
| ? `Received: ${printReceived(received)}` | |
| : '') | |
| : () => | |
| matcherHint(matcherName, undefined, undefined, options) + | |
| '\n\n' + | |
| printDiffOrStringify( | |
| expected, | |
| received, | |
| EXPECTED_LABEL, | |
| RECEIVED_LABEL, | |
| isExpand(this.expand), | |
| ); | |
| // Passing the actual and expected objects so that a custom reporter | |
| // could access them, for example in order to display a custom visual diff, | |
| // or create a different error message | |
| return {actual: received, expected, message, name: matcherName, pass}; | |
| }, | |
| }; | |
| export default matchers; |