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

chore(types): separate MatcherContext, MatcherUtils and MatcherState #13141

Merged
merged 6 commits into from Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 13 additions & 13 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 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
4 changes: 2 additions & 2 deletions packages/expect/src/asymmetricMatchers.ts
Expand Up @@ -17,7 +17,7 @@ import {pluralize} from 'jest-util';
import {getState} from './jestMatchersObject';
import type {
AsymmetricMatcher as AsymmetricMatcherInterface,
MatcherState,
MatcherContext,
} from './types';

const functionToString = Function.prototype.toString;
Expand Down Expand Up @@ -70,7 +70,7 @@ export abstract class AsymmetricMatcher<T>

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

protected getMatcherContext(): MatcherState {
protected getMatcherContext(): MatcherContext {
return {
...getState(),
equals,
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>(),
Copy link
Member Author

Choose a reason for hiding this comment

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

explicit generic as otherwise it inferred MatcherContext and didn't error when MatcherContext where missing

...matcherUtilsThing,
error: err,
isNot,
promise,
utils,
};

const processResult = (
Expand Down
33 changes: 21 additions & 12 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`
Copy link
Member Author

Choose a reason for hiding this comment

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

@mrazauskas what is this TODO for?

Copy link
Contributor

Choose a reason for hiding this comment

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

If I recall it right, MatcherFunction and MatcherFunctionWithContext does not allow any type of actual and expected values. Or so. Currently there is a need to have anys internally, but would be nicer to rework these into unknowns. After this will be done, RawMatcherFn can be replaced with MatcherFunctionWithContext.

Copy link
Contributor

Choose a reason for hiding this comment

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

In other words, RawMatcherFn is redundant type, but it cannot be removed at this moment.

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,12 +45,19 @@ export type MatchersObject = {
export type ThrowingMatcherFn = (actual: any) => void;
export type PromiseMatcherFn = (actual: any) => Promise<void>;

export interface MatcherUtils {
Copy link
Member Author

Choose a reason for hiding this comment

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

I just extracted the fields that felt more "util-y" - should I take others?

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;
expectedAssertionsNumberError?: Error;
Expand All @@ -56,12 +67,10 @@ export interface MatcherState {
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