From a5b52a520591bcd5500538afea94b6994d9fd909 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 17 Aug 2022 08:47:51 +0200 Subject: [PATCH] chore(types): separate MatcherContext, MatcherUtils and MatcherState (#13141) --- CHANGELOG.md | 1 + packages/expect/__typetests__/expect.test.ts | 36 ++++++++-------- packages/expect/src/asymmetricMatchers.ts | 7 +++- packages/expect/src/index.ts | 24 ++++++++--- packages/expect/src/jestMatchersObject.ts | 2 +- packages/expect/src/print.ts | 2 +- packages/expect/src/types.ts | 41 +++++++++++-------- packages/jest-expect/src/index.ts | 4 +- packages/jest-snapshot/src/index.ts | 10 ++--- packages/jest-snapshot/src/types.ts | 4 +- .../jest-types/__typetests__/expect.test.ts | 10 ++--- 11 files changed, 84 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c20e4d2a0c4d..2de38243f4f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/packages/expect/__typetests__/expect.test.ts b/packages/expect/__typetests__/expect.test.ts index ec8ed16118aa..9bc1ccae24cb 100644 --- a/packages/expect/__typetests__/expect.test.ts +++ b/packages/expect/__typetests__/expect.test.ts @@ -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'; @@ -35,16 +35,16 @@ expectType( toBeWithinRange(actual: number, floor: number, ceiling: number) { expectType(this.assertionCalls); expectType(this.currentTestName); - expectType<(() => void) | undefined>(this.dontThrow); + expectType<() => void>(this.dontThrow); expectType(this.error); expectType(this.equals); expectType(this.expand); - expectType(this.expectedAssertionsNumber); + expectType(this.expectedAssertionsNumber); expectType(this.expectedAssertionsNumberError); - expectType(this.isExpectingAssertions); + expectType(this.isExpectingAssertions); expectType(this.isExpectingAssertionsError); - expectType(this.isNot); - expectType(this.promise); + expectType(this.isNot); + expectType(this.promise); expectType>(this.suppressedErrors); expectType(this.testPath); expectType(this.utils); @@ -114,7 +114,7 @@ expectError(() => { }); type ToBeWithinRange = ( - this: MatcherState, + this: MatcherContext, actual: unknown, floor: number, ceiling: number, @@ -133,7 +133,7 @@ const toBeWithinRange: MatcherFunction<[floor: number, ceiling: number]> = ( expectAssignable(toBeWithinRange); -type AllowOmittingExpected = (this: MatcherState, actual: unknown) => any; +type AllowOmittingExpected = (this: MatcherContext, actual: unknown) => any; const allowOmittingExpected: MatcherFunction = ( actual: unknown, @@ -151,13 +151,13 @@ const allowOmittingExpected: MatcherFunction = ( expectAssignable(allowOmittingExpected); -// MatcherState +// MatcherContext const toHaveContext: MatcherFunction = function ( actual: unknown, ...expect: Array ) { - expectType(this); + expectType(this); if (expect.length !== 0) { throw new Error('This matcher does not take any expected argument.'); @@ -169,15 +169,15 @@ const toHaveContext: MatcherFunction = function ( }; }; -interface CustomState extends MatcherState { +interface CustomContext extends MatcherContext { customMethod(): void; } -const customContext: MatcherFunctionWithState = function ( +const customContext: MatcherFunctionWithContext = function ( actual: unknown, ...expect: Array ) { - expectType(this); + expectType(this); expectType(this.customMethod()); if (expect.length !== 0) { @@ -191,16 +191,16 @@ const customContext: MatcherFunctionWithState = 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(this); + expectType(this); expectType(this.customMethod()); return { diff --git a/packages/expect/src/asymmetricMatchers.ts b/packages/expect/src/asymmetricMatchers.ts index fd812b40fed5..41516ba7d364 100644 --- a/packages/expect/src/asymmetricMatchers.ts +++ b/packages/expect/src/asymmetricMatchers.ts @@ -17,6 +17,7 @@ import {pluralize} from 'jest-util'; import {getState} from './jestMatchersObject'; import type { AsymmetricMatcher as AsymmetricMatcherInterface, + MatcherContext, MatcherState, } from './types'; @@ -70,9 +71,11 @@ export abstract class AsymmetricMatcher 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(), equals, isNot: this.inverse, utils, diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index b6bd5505d013..3172ec5de0f5 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -41,7 +41,9 @@ import type { AsyncExpectationResult, Expect, ExpectationResult, + MatcherContext, MatcherState, + MatcherUtils, MatchersObject, PromiseMatcherFn, RawMatcherFn, @@ -54,9 +56,11 @@ export type { AsymmetricMatchers, BaseExpect, Expect, + MatcherContext, MatcherFunction, - MatcherFunctionWithState, + MatcherFunctionWithContext, MatcherState, + MatcherUtils, Matchers, } from './types'; @@ -74,7 +78,7 @@ const createToThrowErrorMatchingSnapshotMatcher = function ( matcher: RawMatcherFn, ) { return function ( - this: MatcherState, + this: MatcherContext, received: any, testNameOrInlineSnapshot?: string, ) { @@ -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(), + ...matcherUtilsThing, error: err, isNot, promise, - utils, }; const processResult = ( diff --git a/packages/expect/src/jestMatchersObject.ts b/packages/expect/src/jestMatchersObject.ts index 090434d66f60..d43aee7df5a1 100644 --- a/packages/expect/src/jestMatchersObject.ts +++ b/packages/expect/src/jestMatchersObject.ts @@ -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 = { + const defaultState: MatcherState = { assertionCalls: 0, expectedAssertionsNumber: null, isExpectingAssertions: false, diff --git a/packages/expect/src/print.ts b/packages/expect/src/print.ts index d4ad8a1c5028..7651715cfb7c 100644 --- a/packages/expect/src/print.ts +++ b/packages/expect/src/print.ts @@ -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') diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 3faf7956e02b..24c063f4022d 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -19,17 +19,21 @@ export type AsyncExpectationResult = Promise; export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; -export type MatcherFunctionWithState< - State extends MatcherState = MatcherState, +export type MatcherFunctionWithContext< + Context extends MatcherContext = MatcherContext, Expected extends Array = [] /** TODO should be: extends Array = [] */, -> = (this: State, actual: unknown, ...expected: Expected) => ExpectationResult; +> = ( + this: Context, + actual: unknown, + ...expected: Expected +) => ExpectationResult; export type MatcherFunction = []> = - MatcherFunctionWithState; + MatcherFunctionWithContext; // TODO should be replaced with `MatcherFunctionWithContext` -export type RawMatcherFn = { - (this: State, actual: any, ...expected: Array): ExpectationResult; +export type RawMatcherFn = { + (this: Context, actual: any, ...expected: Array): ExpectationResult; /** @internal */ [INTERNAL_MATCHER_FLAG]?: boolean; }; @@ -41,27 +45,32 @@ export type MatchersObject = { export type ThrowingMatcherFn = (actual: any) => void; export type PromiseMatcherFn = (actual: any) => Promise; +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; testPath?: string; - utils: typeof jestMatcherUtils & { - iterableEquality: Tester; - subsetEquality: Tester; - }; } +export type MatcherContext = MatcherUtils & Readonly; + export type AsymmetricMatcher = { asymmetricMatch(other: unknown): boolean; toString(): string; diff --git a/packages/jest-expect/src/index.ts b/packages/jest-expect/src/index.ts index f9eae1329f8c..424dc0a47613 100644 --- a/packages/jest-expect/src/index.ts +++ b/packages/jest-expect/src/index.ts @@ -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'; diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 1991a8d49406..398ba5909e08 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -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, @@ -153,7 +153,7 @@ export const cleanup = ( }; }; -export const toMatchSnapshot: MatcherFunctionWithState = function ( +export const toMatchSnapshot: MatcherFunctionWithContext = function ( received: unknown, propertiesOrHint?: object | string, hint?: string, @@ -211,7 +211,7 @@ export const toMatchSnapshot: MatcherFunctionWithState = function ( }); }; -export const toMatchInlineSnapshot: MatcherFunctionWithState = +export const toMatchInlineSnapshot: MatcherFunctionWithContext = function ( received: unknown, propertiesOrSnapshot?: object | string, @@ -408,7 +408,7 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { }; }; -export const toThrowErrorMatchingSnapshot: MatcherFunctionWithState = +export const toThrowErrorMatchingSnapshot: MatcherFunctionWithContext = function (received: unknown, hint?: string, fromPromise?: boolean) { const matcherName = 'toThrowErrorMatchingSnapshot'; @@ -427,7 +427,7 @@ export const toThrowErrorMatchingSnapshot: MatcherFunctionWithState = ); }; -export const toThrowErrorMatchingInlineSnapshot: MatcherFunctionWithState = +export const toThrowErrorMatchingInlineSnapshot: MatcherFunctionWithContext = function (received: unknown, inlineSnapshot?: string, fromPromise?: boolean) { const matcherName = 'toThrowErrorMatchingInlineSnapshot'; diff --git a/packages/jest-snapshot/src/types.ts b/packages/jest-snapshot/src/types.ts index 2f47b96a3ed0..1aebc1be21df 100644 --- a/packages/jest-snapshot/src/types.ts +++ b/packages/jest-snapshot/src/types.ts @@ -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; } diff --git a/packages/jest-types/__typetests__/expect.test.ts b/packages/jest-types/__typetests__/expect.test.ts index 53e3b06fe752..945dbd380451 100644 --- a/packages/jest-types/__typetests__/expect.test.ts +++ b/packages/jest-types/__typetests__/expect.test.ts @@ -361,16 +361,16 @@ expectType( toBeWithinRange(actual: number, floor: number, ceiling: number) { expectType(this.assertionCalls); expectType(this.currentTestName); - expectType<(() => void) | undefined>(this.dontThrow); + expectType<() => void>(this.dontThrow); expectType(this.error); expectType(this.equals); expectType(this.expand); - expectType(this.expectedAssertionsNumber); + expectType(this.expectedAssertionsNumber); expectType(this.expectedAssertionsNumberError); - expectType(this.isExpectingAssertions); + expectType(this.isExpectingAssertions); expectType(this.isExpectingAssertionsError); - expectType(this.isNot); - expectType(this.promise); + expectType(this.isNot); + expectType(this.promise); expectType>(this.suppressedErrors); expectType(this.testPath); expectType(this.utils);