Skip to content

Commit

Permalink
chore(types): separate MatcherContext, MatcherUtils and MatcherState (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Aug 17, 2022
1 parent 79b5e41 commit a5b52a5
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[expect]` [**BREAKING**] Differentiate between `MatcherContext` `MatcherUtils` and `MatcherState` types ([#13141](https://github.com/facebook/jest/pull/13141))
- `[jest-config]` [**BREAKING**] Make `snapshotFormat` default to `escapeString: false` and `printBasicPrototype: false` ([#13036](https://github.com/facebook/jest/pull/13036))
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade to `jsdom@20` ([#13037](https://github.com/facebook/jest/pull/13037), [#13058](https://github.com/facebook/jest/pull/13058))
- `[@jest/globals]` Add `jest.Mocked`, `jest.MockedClass`, `jest.MockedFunction` and `jest.MockedObject` utility types ([#12727](https://github.com/facebook/jest/pull/12727))
Expand Down
36 changes: 18 additions & 18 deletions packages/expect/__typetests__/expect.test.ts
Expand Up @@ -8,9 +8,9 @@
import {expectAssignable, expectError, expectType} from 'tsd-lite';
import type {EqualsFunction, Tester} from '@jest/expect-utils';
import {
MatcherContext,
MatcherFunction,
MatcherFunctionWithState,
MatcherState,
MatcherFunctionWithContext,
Matchers,
expect,
} from 'expect';
Expand All @@ -35,16 +35,16 @@ expectType<void>(
toBeWithinRange(actual: number, floor: number, ceiling: number) {
expectType<number>(this.assertionCalls);
expectType<string | undefined>(this.currentTestName);
expectType<(() => void) | undefined>(this.dontThrow);
expectType<() => void>(this.dontThrow);
expectType<Error | undefined>(this.error);
expectType<EqualsFunction>(this.equals);
expectType<boolean | undefined>(this.expand);
expectType<number | null | undefined>(this.expectedAssertionsNumber);
expectType<number | null>(this.expectedAssertionsNumber);
expectType<Error | undefined>(this.expectedAssertionsNumberError);
expectType<boolean | undefined>(this.isExpectingAssertions);
expectType<boolean>(this.isExpectingAssertions);
expectType<Error | undefined>(this.isExpectingAssertionsError);
expectType<boolean>(this.isNot);
expectType<string>(this.promise);
expectType<boolean | undefined>(this.isNot);
expectType<string | undefined>(this.promise);
expectType<Array<Error>>(this.suppressedErrors);
expectType<string | undefined>(this.testPath);
expectType<MatcherUtils>(this.utils);
Expand Down Expand Up @@ -114,7 +114,7 @@ expectError(() => {
});

type ToBeWithinRange = (
this: MatcherState,
this: MatcherContext,
actual: unknown,
floor: number,
ceiling: number,
Expand All @@ -133,7 +133,7 @@ const toBeWithinRange: MatcherFunction<[floor: number, ceiling: number]> = (

expectAssignable<ToBeWithinRange>(toBeWithinRange);

type AllowOmittingExpected = (this: MatcherState, actual: unknown) => any;
type AllowOmittingExpected = (this: MatcherContext, actual: unknown) => any;

const allowOmittingExpected: MatcherFunction = (
actual: unknown,
Expand All @@ -151,13 +151,13 @@ const allowOmittingExpected: MatcherFunction = (

expectAssignable<AllowOmittingExpected>(allowOmittingExpected);

// MatcherState
// MatcherContext

const toHaveContext: MatcherFunction = function (
actual: unknown,
...expect: Array<unknown>
) {
expectType<MatcherState>(this);
expectType<MatcherContext>(this);

if (expect.length !== 0) {
throw new Error('This matcher does not take any expected argument.');
Expand All @@ -169,15 +169,15 @@ const toHaveContext: MatcherFunction = function (
};
};

interface CustomState extends MatcherState {
interface CustomContext extends MatcherContext {
customMethod(): void;
}

const customContext: MatcherFunctionWithState<CustomState> = function (
const customContext: MatcherFunctionWithContext<CustomContext> = function (
actual: unknown,
...expect: Array<unknown>
) {
expectType<CustomState>(this);
expectType<CustomContext>(this);
expectType<void>(this.customMethod());

if (expect.length !== 0) {
Expand All @@ -191,16 +191,16 @@ const customContext: MatcherFunctionWithState<CustomState> = function (
};

type CustomStateAndExpected = (
this: CustomState,
this: CustomContext,
actual: unknown,
count: number,
) => any;

const customStateAndExpected: MatcherFunctionWithState<
CustomState,
const customStateAndExpected: MatcherFunctionWithContext<
CustomContext,
[count: number]
> = function (actual: unknown, count: unknown) {
expectType<CustomState>(this);
expectType<CustomContext>(this);
expectType<void>(this.customMethod());

return {
Expand Down
7 changes: 5 additions & 2 deletions packages/expect/src/asymmetricMatchers.ts
Expand Up @@ -17,6 +17,7 @@ import {pluralize} from 'jest-util';
import {getState} from './jestMatchersObject';
import type {
AsymmetricMatcher as AsymmetricMatcherInterface,
MatcherContext,
MatcherState,
} from './types';

Expand Down Expand Up @@ -70,9 +71,11 @@ export abstract class AsymmetricMatcher<T>

constructor(protected sample: T, protected inverse = false) {}

protected getMatcherContext(): MatcherState {
protected getMatcherContext(): MatcherContext {
return {
...getState(),
// eslint-disable-next-line @typescript-eslint/no-empty-function
dontThrow: () => {},
...getState<MatcherState>(),
equals,
isNot: this.inverse,
utils,
Expand Down
24 changes: 18 additions & 6 deletions packages/expect/src/index.ts
Expand Up @@ -41,7 +41,9 @@ import type {
AsyncExpectationResult,
Expect,
ExpectationResult,
MatcherContext,
MatcherState,
MatcherUtils,
MatchersObject,
PromiseMatcherFn,
RawMatcherFn,
Expand All @@ -54,9 +56,11 @@ export type {
AsymmetricMatchers,
BaseExpect,
Expect,
MatcherContext,
MatcherFunction,
MatcherFunctionWithState,
MatcherFunctionWithContext,
MatcherState,
MatcherUtils,
Matchers,
} from './types';

Expand All @@ -74,7 +78,7 @@ const createToThrowErrorMatchingSnapshotMatcher = function (
matcher: RawMatcherFn,
) {
return function (
this: MatcherState,
this: MatcherContext,
received: any,
testNameOrInlineSnapshot?: string,
) {
Expand Down Expand Up @@ -269,21 +273,29 @@ const makeThrowingMatcher = (
): ThrowingMatcherFn =>
function throwingMatcher(...args): any {
let throws = true;
const utils = {...matcherUtils, iterableEquality, subsetEquality};
const utils: MatcherUtils['utils'] = {
...matcherUtils,
iterableEquality,
subsetEquality,
};

const matcherContext: MatcherState = {
const matcherUtilsThing: MatcherUtils = {
// When throws is disabled, the matcher will not throw errors during test
// execution but instead add them to the global matcher state. If a
// matcher throws, test execution is normally stopped immediately. The
// snapshot matcher uses it because we want to log all snapshot
// failures in a test.
dontThrow: () => (throws = false),
...getState(),
equals,
utils,
};

const matcherContext: MatcherContext = {
...getState<MatcherState>(),
...matcherUtilsThing,
error: err,
isNot,
promise,
utils,
};

const processResult = (
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/src/jestMatchersObject.ts
Expand Up @@ -24,7 +24,7 @@ const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object');
export const INTERNAL_MATCHER_FLAG = Symbol.for('$$jest-internal-matcher');

if (!Object.prototype.hasOwnProperty.call(globalThis, JEST_MATCHERS_OBJECT)) {
const defaultState: Partial<MatcherState> = {
const defaultState: MatcherState = {
assertionCalls: 0,
expectedAssertionsNumber: null,
isExpectingAssertions: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/src/print.ts
Expand Up @@ -63,7 +63,7 @@ export const printCloseTo = (
receivedDiff: number,
expectedDiff: number,
precision: number,
isNot: boolean,
isNot: boolean | undefined,
): string => {
const receivedDiffString = stringify(receivedDiff);
const expectedDiffString = receivedDiffString.includes('e')
Expand Down
41 changes: 25 additions & 16 deletions packages/expect/src/types.ts
Expand Up @@ -19,17 +19,21 @@ export type AsyncExpectationResult = Promise<SyncExpectationResult>;

export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult;

export type MatcherFunctionWithState<
State extends MatcherState = MatcherState,
export type MatcherFunctionWithContext<
Context extends MatcherContext = MatcherContext,
Expected extends Array<any> = [] /** TODO should be: extends Array<unknown> = [] */,
> = (this: State, actual: unknown, ...expected: Expected) => ExpectationResult;
> = (
this: Context,
actual: unknown,
...expected: Expected
) => ExpectationResult;

export type MatcherFunction<Expected extends Array<unknown> = []> =
MatcherFunctionWithState<MatcherState, Expected>;
MatcherFunctionWithContext<MatcherContext, Expected>;

// TODO should be replaced with `MatcherFunctionWithContext`
export type RawMatcherFn<State extends MatcherState = MatcherState> = {
(this: State, actual: any, ...expected: Array<any>): ExpectationResult;
export type RawMatcherFn<Context extends MatcherContext = MatcherContext> = {
(this: Context, actual: any, ...expected: Array<any>): ExpectationResult;
/** @internal */
[INTERNAL_MATCHER_FLAG]?: boolean;
};
Expand All @@ -41,27 +45,32 @@ export type MatchersObject = {
export type ThrowingMatcherFn = (actual: any) => void;
export type PromiseMatcherFn = (actual: any) => Promise<void>;

export interface MatcherUtils {
dontThrow(): void;
equals: EqualsFunction;
utils: typeof jestMatcherUtils & {
iterableEquality: Tester;
subsetEquality: Tester;
};
}

export interface MatcherState {
assertionCalls: number;
currentTestName?: string;
dontThrow?(): void;
error?: Error;
equals: EqualsFunction;
expand?: boolean;
expectedAssertionsNumber?: number | null;
expectedAssertionsNumber: number | null;
expectedAssertionsNumberError?: Error;
isExpectingAssertions?: boolean;
isExpectingAssertions: boolean;
isExpectingAssertionsError?: Error;
isNot: boolean;
promise: string;
isNot?: boolean;
promise?: string;
suppressedErrors: Array<Error>;
testPath?: string;
utils: typeof jestMatcherUtils & {
iterableEquality: Tester;
subsetEquality: Tester;
};
}

export type MatcherContext = MatcherUtils & Readonly<MatcherState>;

export type AsymmetricMatcher = {
asymmetricMatch(other: unknown): boolean;
toString(): string;
Expand Down
4 changes: 3 additions & 1 deletion packages/jest-expect/src/index.ts
Expand Up @@ -18,9 +18,11 @@ import type {JestExpect} from './types';
export type {
AsymmetricMatchers,
Matchers,
MatcherContext,
MatcherFunction,
MatcherFunctionWithState,
MatcherFunctionWithContext,
MatcherState,
MatcherUtils,
} from 'expect';
export type {JestExpect} from './types';

Expand Down
10 changes: 5 additions & 5 deletions packages/jest-snapshot/src/index.ts
Expand Up @@ -7,7 +7,7 @@

import * as fs from 'graceful-fs';
import type {Config} from '@jest/types';
import type {MatcherFunctionWithState} from 'expect';
import type {MatcherFunctionWithContext} from 'expect';
import type {FS as HasteFS} from 'jest-haste-map';
import {
BOLD_WEIGHT,
Expand Down Expand Up @@ -153,7 +153,7 @@ export const cleanup = (
};
};

export const toMatchSnapshot: MatcherFunctionWithState<Context> = function (
export const toMatchSnapshot: MatcherFunctionWithContext<Context> = function (
received: unknown,
propertiesOrHint?: object | string,
hint?: string,
Expand Down Expand Up @@ -211,7 +211,7 @@ export const toMatchSnapshot: MatcherFunctionWithState<Context> = function (
});
};

export const toMatchInlineSnapshot: MatcherFunctionWithState<Context> =
export const toMatchInlineSnapshot: MatcherFunctionWithContext<Context> =
function (
received: unknown,
propertiesOrSnapshot?: object | string,
Expand Down Expand Up @@ -408,7 +408,7 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => {
};
};

export const toThrowErrorMatchingSnapshot: MatcherFunctionWithState<Context> =
export const toThrowErrorMatchingSnapshot: MatcherFunctionWithContext<Context> =
function (received: unknown, hint?: string, fromPromise?: boolean) {
const matcherName = 'toThrowErrorMatchingSnapshot';

Expand All @@ -427,7 +427,7 @@ export const toThrowErrorMatchingSnapshot: MatcherFunctionWithState<Context> =
);
};

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

Expand Down
4 changes: 2 additions & 2 deletions packages/jest-snapshot/src/types.ts
Expand Up @@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/

import type {MatcherState} from 'expect';
import type {MatcherContext} from 'expect';
import type SnapshotState from './State';

export interface Context extends MatcherState {
export interface Context extends MatcherContext {
snapshotState: SnapshotState;
}

Expand Down
10 changes: 5 additions & 5 deletions packages/jest-types/__typetests__/expect.test.ts
Expand Up @@ -361,16 +361,16 @@ expectType<void>(
toBeWithinRange(actual: number, floor: number, ceiling: number) {
expectType<number>(this.assertionCalls);
expectType<string | undefined>(this.currentTestName);
expectType<(() => void) | undefined>(this.dontThrow);
expectType<() => void>(this.dontThrow);
expectType<Error | undefined>(this.error);
expectType<EqualsFunction>(this.equals);
expectType<boolean | undefined>(this.expand);
expectType<number | null | undefined>(this.expectedAssertionsNumber);
expectType<number | null>(this.expectedAssertionsNumber);
expectType<Error | undefined>(this.expectedAssertionsNumberError);
expectType<boolean | undefined>(this.isExpectingAssertions);
expectType<boolean>(this.isExpectingAssertions);
expectType<Error | undefined>(this.isExpectingAssertionsError);
expectType<boolean>(this.isNot);
expectType<string>(this.promise);
expectType<boolean | undefined>(this.isNot);
expectType<string | undefined>(this.promise);
expectType<Array<Error>>(this.suppressedErrors);
expectType<string | undefined>(this.testPath);
expectType<MatcherUtils>(this.utils);
Expand Down

0 comments on commit a5b52a5

Please sign in to comment.