Skip to content

Commit

Permalink
feat: introduce new @jest/expect-utils package (#12323)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Feb 9, 2022
1 parent 457b5f9 commit 86bf6fc
Show file tree
Hide file tree
Showing 27 changed files with 169 additions and 88 deletions.
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"
}
}
File renamed without changes.
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

0 comments on commit 86bf6fc

Please sign in to comment.