Skip to content
Merged
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))
- `[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 = (
const toBeWithinRange = (
actual: number,
floor: number,
ceiling: number,
) => {
): ExpectationResult => {
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 => {
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 (
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