diff --git a/.eslintrc.js b/.eslintrc.js index 053c92417e1a..227fccc759ce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -91,7 +91,7 @@ module.exports = { 'packages/expect/src/matchers.ts', 'packages/expect/src/print.ts', 'packages/expect/src/toThrowMatchers.ts', - 'packages/expect/src/utils.ts', + 'packages/expect-utils/src/utils.ts', 'packages/jest-core/src/ReporterDispatcher.ts', 'packages/jest-core/src/TestScheduler.ts', 'packages/jest-core/src/collectHandles.ts', @@ -214,8 +214,7 @@ module.exports = { { files: [ 'packages/jest-jasmine2/src/jasmine/**/*', - 'packages/expect/src/jasmineUtils.ts', - '**/vendor/**/*', + 'packages/expect-utils/src/jasmineUtils.ts', ], rules: { 'eslint-comments/disable-enable-pair': 'off', diff --git a/CHANGELOG.md b/CHANGELOG.md index a67810c12ac3..91eb0f524488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - `[jest-environment-jsdom]` [**BREAKING**] Add default `browser` condtion to `exportConditions` for `jsdom` environment ([#11924](https://github.com/facebook/jest/pull/11924)) - `[jest-environment-node]` [**BREAKING**] Add default `node` and `node-addon` conditions to `exportConditions` for `node` environment ([#11924](https://github.com/facebook/jest/pull/11924)) +- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323)) ### Fixes @@ -11,6 +12,7 @@ - `[*]` [**BREAKING**] Drop support for Node v10 and v15 and target first LTS `16.13.0` ([#12220](https://github.com/facebook/jest/pull/12220)) - `[*]` [**BREAKING**] Drop support for `typescript@3.8`, minimum version is now `4.2` ([#11142](https://github.com/facebook/jest/pull/11142)) +- `[expect]` [**BREAKING**] Remove support for importing `build/utils` ([#12323](https://github.com/facebook/jest/pull/12323)) - `[@jest/core]` Use `index.ts` instead of `jest.ts` as main export ([#12329](https://github.com/facebook/jest/pull/12329)) - `[jest]` Use `index.ts` instead of `jest.ts` as main export ([#12329](https://github.com/facebook/jest/pull/12329)) diff --git a/packages/expect-utils/.npmignore b/packages/expect-utils/.npmignore new file mode 100644 index 000000000000..80bf61eb922c --- /dev/null +++ b/packages/expect-utils/.npmignore @@ -0,0 +1,7 @@ +**/__mocks__/** +**/__tests__/** +__typetests__ +src +tsconfig.json +tsconfig.tsbuildinfo +api-extractor.json diff --git a/packages/expect-utils/README.md b/packages/expect-utils/README.md new file mode 100644 index 000000000000..12ae8b2f0fea --- /dev/null +++ b/packages/expect-utils/README.md @@ -0,0 +1,5 @@ +# `@jest/expect-utils` + +This module exports some utils for the `expect` function used in [Jest](https://jestjs.io/). + +You probably don't want to use this package directly. E.g. if you're writing [custom matcher](https://jestjs.io/docs/expect#expectextendmatchers), you should use the injected [`this.equals`](https://jestjs.io/docs/expect#thisequalsa-b). diff --git a/packages/expect-utils/package.json b/packages/expect-utils/package.json new file mode 100644 index 000000000000..2b1dfdf22a63 --- /dev/null +++ b/packages/expect-utils/package.json @@ -0,0 +1,31 @@ +{ + "name": "@jest/expect-utils", + "version": "27.5.1", + "repository": { + "type": "git", + "url": "https://github.com/facebook/jest.git", + "directory": "packages/expect-utils" + }, + "license": "MIT", + "main": "./build/index.js", + "types": "./build/index.d.ts", + "exports": { + ".": { + "types": "./build/index.d.ts", + "default": "./build/index.js" + }, + "./package.json": "./package.json" + }, + "dependencies": { + "jest-get-type": "^27.5.1" + }, + "devDependencies": { + "jest-matcher-utils": "^27.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.13.0 || >=17.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/expect/src/__tests__/isError.test.ts b/packages/expect-utils/src/__tests__/isError.test.ts similarity index 100% rename from packages/expect/src/__tests__/isError.test.ts rename to packages/expect-utils/src/__tests__/isError.test.ts diff --git a/packages/expect/src/__tests__/utils.test.ts b/packages/expect-utils/src/__tests__/utils.test.ts similarity index 100% rename from packages/expect/src/__tests__/utils.test.ts rename to packages/expect-utils/src/__tests__/utils.test.ts diff --git a/packages/expect-utils/src/index.ts b/packages/expect-utils/src/index.ts new file mode 100644 index 000000000000..6eb5e82d3d91 --- /dev/null +++ b/packages/expect-utils/src/index.ts @@ -0,0 +1,13 @@ +/** + * 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. + * + */ + +export {equals, isA} from './jasmineUtils'; +export type {EqualsFunction} from './jasmineUtils'; +export * from './utils'; + +export type {Tester} from './types'; diff --git a/packages/expect/src/jasmineUtils.ts b/packages/expect-utils/src/jasmineUtils.ts similarity index 88% rename from packages/expect/src/jasmineUtils.ts rename to packages/expect-utils/src/jasmineUtils.ts index 8388975dfbb0..81491f453561 100644 --- a/packages/expect/src/jasmineUtils.ts +++ b/packages/expect-utils/src/jasmineUtils.ts @@ -26,18 +26,18 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import type {Tester} from './types'; -// Extracted out of jasmine 2.5.2 -export function equals( +export type EqualsFunction = ( a: unknown, b: unknown, customTesters?: Array, strictCheck?: boolean, -): boolean { +) => boolean; + +// Extracted out of jasmine 2.5.2 +export const equals: EqualsFunction = (a, b, customTesters, strictCheck) => { customTesters = customTesters || []; return eq(a, b, [], [], customTesters, strictCheck ? hasKey : hasDefinedKey); -} - -const functionToString = Function.prototype.toString; +}; function isAsymmetric(obj: any) { return !!obj && isA('Function', obj.asymmetricMatch); @@ -220,45 +220,6 @@ function isDomNode(obj: any): boolean { ); } -export function fnNameFor(func: Function) { - if (func.name) { - return func.name; - } - - const matches = functionToString - .call(func) - .match(/^(?:async)?\s*function\s*\*?\s*([\w$]+)\s*\(/); - return matches ? matches[1] : ''; -} - -export function isUndefined(obj: any) { - return obj === void 0; -} - -function getPrototype(obj: object) { - if (Object.getPrototypeOf) { - return Object.getPrototypeOf(obj); - } - - if (obj.constructor.prototype == obj) { - return null; - } - - return obj.constructor.prototype; -} - -export function hasProperty(obj: object | null, property: string): boolean { - if (!obj) { - return false; - } - - if (Object.prototype.hasOwnProperty.call(obj, property)) { - return true; - } - - return hasProperty(getPrototype(obj), property); -} - // SENTINEL constants are from https://github.com/facebook/immutable-js const IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@'; const IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@'; diff --git a/packages/expect-utils/src/types.ts b/packages/expect-utils/src/types.ts new file mode 100644 index 000000000000..361f648f5f56 --- /dev/null +++ b/packages/expect-utils/src/types.ts @@ -0,0 +1,9 @@ +/** + * 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. + * + */ + +export type Tester = (a: any, b: any) => boolean | undefined; diff --git a/packages/expect/src/utils.ts b/packages/expect-utils/src/utils.ts similarity index 100% rename from packages/expect/src/utils.ts rename to packages/expect-utils/src/utils.ts diff --git a/packages/expect-utils/tsconfig.json b/packages/expect-utils/tsconfig.json new file mode 100644 index 000000000000..78211a8386dc --- /dev/null +++ b/packages/expect-utils/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "include": ["./src/**/*"], + "exclude": ["./**/__tests__/**/*"], + "references": [ + {"path": "../jest-get-type"}, + {"path": "../jest-matcher-utils"} + ] +} diff --git a/packages/expect/package.json b/packages/expect/package.json index a7048305dffb..bdac77ce2e11 100644 --- a/packages/expect/package.json +++ b/packages/expect/package.json @@ -15,10 +15,10 @@ "default": "./build/index.js" }, "./package.json": "./package.json", - "./build/utils": "./build/utils.js", "./build/matchers": "./build/matchers.js" }, "dependencies": { + "@jest/expect-utils": "^27.5.1", "@jest/types": "^27.5.1", "jest-get-type": "^27.5.1", "jest-matcher-utils": "^27.5.1", diff --git a/packages/expect/src/__tests__/extend.test.ts b/packages/expect/src/__tests__/extend.test.ts index b882165d4b60..852dfb139f09 100644 --- a/packages/expect/src/__tests__/extend.test.ts +++ b/packages/expect/src/__tests__/extend.test.ts @@ -6,11 +6,10 @@ * */ +import {equals, iterableEquality, subsetEquality} from '@jest/expect-utils'; import {alignedAnsiStyleSerializer} from '@jest/test-utils'; import * as matcherUtils from 'jest-matcher-utils'; import jestExpect from '../'; -import {equals} from '../jasmineUtils'; -import {iterableEquality, subsetEquality} from '../utils'; expect.addSnapshotSerializer(alignedAnsiStyleSerializer); diff --git a/packages/expect/src/asymmetricMatchers.ts b/packages/expect/src/asymmetricMatchers.ts index 284dd2cf56fb..443552e6ff1f 100644 --- a/packages/expect/src/asymmetricMatchers.ts +++ b/packages/expect/src/asymmetricMatchers.ts @@ -6,14 +6,31 @@ * */ +import { + equals, + isA, + iterableEquality, + subsetEquality, +} from '@jest/expect-utils'; import * as matcherUtils from 'jest-matcher-utils'; -import {equals, fnNameFor, hasProperty, isA, isUndefined} from './jasmineUtils'; import {getState} from './jestMatchersObject'; import type { AsymmetricMatcher as AsymmetricMatcherInterface, MatcherState, } from './types'; -import {iterableEquality, subsetEquality} from './utils'; + +const functionToString = Function.prototype.toString; + +function fnNameFor(func: () => unknown) { + if (func.name) { + return func.name; + } + + const matches = functionToString + .call(func) + .match(/^(?:async)?\s*function\s*\*?\s*([\w$]+)\s*\(/); + return matches ? matches[1] : ''; +} const utils = Object.freeze({ ...matcherUtils, @@ -21,6 +38,32 @@ const utils = Object.freeze({ subsetEquality, }); +// eslint-disable-next-line @typescript-eslint/ban-types +function getPrototype(obj: object) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } + + if (obj.constructor.prototype == obj) { + return null; + } + + return obj.constructor.prototype; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function hasProperty(obj: object | null, property: string): boolean { + if (!obj) { + return false; + } + + if (Object.prototype.hasOwnProperty.call(obj, property)) { + return true; + } + + return hasProperty(getPrototype(obj), property); +} + export abstract class AsymmetricMatcher< T, State extends MatcherState = MatcherState, @@ -123,7 +166,7 @@ class Any extends AsymmetricMatcher { class Anything extends AsymmetricMatcher { asymmetricMatch(other: unknown) { - return !isUndefined(other) && other !== null; + return other != null; } toString() { diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 3ffb08d0e3ac..8660b1f13df6 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -8,6 +8,7 @@ /* eslint-disable local/prefer-spread-eventually */ +import {equals, iterableEquality, subsetEquality} from '@jest/expect-utils'; import * as matcherUtils from 'jest-matcher-utils'; import { any, @@ -24,7 +25,6 @@ import { stringNotMatching, } from './asymmetricMatchers'; import extractExpectedAssertionsErrors from './extractExpectedAssertionsErrors'; -import {equals} from './jasmineUtils'; import { INTERNAL_MATCHER_FLAG, getMatchers, @@ -49,7 +49,6 @@ import type { SyncExpectationResult, ThrowingMatcherFn, } from './types'; -import {iterableEquality, subsetEquality} from './utils'; class JestAssertionError extends Error { matcherResult?: Omit & {message: string}; diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index 02e4c59813d0..4293d44ff9db 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -8,6 +8,17 @@ /* eslint-disable local/ban-types-eventually */ +import { + arrayBufferEquality, + equals, + getObjectSubset, + getPath, + iterableEquality, + pathAsArray, + sparseArrayEquality, + subsetEquality, + typeEquality, +} from '@jest/expect-utils'; import {getType, isPrimitive} from 'jest-get-type'; import { DIM_COLOR, @@ -27,7 +38,6 @@ import { printWithType, stringify, } from 'jest-matcher-utils'; -import {equals} from './jasmineUtils'; import { printCloseTo, printExpectedConstructorName, @@ -39,16 +49,6 @@ import { printReceivedStringContainExpectedSubstring, } from './print'; import type {MatchersObject} from './types'; -import { - arrayBufferEquality, - getObjectSubset, - getPath, - iterableEquality, - pathAsArray, - sparseArrayEquality, - subsetEquality, - typeEquality, -} from './utils'; // Omit colon and one or more spaces, so can call getLabelPrinter. const EXPECTED_LABEL = 'Expected'; diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index e5f3f6a3b542..2b229f6a5c87 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {equals, iterableEquality} from '@jest/expect-utils'; import {getType, isPrimitive} from 'jest-get-type'; import { DIM_COLOR, @@ -21,13 +22,11 @@ import { printWithType, stringify, } from 'jest-matcher-utils'; -import {equals} from './jasmineUtils'; import type { MatcherState, MatchersObject, SyncExpectationResult, } from './types'; -import {iterableEquality} from './utils'; // The optional property of matcher context is true if undefined. const isExpand = (expand?: boolean): boolean => expand !== false; diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index 8be9a79ae5fd..a30cf977917f 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -8,6 +8,7 @@ /* eslint-disable local/ban-types-eventually */ +import {isError} from '@jest/expect-utils'; import { EXPECTED_COLOR, MatcherHintOptions, @@ -35,7 +36,6 @@ import type { RawMatcherFn, SyncExpectationResult, } from './types'; -import {isError} from './utils'; const DID_NOT_THROW = 'Received function did not throw'; diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 6b1d1b8560b0..8bd31aaf7e19 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -6,6 +6,7 @@ * */ +import type {EqualsFunction, Tester} from '@jest/expect-utils'; import type {Config} from '@jest/types'; import type * as jestMatcherUtils from 'jest-matcher-utils'; import {INTERNAL_MATCHER_FLAG} from './jestMatchersObject'; @@ -27,19 +28,12 @@ export type RawMatcherFn = { export type ThrowingMatcherFn = (actual: any) => void; export type PromiseMatcherFn = (actual: any) => Promise; -export type Tester = (a: any, b: any) => boolean | undefined; - export type MatcherState = { assertionCalls: number; currentTestName?: string; dontThrow?: () => void; error?: Error; - equals: ( - a: unknown, - b: unknown, - customTesters?: Array, - strictCheck?: boolean, - ) => boolean; + equals: EqualsFunction; expand?: boolean; expectedAssertionsNumber?: number | null; expectedAssertionsNumberError?: Error; diff --git a/packages/expect/tsconfig.json b/packages/expect/tsconfig.json index 8fd74f140885..3609d0f6e862 100644 --- a/packages/expect/tsconfig.json +++ b/packages/expect/tsconfig.json @@ -7,6 +7,7 @@ "include": ["./src/**/*"], "exclude": ["./**/__tests__/**/*"], "references": [ + {"path": "../expect-utils"}, {"path": "../jest-get-type"}, {"path": "../jest-matcher-utils"}, {"path": "../jest-message-util"}, diff --git a/packages/jest-snapshot/package.json b/packages/jest-snapshot/package.json index 7503ad7060da..df83ebe7f0ae 100644 --- a/packages/jest-snapshot/package.json +++ b/packages/jest-snapshot/package.json @@ -22,6 +22,7 @@ "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", + "@jest/expect-utils": "^27.5.1", "@jest/transform": "^27.5.1", "@jest/types": "^27.5.1", "@types/babel__traverse": "^7.0.4", diff --git a/packages/jest-snapshot/src/printSnapshot.ts b/packages/jest-snapshot/src/printSnapshot.ts index 1d3af52ac865..12621c88d8cf 100644 --- a/packages/jest-snapshot/src/printSnapshot.ts +++ b/packages/jest-snapshot/src/printSnapshot.ts @@ -8,11 +8,7 @@ /* eslint-disable local/ban-types-eventually */ import chalk = require('chalk'); -// Temporary hack because getObjectSubset has known limitations, -// is not in the public interface of the expect package, -// and the long-term goal is to use a non-serialization diff. -// Make sure to remove file from `exports` in `expect/package.json`. -import {getObjectSubset} from 'expect/build/utils'; +import {getObjectSubset} from '@jest/expect-utils'; import { DIFF_DELETE, DIFF_EQUAL, diff --git a/packages/jest-snapshot/tsconfig.json b/packages/jest-snapshot/tsconfig.json index de9818a9b1dd..411a0f5a3a83 100644 --- a/packages/jest-snapshot/tsconfig.json +++ b/packages/jest-snapshot/tsconfig.json @@ -8,6 +8,7 @@ "exclude": ["./**/__mocks__/**/*", "./**/__tests__/**/*"], "references": [ {"path": "../expect"}, + {"path": "../expect-utils"}, {"path": "../jest-diff"}, {"path": "../jest-get-type"}, {"path": "../jest-haste-map"}, diff --git a/scripts/buildUtils.js b/scripts/buildUtils.js index 2b5e7eba7cdf..1136a369308d 100644 --- a/scripts/buildUtils.js +++ b/scripts/buildUtils.js @@ -62,10 +62,7 @@ module.exports.getPackages = function getPackages() { ), ...(pkg.name === 'jest-circus' ? {'./runner': './runner.js'} : {}), ...(pkg.name === 'expect' - ? { - './build/matchers': './build/matchers.js', - './build/utils': './build/utils.js', - } + ? {'./build/matchers': './build/matchers.js'} : {}), ...(pkg.name === 'pretty-format' ? {'./ConvertAnsi': './build/plugins/ConvertAnsi.js'} diff --git a/scripts/checkCopyrightHeaders.js b/scripts/checkCopyrightHeaders.js index 72a1e1333f74..8d490d570f6c 100755 --- a/scripts/checkCopyrightHeaders.js +++ b/scripts/checkCopyrightHeaders.js @@ -99,7 +99,7 @@ const GENERIC_IGNORED_PATTERNS = [ const CUSTOM_IGNORED_PATTERNS = [ '\\.(example|map)$', '^examples/.*', - '^packages/expect/src/jasmineUtils\\.ts$', + '^packages/expect-utils/src/jasmineUtils\\.ts$', '^packages/jest-diff/src/cleanupSemantic\\.ts$', '^packages/jest-haste-map/src/watchers/common\\.js$', '^packages/jest-haste-map/src/watchers/NodeWatcher\\.js$', diff --git a/yarn.lock b/yarn.lock index 3770031ed896..f2ee5a5189bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2562,6 +2562,15 @@ __metadata: languageName: unknown linkType: soft +"@jest/expect-utils@^27.5.1, @jest/expect-utils@workspace:packages/expect-utils": + version: 0.0.0-use.local + resolution: "@jest/expect-utils@workspace:packages/expect-utils" + dependencies: + jest-get-type: ^27.5.1 + jest-matcher-utils: ^27.5.1 + languageName: unknown + linkType: soft + "@jest/fake-timers@^27.5.1, @jest/fake-timers@workspace:packages/jest-fake-timers": version: 0.0.0-use.local resolution: "@jest/fake-timers@workspace:packages/jest-fake-timers" @@ -9933,6 +9942,7 @@ __metadata: version: 0.0.0-use.local resolution: "expect@workspace:packages/expect" dependencies: + "@jest/expect-utils": ^27.5.1 "@jest/test-utils": ^27.5.1 "@jest/types": ^27.5.1 "@tsd/typescript": ~4.5.5 @@ -13147,6 +13157,7 @@ __metadata: "@babel/preset-react": ^7.7.2 "@babel/traverse": ^7.7.2 "@babel/types": ^7.0.0 + "@jest/expect-utils": ^27.5.1 "@jest/test-utils": ^27.5.1 "@jest/transform": ^27.5.1 "@jest/types": ^27.5.1