Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce new @jest/expect-utils package #12323

Merged
merged 1 commit into from Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions .eslintrc.js
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,13 +4,15 @@

- `[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

### Chore & Maintenance

- `[*]` [**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))

Expand Down
7 changes: 7 additions & 0 deletions packages/expect-utils/.npmignore
@@ -0,0 +1,7 @@
**/__mocks__/**
**/__tests__/**
__typetests__
src
tsconfig.json
tsconfig.tsbuildinfo
api-extractor.json
5 changes: 5 additions & 0 deletions 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).
31 changes: 31 additions & 0 deletions 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"
}
}
13 changes: 13 additions & 0 deletions 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';
Expand Up @@ -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<Tester>,
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);
Expand Down Expand Up @@ -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] : '<anonymous>';
}

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__@@';
Expand Down
9 changes: 9 additions & 0 deletions 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;
File renamed without changes.
13 changes: 13 additions & 0 deletions 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"}
]
}
2 changes: 1 addition & 1 deletion packages/expect/package.json
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions packages/expect/src/__tests__/extend.test.ts
Expand Up @@ -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);

Expand Down
49 changes: 46 additions & 3 deletions packages/expect/src/asymmetricMatchers.ts
Expand Up @@ -6,21 +6,64 @@
*
*/

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] : '<anonymous>';
}

const utils = Object.freeze({
...matcherUtils,
iterableEquality,
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,
Expand Down Expand Up @@ -123,7 +166,7 @@ class Any extends AsymmetricMatcher<any> {

class Anything extends AsymmetricMatcher<void> {
asymmetricMatch(other: unknown) {
return !isUndefined(other) && other !== null;
return other != null;
}

toString() {
Expand Down
3 changes: 1 addition & 2 deletions packages/expect/src/index.ts
Expand Up @@ -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,
Expand All @@ -24,7 +25,6 @@ import {
stringNotMatching,
} from './asymmetricMatchers';
import extractExpectedAssertionsErrors from './extractExpectedAssertionsErrors';
import {equals} from './jasmineUtils';
import {
INTERNAL_MATCHER_FLAG,
getMatchers,
Expand All @@ -49,7 +49,6 @@ import type {
SyncExpectationResult,
ThrowingMatcherFn,
} from './types';
import {iterableEquality, subsetEquality} from './utils';

class JestAssertionError extends Error {
matcherResult?: Omit<SyncExpectationResult, 'message'> & {message: string};
Expand Down
22 changes: 11 additions & 11 deletions packages/expect/src/matchers.ts
Expand Up @@ -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,
Expand All @@ -27,7 +38,6 @@ import {
printWithType,
stringify,
} from 'jest-matcher-utils';
import {equals} from './jasmineUtils';
import {
printCloseTo,
printExpectedConstructorName,
Expand All @@ -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';
Expand Down
3 changes: 1 addition & 2 deletions packages/expect/src/spyMatchers.ts
Expand Up @@ -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,
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/src/toThrowMatchers.ts
Expand Up @@ -8,6 +8,7 @@

/* eslint-disable local/ban-types-eventually */

import {isError} from '@jest/expect-utils';
import {
EXPECTED_COLOR,
MatcherHintOptions,
Expand Down Expand Up @@ -35,7 +36,6 @@ import type {
RawMatcherFn,
SyncExpectationResult,
} from './types';
import {isError} from './utils';

const DID_NOT_THROW = 'Received function did not throw';

Expand Down