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(expect): replace RawMatcherFn with MatcherFunction and MatcherFunctionWithState types #12376

Merged
merged 13 commits into from
Feb 15, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

- `[expect]` Move typings of `.not`, `.rejects` and `.resolves` modifiers outside of `Matchers` interface ([#12346](https://github.com/facebook/jest/pull/12346))
- `[expect]` Expose `AsymmetricMatchers` and `RawMatcherFn` interfaces ([#12363](https://github.com/facebook/jest/pull/12363))
- `[expect]` Expose `ExpectationResult` type instead of `RawMatcherFn` type (partly reverts #12363) ([#12376](https://github.com/facebook/jest/pull/12376))
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
- `[jest-environment-jsdom]` Make `jsdom` accessible to extending environments again ([#12232](https://github.com/facebook/jest/pull/12232))
- `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125))

Expand Down
6 changes: 3 additions & 3 deletions examples/expect-extend/toBeWithinRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
*/

import {expect} from '@jest/globals';
import type {RawMatcherFn} from 'expect';
import type {ExpectationResult} from 'expect';

const toBeWithinRange: RawMatcherFn = (
SimenB marked this conversation as resolved.
Show resolved Hide resolved
const toBeWithinRange = (
actual: number,
floor: number,
ceiling: number,
) => {
): ExpectationResult => {
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
const pass = actual >= floor && actual <= ceiling;
if (pass) {
return {
Expand Down
85 changes: 84 additions & 1 deletion packages/expect/__typetests__/expect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

import {expectError, expectType} from 'tsd-lite';
import type {EqualsFunction, Tester} from '@jest/expect-utils';
import {type Matchers, expect} from 'expect';
import {
type ExpectationResult,
type MatcherState,
type Matchers,
expect,
} from 'expect';
import type * as jestMatcherUtils from 'jest-matcher-utils';

type M = Matchers<void, unknown>;
Expand Down Expand Up @@ -79,3 +84,81 @@ expectType<void>(
bananas: expect.not.toBeWithinRange(11, 20),
}),
);

// ExpectationResult

const toBeResult = (received: string): ExpectationResult => {
if (received === 'result') {
return {
message: () => 'is result',
pass: true,
};
} else {
return {
message: () => 'is not result',
pass: false,
};
}
};

expectType<void>(expect.extend({toBeResult}));

expectError(() => {
const lacksElseBranch = (received: string): ExpectationResult => {
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
if (received === 'result') {
return {
message: () => 'is result',
pass: true,
};
}
};
});

expectError(() => {
const lacksMessage = (received: string): ExpectationResult => {
if (received === 'result') {
return {
pass: true,
};
} else {
return {
pass: false,
};
}
};
});

// MatcherState

function toHaveContext(
this: MatcherState,
received: string,
): ExpectationResult {
expectType<number>(this.assertionCalls);
expectType<string | undefined>(this.currentTestName);
expectType<(() => void) | undefined>(this.dontThrow);
expectType<Error | undefined>(this.error);
expectType<EqualsFunction>(this.equals);
expectType<boolean | undefined>(this.expand);
expectType<number | null | undefined>(this.expectedAssertionsNumber);
expectType<Error | undefined>(this.expectedAssertionsNumberError);
expectType<boolean | undefined>(this.isExpectingAssertions);
expectType<Error | undefined>(this.isExpectingAssertionsError);
expectType<boolean>(this.isNot);
expectType<string>(this.promise);
expectType<Array<Error>>(this.suppressedErrors);
expectType<string | undefined>(this.testPath);
expectType<MatcherUtils>(this.utils);

if (received === 'result') {
return {
message: () => 'is result',
pass: true,
};
} else {
return {
message: () => 'is not result',
pass: false,
};
}
}
2 changes: 1 addition & 1 deletion packages/expect/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ import type {
export type {
AsymmetricMatchers,
Expect,
ExpectationResult,
MatcherState,
Matchers,
RawMatcherFn,
} from './types';

export class JestAssertionError extends Error {
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type AsyncExpectationResult = Promise<SyncExpectationResult>;
export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult;

export type RawMatcherFn<T extends MatcherState = MatcherState> = {
(this: T, actual: any, expected: any, options?: any): ExpectationResult;
(this: T, actual: any, ...expected: Array<any>): ExpectationResult;
/** @internal */
[INTERNAL_MATCHER_FLAG]?: boolean;
};
Expand Down
16 changes: 4 additions & 12 deletions packages/jest-jasmine2/src/jestExpect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/* eslint-disable local/prefer-spread-eventually */

import {type MatcherState, type RawMatcherFn, expect} from 'expect';
import {type MatcherState, expect} from 'expect';
import {
addSerializer,
toMatchInlineSnapshot,
Expand Down Expand Up @@ -41,23 +41,15 @@ export default function jestExpect(config: {expand: boolean}): void {
jestMatchersObject[name] = function (
this: MatcherState,
...args: Array<unknown>
): RawMatcherFn {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has inferred return type of ExpectationResult without this extra type.

) {
// use "expect.extend" if you need to use equality testers (via this.equal)
const result = jasmineMatchersObject[name](null, null);
// if there is no 'negativeCompare', both should be handled by `compare`
const negativeCompare = result.negativeCompare || result.compare;

return this.isNot
? negativeCompare.apply(
null,
// @ts-expect-error
args,
)
: result.compare.apply(
null,
// @ts-expect-error
args,
);
? negativeCompare.apply(null, args)
: result.compare.apply(null, args);
};
});

Expand Down
6 changes: 3 additions & 3 deletions packages/jest-jasmine2/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import type {AssertionError} from 'assert';
import type {Config} from '@jest/types';
import type {Expect, RawMatcherFn} from 'expect';
import type {Expect, ExpectationResult} from 'expect';
import type CallTracker from './jasmine/CallTracker';
import type Env from './jasmine/Env';
import type JsApiReporter from './jasmine/JsApiReporter';
Expand Down Expand Up @@ -48,8 +48,8 @@ export interface Spy extends Record<string, any> {

type JasmineMatcher = {
(matchersUtil: unknown, context: unknown): JasmineMatcher;
compare: () => RawMatcherFn;
negativeCompare: () => RawMatcherFn;
compare(...args: Array<unknown>): ExpectationResult;
negativeCompare(...args: Array<unknown>): ExpectationResult;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle. To be precise this type should be () => (...args: Array<unknown>) => ExpectationResult, but my version eliminates these @ts-expect-error: https://github.com/facebook/jest/blob/faef0b4b7082df574a0e4423b86d468847360f17/packages/jest-jasmine2/src/jestExpect.ts#L50-L60

};

export type JasmineMatchersObject = {[id: string]: JasmineMatcher};
Expand Down
87 changes: 47 additions & 40 deletions packages/jest-snapshot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import * as fs from 'graceful-fs';
import type {Config} from '@jest/types';
import type {RawMatcherFn} from 'expect';
import type {ExpectationResult} from 'expect';
import type {FS as HasteFS} from 'jest-haste-map';
import {
BOLD_WEIGHT,
Expand Down Expand Up @@ -156,11 +156,12 @@ export const cleanup = (
};
};

export const toMatchSnapshot: RawMatcherFn<Context> = function (
export const toMatchSnapshot = function (
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
this: Context,
received: unknown,
propertiesOrHint?: object | Config.Path,
hint?: Config.Path,
) {
): ExpectationResult {
const matcherName = 'toMatchSnapshot';
let properties;

Expand Down Expand Up @@ -214,11 +215,12 @@ export const toMatchSnapshot: RawMatcherFn<Context> = function (
});
};

export const toMatchInlineSnapshot: RawMatcherFn<Context> = function (
export const toMatchInlineSnapshot = function (
this: Context,
received: unknown,
propertiesOrSnapshot?: object | string,
inlineSnapshot?: string,
) {
): ExpectationResult {
const matcherName = 'toMatchInlineSnapshot';
let properties;

Expand Down Expand Up @@ -407,11 +409,12 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => {
};
};

export const toThrowErrorMatchingSnapshot: RawMatcherFn<Context> = function (
export const toThrowErrorMatchingSnapshot = function (
this: Context,
received: unknown,
hint: string | undefined, // because error TS1016 for hint?: string
fromPromise: boolean,
) {
hint?: string,
fromPromise?: boolean,
): ExpectationResult {
const matcherName = 'toThrowErrorMatchingSnapshot';

// Future breaking change: Snapshot hint must be a string
Expand All @@ -429,40 +432,44 @@ export const toThrowErrorMatchingSnapshot: RawMatcherFn<Context> = function (
);
};

export const toThrowErrorMatchingInlineSnapshot: RawMatcherFn<Context> =
function (received: unknown, inlineSnapshot?: string, fromPromise?: boolean) {
const matcherName = 'toThrowErrorMatchingInlineSnapshot';

if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') {
const options: MatcherHintOptions = {
expectedColor: noColor,
isNot: this.isNot,
promise: this.promise,
};
export const toThrowErrorMatchingInlineSnapshot = function (
this: Context,
received: unknown,
inlineSnapshot?: string,
fromPromise?: boolean,
): ExpectationResult {
const matcherName = 'toThrowErrorMatchingInlineSnapshot';

throw new Error(
matcherErrorMessage(
matcherHint(matcherName, undefined, SNAPSHOT_ARG, options),
'Inline snapshot must be a string',
printWithType('Inline snapshot', inlineSnapshot, serialize),
),
);
}
if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') {
const options: MatcherHintOptions = {
expectedColor: noColor,
isNot: this.isNot,
promise: this.promise,
};

return _toThrowErrorMatchingSnapshot(
{
context: this,
inlineSnapshot:
inlineSnapshot !== undefined
? stripAddedIndentation(inlineSnapshot)
: undefined,
isInline: true,
matcherName,
received,
},
fromPromise,
throw new Error(
matcherErrorMessage(
matcherHint(matcherName, undefined, SNAPSHOT_ARG, options),
'Inline snapshot must be a string',
printWithType('Inline snapshot', inlineSnapshot, serialize),
),
);
};
}

return _toThrowErrorMatchingSnapshot(
{
context: this,
inlineSnapshot:
inlineSnapshot !== undefined
? stripAddedIndentation(inlineSnapshot)
: undefined,
isInline: true,
matcherName,
received,
},
fromPromise,
);
};

const _toThrowErrorMatchingSnapshot = (
config: MatchSnapshotConfig,
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21077,11 +21077,11 @@ __metadata:
linkType: hard

"tsd-lite@npm:^0.5.0, tsd-lite@npm:^0.5.1":
version: 0.5.2
resolution: "tsd-lite@npm:0.5.2"
version: 0.5.3
resolution: "tsd-lite@npm:0.5.3"
peerDependencies:
"@tsd/typescript": ^3.8.3 || ^4.0.7
checksum: e3e49a4b660149bc2056154db0b1f42d636826e9b7a1c59f4790efd6f5b3d158ab2b9c996bf4362688b41b479d9ae99ec19b09dc35211c07ccb0d0f41f006a0b
checksum: b1a5ffcd584b66e34173f0182d4cc7b2c5a145655346f9014318dd16a7ab3418d462df1cfa13923f79628e7900e5b8992c1210f59492adc9b7ef242b2026ab14
languageName: node
linkType: hard

Expand Down