diff --git a/CHANGELOG.md b/CHANGELOG.md index 43db8088d062..dfa05c42e017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ - `[expect]` Move typings of `.not`, `.rejects` and `.resolves` modifiers outside of `Matchers` interface ([#12346](https://github.com/facebook/jest/pull/12346)) - `[expect]` Throw useful error if `expect.extend` is called with invalid matchers ([#12488](https://github.com/facebook/jest/pull/12488)) - `[expect]` Fix `iterableEquality` ignores other properties ([#8359](https://github.com/facebook/jest/pull/8359)) +- `[expect]` Fix print for the `closeTo` matcher ([#12626](https://github.com/facebook/jest/pull/12626)) - `[jest-circus, @jest/types]` Disallow undefined value in `TestContext` type ([#12507](https://github.com/facebook/jest/pull/12507)) - `[jest-config]` Correctly detect CI environment and update snapshots accordingly ([#12378](https://github.com/facebook/jest/pull/12378)) - `[jest-config]` Pass `moduleTypes` to `ts-node` to enforce CJS when transpiling ([#12397](https://github.com/facebook/jest/pull/12397)) diff --git a/packages/expect/src/asymmetricMatchers.ts b/packages/expect/src/asymmetricMatchers.ts index 58390fc11421..e9aff27f9a75 100644 --- a/packages/expect/src/asymmetricMatchers.ts +++ b/packages/expect/src/asymmetricMatchers.ts @@ -13,6 +13,7 @@ import { subsetEquality, } from '@jest/expect-utils'; import * as matcherUtils from 'jest-matcher-utils'; +import {pluralize} from 'jest-util'; import {getState} from './jestMatchersObject'; import type { AsymmetricMatcher as AsymmetricMatcherInterface, @@ -329,6 +330,14 @@ class CloseTo extends AsymmetricMatcher { override getExpectedType() { return 'number'; } + + override toAsymmetricMatcher(): string { + return [ + this.toString(), + this.sample, + `(${pluralize('digit', this.precision)})`, + ].join(' '); + } } export const any = (expectedObject: unknown): Any => new Any(expectedObject); diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index ee6179aa8402..4511b9e0b8bb 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -49,6 +49,7 @@ import type { ThrowingMatcherFn, } from './types'; +export {AsymmetricMatcher} from './asymmetricMatchers'; export type { AsymmetricMatchers, BaseExpect, diff --git a/packages/pretty-format/package.json b/packages/pretty-format/package.json index 689422ef07bc..690af3ce4e36 100644 --- a/packages/pretty-format/package.json +++ b/packages/pretty-format/package.json @@ -29,6 +29,7 @@ "@types/react": "*", "@types/react-is": "^17.0.0", "@types/react-test-renderer": "*", + "expect": "^28.0.0-alpha.8", "immutable": "^4.0.0", "jest-util": "^28.0.0-alpha.8", "react": "*", diff --git a/packages/pretty-format/src/__tests__/AsymmetricMatcher.test.ts b/packages/pretty-format/src/__tests__/AsymmetricMatcher.test.ts index dd0495fb17d4..42da89bd9b25 100644 --- a/packages/pretty-format/src/__tests__/AsymmetricMatcher.test.ts +++ b/packages/pretty-format/src/__tests__/AsymmetricMatcher.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {AsymmetricMatcher as AbstractAsymmetricMatcher} from 'expect'; import prettyFormat, {plugins} from '../'; import type {OptionsReceived} from '../types'; @@ -131,6 +132,42 @@ test('stringNotMatching(string)', () => { expect(result).toEqual('StringNotMatching /jest/'); }); +test('closeTo(number, precision)', () => { + const result = prettyFormat(expect.closeTo(1.2345, 4), options); + expect(result).toEqual('NumberCloseTo 1.2345 (4 digits)'); +}); + +test('notCloseTo(number, precision)', () => { + const result = prettyFormat(expect.not.closeTo(1.2345, 1), options); + expect(result).toEqual('NumberNotCloseTo 1.2345 (1 digit)'); +}); + +test('closeTo(number)', () => { + const result = prettyFormat(expect.closeTo(1.2345), options); + expect(result).toEqual('NumberCloseTo 1.2345 (2 digits)'); +}); + +test('closeTo(Infinity)', () => { + const result = prettyFormat(expect.closeTo(-Infinity), options); + expect(result).toEqual('NumberCloseTo -Infinity (2 digits)'); +}); + +test('closeTo(scientific number)', () => { + const result = prettyFormat(expect.closeTo(1.56e-3, 4), options); + expect(result).toEqual('NumberCloseTo 0.00156 (4 digits)'); +}); + +test('closeTo(very small scientific number)', () => { + const result = prettyFormat(expect.closeTo(1.56e-10, 4), options); + expect(result).toEqual('NumberCloseTo 1.56e-10 (4 digits)'); +}); + +test('correctly handles inability to pretty-print matcher', () => { + expect(() => prettyFormat(new DummyMatcher(1), options)).toThrow( + 'Asymmetric matcher DummyMatcher does not implement toAsymmetricMatcher()', + ); +}); + test('supports multiple nested asymmetric matchers', () => { const result = prettyFormat( { @@ -311,3 +348,21 @@ test('min option', () => { '{"test": {"nested": ObjectContaining {"a": ArrayContaining [1], "b": Anything, "c": Any, "d": StringContaining "jest", "e": StringMatching /jest/, "f": ObjectContaining {"test": "case"}}}}', ); }); + +class DummyMatcher extends AbstractAsymmetricMatcher { + constructor(sample: number) { + super(sample); + } + + asymmetricMatch(other: number) { + return this.sample === other; + } + + toString() { + return 'DummyMatcher'; + } + + override getExpectedType() { + return 'number'; + } +} diff --git a/packages/pretty-format/src/plugins/AsymmetricMatcher.ts b/packages/pretty-format/src/plugins/AsymmetricMatcher.ts index 7457cdd8973b..331b64e1dbb5 100644 --- a/packages/pretty-format/src/plugins/AsymmetricMatcher.ts +++ b/packages/pretty-format/src/plugins/AsymmetricMatcher.ts @@ -80,6 +80,12 @@ export const serialize: NewPlugin['serialize'] = ( ); } + if (typeof val.toAsymmetricMatcher !== 'function') { + throw new Error( + `Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()`, + ); + } + return val.toAsymmetricMatcher(); }; diff --git a/packages/pretty-format/tsconfig.json b/packages/pretty-format/tsconfig.json index b91ffb6b1f33..6f91b2984094 100644 --- a/packages/pretty-format/tsconfig.json +++ b/packages/pretty-format/tsconfig.json @@ -6,5 +6,6 @@ }, "include": ["./src/**/*"], "exclude": ["./**/__tests__/**/*"], + // no `expect`, only used in tests "references": [{"path": "../jest-schemas"}, {"path": "../jest-util"}] } diff --git a/scripts/buildTs.mjs b/scripts/buildTs.mjs index 6ab57279aced..c8fb4267c076 100644 --- a/scripts/buildTs.mjs +++ b/scripts/buildTs.mjs @@ -62,6 +62,13 @@ import {getPackages} from './buildUtils.mjs'; } } + // dev dep + if (pkg.name === 'pretty-format') { + if (dep === 'expect') { + return false; + } + } + return true; }) .map(dep => @@ -84,7 +91,7 @@ import {getPackages} from './buildUtils.mjs'; assert.deepStrictEqual( references, jestDependenciesOfPackage, - `Expected declared references to match dependencies in packages ${ + `Expected declared references to match dependencies in package ${ pkg.name }. Got:\n\n${references.join( '\n', diff --git a/yarn.lock b/yarn.lock index f8d2457c09c3..2455bf6708ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17974,6 +17974,7 @@ __metadata: "@types/react-test-renderer": "*" ansi-regex: ^5.0.1 ansi-styles: ^5.0.0 + expect: ^28.0.0-alpha.8 immutable: ^4.0.0 jest-util: ^28.0.0-alpha.8 react: "*"