From 92706c8861e1dd1bac09ee731af12465130c8cc2 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:07:21 +0200 Subject: [PATCH 01/11] move `expect` types to `@jest/types` --- .eslintrc.js | 4 +- .../expect-extend/__tests__/ranges.test.ts | 20 ++++ examples/expect-extend/package.json | 29 +++++ examples/expect-extend/toBeWithinRange.ts | 38 +++++++ jest.config.js | 2 +- jest.config.types.js | 20 +--- packages/expect/__typechecks__/expect.test.ts | 105 ++++++++++++++++++ packages/expect/package.json | 3 +- packages/expect/src/asymmetricMatchers.ts | 15 +-- .../src/extractExpectedAssertionsErrors.ts | 6 +- packages/expect/src/index.ts | 67 +++++------ packages/expect/src/jasmineUtils.ts | 6 +- packages/expect/src/jestMatchersObject.ts | 39 ++++--- packages/expect/src/matchers.ts | 4 +- packages/expect/src/spyMatchers.ts | 48 ++++---- packages/expect/src/toThrowMatchers.ts | 28 ++--- packages/expect/src/types-patch.ts | 13 +++ .../jestAdapterInit.ts | 12 +- .../legacy-code-todo-rewrite/jestExpect.ts | 14 +-- packages/jest-circus/src/types.ts | 6 +- packages/jest-circus/tsconfig.json | 2 + packages/jest-globals/package.json | 2 +- packages/jest-globals/src/index.ts | 25 ++++- packages/jest-globals/tsconfig.json | 2 +- packages/jest-jasmine2/src/jestExpect.ts | 30 ++--- .../jest-jasmine2/src/setup_jest_globals.ts | 2 +- packages/jest-jasmine2/src/types.ts | 30 +---- packages/jest-jasmine2/tsconfig.json | 2 + packages/jest-snapshot/src/index.ts | 20 ++-- packages/jest-snapshot/src/types.ts | 29 +++-- .../jest-types/__typechecks__/Config.test.ts | 0 .../jest-types/__typechecks__/Expect.test.ts | 88 ++++++++++++--- .../jest-types/__typechecks__/Globals.test.ts | 0 .../jest-types/__typechecks__/Jest.test.ts | 0 packages/jest-types/package.json | 3 + packages/jest-types/src/Circus.ts | 7 +- .../src/types.ts => jest-types/src/Expect.ts} | 64 ++++++----- packages/jest-types/src/Global.ts | 2 + packages/jest-types/src/index.ts | 3 +- test-types/expect.test.ts | 13 --- tsconfig.json | 1 + yarn.lock | 20 +++- 42 files changed, 529 insertions(+), 295 deletions(-) create mode 100644 examples/expect-extend/__tests__/ranges.test.ts create mode 100644 examples/expect-extend/package.json create mode 100644 examples/expect-extend/toBeWithinRange.ts create mode 100644 packages/expect/__typechecks__/expect.test.ts create mode 100644 packages/expect/src/types-patch.ts rename test-types/top-level-config.test.ts => packages/jest-types/__typechecks__/Config.test.ts (100%) rename test-types/top-level-expect-namespace.test.ts => packages/jest-types/__typechecks__/Expect.test.ts (82%) rename test-types/top-level-globals.test.ts => packages/jest-types/__typechecks__/Globals.test.ts (100%) rename test-types/top-level-jest-namespace.test.ts => packages/jest-types/__typechecks__/Jest.test.ts (100%) rename packages/{expect/src/types.ts => jest-types/src/Expect.ts} (89%) delete mode 100644 test-types/expect.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index 905133b3a923..ded3411aaef2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -234,7 +234,7 @@ module.exports = { }, }, { - files: ['test-types/*.test.ts', '*.md'], + files: ['**/__typechecks__/**', '*.md'], rules: { 'jest/no-focused-tests': 'off', 'jest/no-identical-title': 'off', @@ -301,9 +301,9 @@ module.exports = { 'error', { devDependencies: [ - '/test-types/**', '**/__tests__/**', '**/__mocks__/**', + '**/__typechecks__/**', '**/?(*.)(spec|test).js?(x)', 'scripts/**', 'babel.config.js', diff --git a/examples/expect-extend/__tests__/ranges.test.ts b/examples/expect-extend/__tests__/ranges.test.ts new file mode 100644 index 000000000000..77f80e8a5ef8 --- /dev/null +++ b/examples/expect-extend/__tests__/ranges.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {expect, test} from '@jest/globals'; +import '../toBeWithinRange'; + +test('is within range', () => expect(100).toBeWithinRange(90, 110)); + +test('is NOT within range', () => expect(101).not.toBeWithinRange(0, 100)); + +test('asymmetric ranges', () => { + expect({apples: 6, bananas: 3}).toEqual({ + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), + }); +}); diff --git a/examples/expect-extend/package.json b/examples/expect-extend/package.json new file mode 100644 index 000000000000..ae42cf64bac5 --- /dev/null +++ b/examples/expect-extend/package.json @@ -0,0 +1,29 @@ +{ + "private": true, + "version": "0.0.0", + "name": "example-expect-extend", + "devDependencies": { + "@babel/core": "*", + "@babel/preset-env": "*", + "@babel/preset-typescript": "*", + "@jest/globals": "*", + "babel-jest": "*", + "jest": "*" + }, + "scripts": { + "test": "jest" + }, + "babel": { + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ], + "@babel/preset-typescript" + ] + } +} diff --git a/examples/expect-extend/toBeWithinRange.ts b/examples/expect-extend/toBeWithinRange.ts new file mode 100644 index 000000000000..ff937aa6b64c --- /dev/null +++ b/examples/expect-extend/toBeWithinRange.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {expect} from '@jest/globals'; + +expect.extend({ + toBeWithinRange(actual: number, floor: number, ceiling: number) { + const pass = actual >= floor && actual <= ceiling; + if (pass) { + return { + message: () => + `expected ${actual} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${actual} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } + }, +}); + +declare module '@jest/types' { + namespace Expect { + interface AsymmetricMatchers { + toBeWithinRange(a: number, b: number): AsymmetricMatcher; + } + interface Matchers { + toBeWithinRange(a: number, b: number): R; + } + } +} diff --git a/jest.config.js b/jest.config.js index 4ef88e4708fb..e8e4ff73eb23 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,8 +35,8 @@ module.exports = { require.resolve('jest-snapshot-serializer-raw'), ], testPathIgnorePatterns: [ - '/test-types/', '/__arbitraries__/', + '/__typechecks__/', '/node_modules/', '/examples/', '/e2e/.*/__tests__', diff --git a/jest.config.types.js b/jest.config.types.js index b53ac3c5abda..77a65008fffb 100644 --- a/jest.config.types.js +++ b/jest.config.types.js @@ -7,20 +7,7 @@ 'use strict'; -const assert = require('assert'); -const baseConfig = require('./jest.config'); - -const { - modulePathIgnorePatterns, - testPathIgnorePatterns, - watchPathIgnorePatterns, -} = baseConfig; - -assert.strictEqual( - testPathIgnorePatterns[0], - '/test-types/', - 'First entry must be types', -); +const {modulePathIgnorePatterns} = require('./jest.config'); module.exports = { displayName: { @@ -28,8 +15,7 @@ module.exports = { name: 'types', }, modulePathIgnorePatterns, + roots: ['/packages'], runner: 'jest-runner-tsd', - testMatch: ['/test-types/*.test.ts'], - testPathIgnorePatterns: testPathIgnorePatterns.slice(1), - watchPathIgnorePatterns, + testMatch: ['**/__typechecks__/**/*.ts'], }; diff --git a/packages/expect/__typechecks__/expect.test.ts b/packages/expect/__typechecks__/expect.test.ts new file mode 100644 index 000000000000..1b37cb46d171 --- /dev/null +++ b/packages/expect/__typechecks__/expect.test.ts @@ -0,0 +1,105 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {expectError, expectType} from 'mlh-tsd'; +import * as expect from 'expect'; +import type * as jestMatcherUtils from 'jest-matcher-utils'; + +export type M = expect.Matchers; +export type N = expect.Matchers; +// @ts-expect-error +export type E = expect.Matchers<>; + +// extend + +type Tester = (a: any, b: any) => boolean | undefined; + +type MatcherUtils = typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; +}; + +expectType( + expect.extend({ + toBeWithinRange(actual: number, floor: number, ceiling: number) { + expectType(this.assertionCalls); + expectType(this.currentTestName); + expectType<(() => void) | undefined>(this.dontThrow); + expectType(this.error); + expectType< + ( + a: unknown, + b: unknown, + customTesters?: Array, + strictCheck?: boolean, + ) => boolean + >(this.equals); + expectType(this.expand); + expectType(this.expectedAssertionsNumber); + expectType(this.expectedAssertionsNumberError); + expectType(this.isExpectingAssertions); + expectType(this.isExpectingAssertionsError); + expectType(this.isNot); + expectType(this.promise); + expectType>(this.suppressedErrors); + expectType(this.testPath); + expectType(this.utils); + + // `snapshotState` type should not leak from `@jest/types` + + expectError(this.snapshotState); + + const pass = actual >= floor && actual <= ceiling; + if (pass) { + return { + message: () => + `expected ${actual} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${actual} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } + }, + }), +); + +declare module '@jest/types' { + namespace Expect { + interface AsymmetricMatchers { + toBeWithinRange(floor: number, ceiling: number): AsymmetricMatcher; + } + interface Matchers { + toBeWithinRange(floor: number, ceiling: number): R; + } + } +} + +expectType(expect(100).toBeWithinRange(90, 110)); +expectType(expect(101).not.toBeWithinRange(0, 100)); + +expectType( + expect({apples: 6, bananas: 3}).toEqual({ + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), + }), +); + +// `addSnapshotSerializer` type should not leak from `@jest/types` + +expectError(expect.addSnapshotSerializer()); + +// snapshot matchers types should not leak from `@jest/types` + +expectError(expect({a: 1}).toMatchSnapshot()); +expectError(expect('abc').toMatchInlineSnapshot()); + +expectError(expect(jest.fn()).toThrowErrorMatchingSnapshot()); +expectError(expect(jest.fn()).toThrowErrorMatchingInlineSnapshot()); diff --git a/packages/expect/package.json b/packages/expect/package.json index 606606abea91..1d49ee84351b 100644 --- a/packages/expect/package.json +++ b/packages/expect/package.json @@ -27,7 +27,8 @@ "@jest/test-utils": "^27.3.1", "chalk": "^4.0.0", "fast-check": "^2.0.0", - "immutable": "^4.0.0-rc.12" + "immutable": "^4.0.0-rc.12", + "mlh-tsd": "^0.14.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" diff --git a/packages/expect/src/asymmetricMatchers.ts b/packages/expect/src/asymmetricMatchers.ts index 02b19c363956..5a32d74cf29e 100644 --- a/packages/expect/src/asymmetricMatchers.ts +++ b/packages/expect/src/asymmetricMatchers.ts @@ -6,13 +6,10 @@ * */ +import type {Expect} from '@jest/types'; import * as matcherUtils from 'jest-matcher-utils'; import {equals, fnNameFor, hasProperty, isA, isUndefined} from './jasmineUtils'; import {getState} from './jestMatchersObject'; -import type { - AsymmetricMatcher as AsymmetricMatcherInterface, - MatcherState, -} from './types'; import {iterableEquality, subsetEquality} from './utils'; const utils = Object.freeze({ @@ -21,22 +18,18 @@ const utils = Object.freeze({ subsetEquality, }); -export abstract class AsymmetricMatcher< - T, - State extends MatcherState = MatcherState, -> implements AsymmetricMatcherInterface -{ +export abstract class AsymmetricMatcher implements Expect.AsymmetricMatcher { $$typeof = Symbol.for('jest.asymmetricMatcher'); constructor(protected sample: T, protected inverse = false) {} - protected getMatcherContext(): State { + protected getMatcherContext(): Expect.MatcherState { return { ...getState(), equals, isNot: this.inverse, utils, - } as State; + }; } abstract asymmetricMatch(other: unknown): boolean; diff --git a/packages/expect/src/extractExpectedAssertionsErrors.ts b/packages/expect/src/extractExpectedAssertionsErrors.ts index ebc24cd9d976..81a0f7808ce2 100644 --- a/packages/expect/src/extractExpectedAssertionsErrors.ts +++ b/packages/expect/src/extractExpectedAssertionsErrors.ts @@ -6,6 +6,7 @@ * */ +import type {Expect} from '@jest/types'; import { EXPECTED_COLOR, RECEIVED_COLOR, @@ -13,7 +14,6 @@ import { pluralize, } from 'jest-matcher-utils'; import {getState, setState} from './jestMatchersObject'; -import type {Expect, ExpectedAssertionsErrors} from './types'; const resetAssertionsLocalState = () => { setState({ @@ -25,9 +25,9 @@ const resetAssertionsLocalState = () => { // Create and format all errors related to the mismatched number of `expect` // calls and reset the matcher's state. -const extractExpectedAssertionsErrors: Expect['extractExpectedAssertionsErrors'] = +const extractExpectedAssertionsErrors: Expect.Expect['extractExpectedAssertionsErrors'] = () => { - const result: ExpectedAssertionsErrors = []; + const result: Expect.ExpectedAssertionsErrors = []; const { assertionCalls, expectedAssertionsNumber, diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 75ab9ba9d18d..1a9c98f4df0f 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -8,6 +8,7 @@ /* eslint-disable local/prefer-spread-eventually */ +import type {Expect} from '@jest/types'; import * as matcherUtils from 'jest-matcher-utils'; import { any, @@ -25,6 +26,8 @@ import extractExpectedAssertionsErrors from './extractExpectedAssertionsErrors'; import {equals} from './jasmineUtils'; import { INTERNAL_MATCHER_FLAG, + JestMatchersObject, + JestRawMatcherFn, getMatchers, getState, setMatchers, @@ -35,22 +38,13 @@ import spyMatchers from './spyMatchers'; import toThrowMatchers, { createMatcher as createThrowMatcher, } from './toThrowMatchers'; -import type { - AsyncExpectationResult, - Expect, - ExpectationResult, - MatcherState as JestMatcherState, - Matchers as MatcherInterface, - MatchersObject, - PromiseMatcherFn, - RawMatcherFn, - SyncExpectationResult, - ThrowingMatcherFn, -} from './types'; import {iterableEquality, subsetEquality} from './utils'; +import './types-patch'; class JestAssertionError extends Error { - matcherResult?: Omit & {message: string}; + matcherResult?: Omit & { + message: string; + }; } const isPromise = (obj: any): obj is PromiseLike => @@ -59,10 +53,10 @@ const isPromise = (obj: any): obj is PromiseLike => typeof obj.then === 'function'; const createToThrowErrorMatchingSnapshotMatcher = function ( - matcher: RawMatcherFn, + matcher: JestRawMatcherFn, ) { return function ( - this: JestMatcherState, + this: Expect.MatcherState, received: any, testNameOrInlineSnapshot?: string, ) { @@ -83,7 +77,7 @@ const getPromiseMatcher = (name: string, matcher: any) => { return null; }; -const expect: any = (actual: any, ...rest: Array) => { +const expect: Expect.Expect = (actual: any, ...rest: Array) => { if (rest.length !== 0) { throw new Error('Expect takes at most one argument.'); } @@ -144,11 +138,11 @@ const getMessage = (message?: () => string) => const makeResolveMatcher = ( matcherName: string, - matcher: RawMatcherFn, + matcher: JestRawMatcherFn, isNot: boolean, actual: Promise, outerErr: JestAssertionError, - ): PromiseMatcherFn => + ): Expect.PromiseMatcherFn => (...args) => { const options = { isNot, @@ -191,11 +185,11 @@ const makeResolveMatcher = const makeRejectMatcher = ( matcherName: string, - matcher: RawMatcherFn, + matcher: JestRawMatcherFn, isNot: boolean, actual: Promise | (() => Promise), outerErr: JestAssertionError, - ): PromiseMatcherFn => + ): Expect.PromiseMatcherFn => (...args) => { const options = { isNot, @@ -241,17 +235,17 @@ const makeRejectMatcher = }; const makeThrowingMatcher = ( - matcher: RawMatcherFn, + matcher: JestRawMatcherFn, isNot: boolean, promise: string, actual: any, err?: JestAssertionError, -): ThrowingMatcherFn => +): Expect.ThrowingMatcherFn => function throwingMatcher(...args): any { let throws = true; const utils = {...matcherUtils, iterableEquality, subsetEquality}; - const matcherContext: JestMatcherState = { + const matcherContext: Expect.MatcherState = { // 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 @@ -267,7 +261,7 @@ const makeThrowingMatcher = ( }; const processResult = ( - result: SyncExpectationResult, + result: Expect.SyncExpectationResult, asyncError?: JestAssertionError, ) => { _validateResult(result); @@ -321,7 +315,7 @@ const makeThrowingMatcher = ( throw error; }; - let potentialResult: ExpectationResult; + let potentialResult: Expect.ExpectationResult; try { potentialResult = @@ -335,7 +329,7 @@ const makeThrowingMatcher = ( })(); if (isPromise(potentialResult)) { - const asyncResult = potentialResult as AsyncExpectationResult; + const asyncResult = potentialResult as Expect.AsyncExpectationResult; const asyncError = new JestAssertionError(); if (Error.captureStackTrace) { Error.captureStackTrace(asyncError, throwingMatcher); @@ -345,7 +339,7 @@ const makeThrowingMatcher = ( .then(aResult => processResult(aResult, asyncError)) .catch(handleError); } else { - const syncResult = potentialResult as SyncExpectationResult; + const syncResult = potentialResult as Expect.SyncExpectationResult; return processResult(syncResult); } @@ -354,9 +348,8 @@ const makeThrowingMatcher = ( } }; -expect.extend = ( - matchers: MatchersObject, -): void => setMatchers(matchers, false, expect); +expect.extend = (matchers: JestMatchersObject): void => + setMatchers(matchers, false, expect); expect.anything = anything; expect.any = any; @@ -416,22 +409,22 @@ function hasAssertions(...args: Array) { } // add default jest matchers -setMatchers(matchers, true, expect as Expect); -setMatchers(spyMatchers, true, expect as Expect); -setMatchers(toThrowMatchers, true, expect as Expect); +setMatchers(matchers, true, expect); +setMatchers(spyMatchers, true, expect); +setMatchers(toThrowMatchers, true, expect); -expect.addSnapshotSerializer = () => void 0; expect.assertions = assertions; expect.hasAssertions = hasAssertions; expect.getState = getState; expect.setState = setState; expect.extractExpectedAssertionsErrors = extractExpectedAssertionsErrors; -const expectExport = expect as Expect; +const expectExport = expect as Expect.Expect; +// TODO: remove reexport of types in Jest 28. The types must be imported directly from @jest/types declare namespace expectExport { - export type MatcherState = JestMatcherState; - export interface Matchers extends MatcherInterface {} + export type MatcherState = Expect.MatcherState; + export interface Matchers extends Expect.Matchers {} } export = expectExport; diff --git a/packages/expect/src/jasmineUtils.ts b/packages/expect/src/jasmineUtils.ts index 8388975dfbb0..3de94f7140d1 100644 --- a/packages/expect/src/jasmineUtils.ts +++ b/packages/expect/src/jasmineUtils.ts @@ -24,13 +24,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* eslint-disable */ -import type {Tester} from './types'; +import type {Expect} from '@jest/types'; // Extracted out of jasmine 2.5.2 export function equals( a: unknown, b: unknown, - customTesters?: Array, + customTesters?: Array, strictCheck?: boolean, ): boolean { customTesters = customTesters || []; @@ -67,7 +67,7 @@ function eq( b: any, aStack: Array, bStack: Array, - customTesters: Array, + customTesters: Array, hasKey: any, ): boolean { var result = true; diff --git a/packages/expect/src/jestMatchersObject.ts b/packages/expect/src/jestMatchersObject.ts index cbfe6344a6db..e488db14d6de 100644 --- a/packages/expect/src/jestMatchersObject.ts +++ b/packages/expect/src/jestMatchersObject.ts @@ -6,13 +6,8 @@ * */ +import type {Expect} from '@jest/types'; import {AsymmetricMatcher} from './asymmetricMatchers'; -import type { - Expect, - MatcherState, - MatchersObject, - SyncExpectationResult, -} from './types'; // Global matchers object holds the list of available matchers and // the state, that can hold matcher specific values that change over time. @@ -22,8 +17,16 @@ const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object'); // Jest may override the stack trace of Errors thrown by internal matchers. export const INTERNAL_MATCHER_FLAG = Symbol.for('$$jest-internal-matcher'); +export type JestRawMatcherFn = Expect.RawMatcherFn & { + [INTERNAL_MATCHER_FLAG]?: boolean; +}; + +export type JestMatchersObject = { + [matcherName: string]: JestRawMatcherFn; +}; + if (!global.hasOwnProperty(JEST_MATCHERS_OBJECT)) { - const defaultState: Partial = { + const defaultState: Partial = { assertionCalls: 0, expectedAssertionsNumber: null, isExpectingAssertions: false, @@ -37,23 +40,20 @@ if (!global.hasOwnProperty(JEST_MATCHERS_OBJECT)) { }); } -export const getState = (): State => +export const getState = (): Expect.MatcherState => (global as any)[JEST_MATCHERS_OBJECT].state; -export const setState = ( - state: Partial, -): void => { +export const setState = (state: Partial): void => { Object.assign((global as any)[JEST_MATCHERS_OBJECT].state, state); }; -export const getMatchers = < - State extends MatcherState = MatcherState, ->(): MatchersObject => (global as any)[JEST_MATCHERS_OBJECT].matchers; +export const getMatchers = (): Expect.MatchersObject => + (global as any)[JEST_MATCHERS_OBJECT].matchers; -export const setMatchers = ( - matchers: MatchersObject, +export const setMatchers = ( + matchers: Expect.MatchersObject, isInternal: boolean, - expect: Expect, + expect: Expect.Expect, ): void => { Object.keys(matchers).forEach(key => { const matcher = matchers[key]; @@ -65,8 +65,7 @@ export const setMatchers = ( // expect is defined class CustomMatcher extends AsymmetricMatcher< - [unknown, ...Array], - State + [unknown, ...Array] > { constructor( inverse: boolean = false, @@ -80,7 +79,7 @@ export const setMatchers = ( this.getMatcherContext(), other, ...this.sample, - ) as SyncExpectationResult; + ) as Expect.SyncExpectationResult; return this.inverse ? !pass : pass; } diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index f7f019129d51..606cfe9315c1 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -8,6 +8,7 @@ /* eslint-disable local/ban-types-eventually */ +import type {Expect} from '@jest/types'; import {getType, isPrimitive} from 'jest-get-type'; import { DIM_COLOR, @@ -38,7 +39,6 @@ import { printReceivedStringContainExpectedResult, printReceivedStringContainExpectedSubstring, } from './print'; -import type {MatchersObject} from './types'; import { arrayBufferEquality, getObjectSubset, @@ -72,7 +72,7 @@ type ContainIterable = | DOMTokenList | HTMLCollectionOf; -const matchers: MatchersObject = { +const matchers: Expect.MatchersObject = { toBe(received: unknown, expected: unknown) { const matcherName = 'toBe'; const options: MatcherHintOptions = { diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index 3c930e9e01c7..a903de4c17cf 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import type {Expect} from '@jest/types'; import {getType, isPrimitive} from 'jest-get-type'; import { DIM_COLOR, @@ -22,11 +23,6 @@ import { stringify, } from 'jest-matcher-utils'; import {equals} from './jasmineUtils'; -import type { - MatcherState, - MatchersObject, - SyncExpectationResult, -} from './types'; import {iterableEquality} from './utils'; // The optional property of matcher context is true if undefined. @@ -357,10 +353,10 @@ const printReceivedResults = ( const createToBeCalledMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, expected: unknown, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = ''; const options: MatcherHintOptions = { isNot: this.isNot, @@ -404,10 +400,10 @@ const createToBeCalledMatcher = (matcherName: string) => const createToReturnMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, expected: unknown, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = ''; const options: MatcherHintOptions = { isNot: this.isNot, @@ -462,10 +458,10 @@ const createToReturnMatcher = (matcherName: string) => const createToBeCalledTimesMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, expected: number, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = 'expected'; const options: MatcherHintOptions = { isNot: this.isNot, @@ -498,10 +494,10 @@ const createToBeCalledTimesMatcher = (matcherName: string) => const createToReturnTimesMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, expected: number, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = 'expected'; const options: MatcherHintOptions = { isNot: this.isNot, @@ -546,10 +542,10 @@ const createToReturnTimesMatcher = (matcherName: string) => const createToBeCalledWithMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, ...expected: Array - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = '...expected'; const options: MatcherHintOptions = { isNot: this.isNot, @@ -619,10 +615,10 @@ const createToBeCalledWithMatcher = (matcherName: string) => const createToReturnWithMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, expected: unknown, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = 'expected'; const options: MatcherHintOptions = { isNot: this.isNot, @@ -692,10 +688,10 @@ const createToReturnWithMatcher = (matcherName: string) => const createLastCalledWithMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, ...expected: Array - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = '...expected'; const options: MatcherHintOptions = { isNot: this.isNot, @@ -775,10 +771,10 @@ const createLastCalledWithMatcher = (matcherName: string) => const createLastReturnedMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, expected: unknown, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = 'expected'; const options: MatcherHintOptions = { isNot: this.isNot, @@ -859,11 +855,11 @@ const createLastReturnedMatcher = (matcherName: string) => const createNthCalledWithMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, nth: number, ...expected: Array - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = 'n'; const options: MatcherHintOptions = { expectedColor: (arg: string) => arg, @@ -989,11 +985,11 @@ const createNthCalledWithMatcher = (matcherName: string) => const createNthReturnedWithMatcher = (matcherName: string) => function ( - this: MatcherState, + this: Expect.MatcherState, received: any, nth: number, expected: unknown, - ): SyncExpectationResult { + ): Expect.SyncExpectationResult { const expectedArgument = 'n'; const options: MatcherHintOptions = { expectedColor: (arg: string) => arg, @@ -1117,7 +1113,7 @@ const createNthReturnedWithMatcher = (matcherName: string) => return {message, pass}; }; -const spyMatchers: MatchersObject = { +const spyMatchers: Expect.MatchersObject = { lastCalledWith: createLastCalledWithMatcher('lastCalledWith'), lastReturnedWith: createLastReturnedMatcher('lastReturnedWith'), nthCalledWith: createNthCalledWithMatcher('nthCalledWith'), diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index 3e5933551a49..5c65e866066a 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -8,6 +8,7 @@ /* eslint-disable local/ban-types-eventually */ +import type {Expect} from '@jest/types'; import { EXPECTED_COLOR, MatcherHintOptions, @@ -28,13 +29,6 @@ import { printReceivedStringContainExpectedResult, printReceivedStringContainExpectedSubstring, } from './print'; -import type { - ExpectationResult, - MatcherState, - MatchersObject, - RawMatcherFn, - SyncExpectationResult, -} from './types'; import {isError} from './utils'; const DID_NOT_THROW = 'Received function did not throw'; @@ -77,12 +71,12 @@ const getThrown = (e: any): Thrown => { export const createMatcher = ( matcherName: string, fromPromise?: boolean, -): RawMatcherFn => +): Expect.RawMatcherFn => function ( - this: MatcherState, + this: Expect.MatcherState, received: Function, expected: any, - ): ExpectationResult { + ): Expect.ExpectationResult { const options = { isNot: this.isNot, promise: this.promise, @@ -141,7 +135,7 @@ export const createMatcher = ( } }; -const matchers: MatchersObject = { +const matchers: Expect.MatchersObject = { toThrow: createMatcher('toThrow'), toThrowError: createMatcher('toThrowError'), }; @@ -151,7 +145,7 @@ const toThrowExpectedRegExp = ( options: MatcherHintOptions, thrown: Thrown | null, expected: RegExp, -): SyncExpectationResult => { +): Expect.SyncExpectationResult => { const pass = thrown !== null && expected.test(thrown.message); const message = pass @@ -190,7 +184,7 @@ const toThrowExpectedAsymmetric = ( options: MatcherHintOptions, thrown: Thrown | null, expected: AsymmetricMatcher, -): SyncExpectationResult => { +): Expect.SyncExpectationResult => { const pass = thrown !== null && expected.asymmetricMatch(thrown.value); const message = pass @@ -225,7 +219,7 @@ const toThrowExpectedObject = ( options: MatcherHintOptions, thrown: Thrown | null, expected: Error, -): SyncExpectationResult => { +): Expect.SyncExpectationResult => { const pass = thrown !== null && thrown.message === expected.message; const message = pass @@ -264,7 +258,7 @@ const toThrowExpectedClass = ( options: MatcherHintOptions, thrown: Thrown | null, expected: Function, -): SyncExpectationResult => { +): Expect.SyncExpectationResult => { const pass = thrown !== null && thrown.value instanceof expected; const message = pass @@ -314,7 +308,7 @@ const toThrowExpectedString = ( options: MatcherHintOptions, thrown: Thrown | null, expected: string, -): SyncExpectationResult => { +): Expect.SyncExpectationResult => { const pass = thrown !== null && thrown.message.includes(expected); const message = pass @@ -348,7 +342,7 @@ const toThrow = ( matcherName: string, options: MatcherHintOptions, thrown: Thrown | null, -): SyncExpectationResult => { +): Expect.SyncExpectationResult => { const pass = thrown !== null; const message = pass diff --git a/packages/expect/src/types-patch.ts b/packages/expect/src/types-patch.ts new file mode 100644 index 000000000000..02e3f901f157 --- /dev/null +++ b/packages/expect/src/types-patch.ts @@ -0,0 +1,13 @@ +import type * as jestMatcherUtils from 'jest-matcher-utils'; + +// TODO remove in Jest 28, the utils must be imported from 'jest-matcher-utils' +declare module '@jest/types' { + namespace Expect { + interface MatcherState { + utils: typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; + }; + } + } +} diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts index d7487477e31d..95c46064b856 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts @@ -34,14 +34,10 @@ import { } from '../state'; import testCaseReportHandler from '../testCaseReportHandler'; import {getTestID} from '../utils'; -import createExpect, {Expect} from './jestExpect'; +import createExpect from './jestExpect'; type Process = NodeJS.Process; -interface JestGlobals extends Global.TestFrameworkGlobals { - expect: Expect; -} - export const initialize = async ({ config, environment, @@ -59,7 +55,7 @@ export const initialize = async ({ testPath: Config.Path; parentProcess: Process; sendMessageToJest?: TestFileEvent; - setGlobalsForRuntime: (globals: JestGlobals) => void; + setGlobalsForRuntime: (globals: Global.TestFrameworkGlobals) => void; }): Promise<{ globals: Global.TestFrameworkGlobals; snapshotState: SnapshotStateType; @@ -122,7 +118,7 @@ export const initialize = async ({ addEventHandler(environment.handleTestEvent.bind(environment)); } - const runtimeGlobals: JestGlobals = { + const runtimeGlobals: Global.TestFrameworkGlobals = { ...globalsObject, expect: createExpect(globalConfig), }; @@ -159,7 +155,7 @@ export const initialize = async ({ snapshotFormat: config.snapshotFormat, updateSnapshot, }); - // @ts-expect-error: snapshotState is a jest extension of `expect` + setState({snapshotState, testPath}); addEventHandler(handleSnapshotStateAfterRetry(snapshotState)); diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts index a13ce01bd3a8..07a9b74aba84 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {Config} from '@jest/types'; +import type {Config, Expect} from '@jest/types'; import expect = require('expect'); import { addSerializer, @@ -15,18 +15,18 @@ import { toThrowErrorMatchingSnapshot, } from 'jest-snapshot'; -export type Expect = typeof expect; +export default (config: Config.GlobalConfig): Expect.JestExpect => { + const jestExpect = expect as Expect.JestExpect; -export default (config: Pick): Expect => { - expect.setState({expand: config.expand}); - expect.extend({ + jestExpect.setState({expand: config.expand}); + jestExpect.extend({ toMatchInlineSnapshot, toMatchSnapshot, toThrowErrorMatchingInlineSnapshot, toThrowErrorMatchingSnapshot, }); - expect.addSnapshotSerializer = addSerializer; + jestExpect.addSnapshotSerializer = addSerializer; - return expect; + return jestExpect; }; diff --git a/packages/jest-circus/src/types.ts b/packages/jest-circus/src/types.ts index 98f36bf05af5..9fded93a2b75 100644 --- a/packages/jest-circus/src/types.ts +++ b/packages/jest-circus/src/types.ts @@ -5,9 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -// Used as type -import type {Circus} from '@jest/types'; -import expect = require('expect'); +import type {Circus, Expect} from '@jest/types'; export const STATE_SYM = Symbol( 'JEST_STATE_SYMBOL', @@ -26,7 +24,7 @@ declare global { STATE_SYM_SYMBOL: Circus.State; RETRY_TIMES_SYMBOL: string; TEST_TIMEOUT_SYMBOL: number; - expect: typeof expect; + expect: Expect.JestExpect; } } } diff --git a/packages/jest-circus/tsconfig.json b/packages/jest-circus/tsconfig.json index 423a421a0cb0..5d31cc358e60 100644 --- a/packages/jest-circus/tsconfig.json +++ b/packages/jest-circus/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../tsconfig", "compilerOptions": { + // we don't want `@types/jest` to be referenced + "types": [], "outDir": "build", "rootDir": "src" }, diff --git a/packages/jest-globals/package.json b/packages/jest-globals/package.json index c74a0e69a14f..8a4b9b5a4183 100644 --- a/packages/jest-globals/package.json +++ b/packages/jest-globals/package.json @@ -19,7 +19,7 @@ "dependencies": { "@jest/environment": "^27.3.1", "@jest/types": "^27.2.5", - "expect": "^27.3.1" + "jest-matcher-utils": "^27.3.1" }, "publishConfig": { "access": "public" diff --git a/packages/jest-globals/src/index.ts b/packages/jest-globals/src/index.ts index 772ee1a8c5ff..15f070e6cc31 100644 --- a/packages/jest-globals/src/index.ts +++ b/packages/jest-globals/src/index.ts @@ -7,25 +7,40 @@ import type {Jest} from '@jest/environment'; import type {Global} from '@jest/types'; -import importedExpect = require('expect'); +import type * as jestMatcherUtils from 'jest-matcher-utils'; export declare const jest: Jest; -export declare const expect: typeof importedExpect; - export declare const it: Global.GlobalAdditions['it']; -export declare const test: Global.GlobalAdditions['test']; export declare const fit: Global.GlobalAdditions['fit']; export declare const xit: Global.GlobalAdditions['xit']; + +export declare const test: Global.GlobalAdditions['test']; export declare const xtest: Global.GlobalAdditions['xtest']; + export declare const describe: Global.GlobalAdditions['describe']; -export declare const xdescribe: Global.GlobalAdditions['xdescribe']; export declare const fdescribe: Global.GlobalAdditions['fdescribe']; +export declare const xdescribe: Global.GlobalAdditions['xdescribe']; + export declare const beforeAll: Global.GlobalAdditions['beforeAll']; export declare const beforeEach: Global.GlobalAdditions['beforeEach']; export declare const afterEach: Global.GlobalAdditions['afterEach']; export declare const afterAll: Global.GlobalAdditions['afterAll']; +export declare const expect: Global.GlobalAdditions['expect']; + throw new Error( 'Do not import `@jest/globals` outside of the Jest test environment', ); + +declare module '@jest/types' { + namespace Expect { + interface MatcherState { + // TODO remove utils in Jest 28, users should import them from 'jest-matcher-utils' + utils: typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; + }; + } + } +} diff --git a/packages/jest-globals/tsconfig.json b/packages/jest-globals/tsconfig.json index f6060ca2e675..beed35ef992a 100644 --- a/packages/jest-globals/tsconfig.json +++ b/packages/jest-globals/tsconfig.json @@ -7,8 +7,8 @@ "outDir": "build" }, "references": [ - {"path": "../expect"}, {"path": "../jest-environment"}, + {"path": "../jest-matcher-utils"}, {"path": "../jest-types"} ] } diff --git a/packages/jest-jasmine2/src/jestExpect.ts b/packages/jest-jasmine2/src/jestExpect.ts index 6b586a2fbeb2..53d4aa048fdf 100644 --- a/packages/jest-jasmine2/src/jestExpect.ts +++ b/packages/jest-jasmine2/src/jestExpect.ts @@ -7,7 +7,7 @@ /* eslint-disable local/prefer-spread-eventually */ -import type {Global} from '@jest/types'; +import type {Expect, Global} from '@jest/types'; import expect = require('expect'); import { addSerializer, @@ -16,35 +16,37 @@ import { toThrowErrorMatchingInlineSnapshot, toThrowErrorMatchingSnapshot, } from 'jest-snapshot'; -import type {Jasmine, JasmineMatchersObject, RawMatcherFn} from './types'; +import type {Jasmine, JasmineMatchersObject} from './types'; declare const global: Global.Global; export default (config: {expand: boolean}): void => { - global.expect = expect; - expect.setState({expand: config.expand}); - expect.extend({ + const jestExpect = expect as Expect.JestExpect; + + global.expect = jestExpect; + jestExpect.setState({expand: config.expand}); + jestExpect.extend({ toMatchInlineSnapshot, toMatchSnapshot, toThrowErrorMatchingInlineSnapshot, toThrowErrorMatchingSnapshot, }); - expect.addSnapshotSerializer = addSerializer; + jestExpect.addSnapshotSerializer = addSerializer; const jasmine = global.jasmine as Jasmine; - jasmine.anything = expect.anything; - jasmine.any = expect.any; - jasmine.objectContaining = expect.objectContaining; - jasmine.arrayContaining = expect.arrayContaining; - jasmine.stringMatching = expect.stringMatching; + jasmine.anything = jestExpect.anything; + jasmine.any = jestExpect.any; + jasmine.objectContaining = jestExpect.objectContaining; + jasmine.arrayContaining = jestExpect.arrayContaining; + jasmine.stringMatching = jestExpect.stringMatching; jasmine.addMatchers = (jasmineMatchersObject: JasmineMatchersObject) => { const jestMatchersObject = Object.create(null); Object.keys(jasmineMatchersObject).forEach(name => { jestMatchersObject[name] = function ( - this: expect.MatcherState, + this: Expect.MatcherState, ...args: Array - ): RawMatcherFn { + ): Expect.RawMatcherFn { // 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` @@ -64,6 +66,6 @@ export default (config: {expand: boolean}): void => { }; }); - expect.extend(jestMatchersObject); + jestExpect.extend(jestMatchersObject); }; }; diff --git a/packages/jest-jasmine2/src/setup_jest_globals.ts b/packages/jest-jasmine2/src/setup_jest_globals.ts index 7fc6329aa317..5a14787e7bef 100644 --- a/packages/jest-jasmine2/src/setup_jest_globals.ts +++ b/packages/jest-jasmine2/src/setup_jest_globals.ts @@ -115,7 +115,7 @@ export default async ({ snapshotFormat, updateSnapshot, }); - // @ts-expect-error: snapshotState is a jest extension of `expect` + setState({snapshotState, testPath}); // Return it back to the outer scope (test runner outside the VM). return snapshotState; diff --git a/packages/jest-jasmine2/src/types.ts b/packages/jest-jasmine2/src/types.ts index 4d0ff244005e..39e9f2726050 100644 --- a/packages/jest-jasmine2/src/types.ts +++ b/packages/jest-jasmine2/src/types.ts @@ -6,8 +6,7 @@ */ import type {AssertionError} from 'assert'; -import type {Config} from '@jest/types'; -import expect = require('expect'); +import type {Config, Expect} from '@jest/types'; import type CallTracker from './jasmine/CallTracker'; import type Env from './jasmine/Env'; import type JsApiReporter from './jasmine/JsApiReporter'; @@ -25,25 +24,6 @@ export interface AssertionErrorWithStack extends AssertionError { stack: string; } -// TODO Add expect types to @jest/types or leave it here -// Borrowed from "expect" -// -------START------- -export type SyncExpectationResult = { - pass: boolean; - message: () => string; -}; - -export type AsyncExpectationResult = Promise; - -export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; - -export type RawMatcherFn = ( - expected: unknown, - actual: unknown, - options?: unknown, -) => ExpectationResult; -// -------END------- - export type RunDetails = { totalSpecsDefined?: number; failedExpectations?: SuiteResult['failedExpectations']; @@ -67,8 +47,8 @@ export interface Spy extends Record { type JasmineMatcher = { (matchersUtil: unknown, context: unknown): JasmineMatcher; - compare: () => RawMatcherFn; - negativeCompare: () => RawMatcherFn; + compare: () => Expect.RawMatcherFn; + negativeCompare: () => Expect.RawMatcherFn; }; export type JasmineMatchersObject = {[id: string]: JasmineMatcher}; @@ -89,13 +69,13 @@ export type Jasmine = { version: string; testPath: Config.Path; addMatchers: (matchers: JasmineMatchersObject) => void; -} & typeof expect & +} & Expect.JestExpect & typeof globalThis; declare global { namespace NodeJS { interface Global { - expect: typeof expect; + expect: Expect.JestExpect; } } } diff --git a/packages/jest-jasmine2/tsconfig.json b/packages/jest-jasmine2/tsconfig.json index a52d122e4792..3aec586aa9a3 100644 --- a/packages/jest-jasmine2/tsconfig.json +++ b/packages/jest-jasmine2/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../tsconfig", "compilerOptions": { + // we don't want `@types/jest` to be referenced + "types": [], "rootDir": "src", "outDir": "build" }, diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index d9c7fed4dc38..a0fd652d951b 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -8,7 +8,7 @@ /* eslint-disable local/ban-types-eventually */ import * as fs from 'graceful-fs'; -import type {Config} from '@jest/types'; +import type {Config, Expect} from '@jest/types'; import type {FS as HasteFS} from 'jest-haste-map'; import { BOLD_WEIGHT, @@ -39,7 +39,7 @@ import { printReceived, printSnapshotAndReceived, } from './printSnapshot'; -import type {Context, ExpectationResult, MatchSnapshotConfig} from './types'; +import type {MatchSnapshotConfig} from './types'; import * as utils from './utils'; const DID_NOT_THROW = 'Received function did not throw'; // same as toThrow @@ -156,11 +156,11 @@ const cleanup = ( }; const toMatchSnapshot = function ( - this: Context, + this: Expect.MatcherState, received: unknown, propertiesOrHint?: object | Config.Path, hint?: Config.Path, -): ExpectationResult { +): Expect.ExpectationResult { const matcherName = 'toMatchSnapshot'; let properties; @@ -214,11 +214,11 @@ const toMatchSnapshot = function ( }; const toMatchInlineSnapshot = function ( - this: Context, + this: Expect.MatcherState, received: unknown, propertiesOrSnapshot?: object | string, inlineSnapshot?: string, -): ExpectationResult { +): Expect.ExpectationResult { const matcherName = 'toMatchInlineSnapshot'; let properties; @@ -408,11 +408,11 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { }; const toThrowErrorMatchingSnapshot = function ( - this: Context, + this: Expect.MatcherState, received: unknown, hint: string | undefined, // because error TS1016 for hint?: string fromPromise: boolean, -): ExpectationResult { +): Expect.ExpectationResult { const matcherName = 'toThrowErrorMatchingSnapshot'; // Future breaking change: Snapshot hint must be a string @@ -431,11 +431,11 @@ const toThrowErrorMatchingSnapshot = function ( }; const toThrowErrorMatchingInlineSnapshot = function ( - this: Context, + this: Expect.MatcherState, received: unknown, inlineSnapshot?: string, fromPromise?: boolean, -): ExpectationResult { +): Expect.ExpectationResult { const matcherName = 'toThrowErrorMatchingInlineSnapshot'; if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') { diff --git a/packages/jest-snapshot/src/types.ts b/packages/jest-snapshot/src/types.ts index 31db0429d212..bf8491cf81ba 100644 --- a/packages/jest-snapshot/src/types.ts +++ b/packages/jest-snapshot/src/types.ts @@ -7,15 +7,12 @@ /* eslint-disable local/ban-types-eventually */ -import type {MatcherState} from 'expect'; -import type SnapshotState from './State'; - -export type Context = MatcherState & { - snapshotState: SnapshotState; -}; +import type {Expect} from '@jest/types'; +import type * as jestMatcherUtils from 'jest-matcher-utils'; +import type SnapshotStateType from './State'; export type MatchSnapshotConfig = { - context: Context; + context: Expect.MatcherState; hint?: string; inlineSnapshot?: string; isInline: boolean; @@ -26,8 +23,16 @@ export type MatchSnapshotConfig = { export type SnapshotData = Record; -// copied from `expect` - should be shared -export type ExpectationResult = { - pass: boolean; - message: () => string; -}; +declare module '@jest/types' { + namespace Expect { + interface MatcherState { + snapshotState: SnapshotStateType; + + // TODO remove utils in Jest 28, they should be imported from 'jest-matcher-utils' + utils: typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; + }; + } + } +} diff --git a/test-types/top-level-config.test.ts b/packages/jest-types/__typechecks__/Config.test.ts similarity index 100% rename from test-types/top-level-config.test.ts rename to packages/jest-types/__typechecks__/Config.test.ts diff --git a/test-types/top-level-expect-namespace.test.ts b/packages/jest-types/__typechecks__/Expect.test.ts similarity index 82% rename from test-types/top-level-expect-namespace.test.ts rename to packages/jest-types/__typechecks__/Expect.test.ts index 80d87e86d186..19fc195dcff8 100644 --- a/test-types/top-level-expect-namespace.test.ts +++ b/packages/jest-types/__typechecks__/Expect.test.ts @@ -7,6 +7,8 @@ import {expectError, expectType} from 'mlh-tsd'; import {expect} from '@jest/globals'; +import type * as jestMatcherUtils from 'jest-matcher-utils'; +import type {SnapshotStateType} from 'jest-snapshot'; // asymmetric matchers @@ -320,27 +322,79 @@ expectError(expect(jest.fn()).toThrowErrorMatchingInlineSnapshot(true)); // extend +type Tester = (a: any, b: any) => boolean | undefined; + +type MatcherUtils = typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; +}; + expectType( expect.extend({ - toBeDivisibleBy(actual: number, expected: number) { + toBeWithinRange(actual: number, floor: number, ceiling: number) { + expectType(this.assertionCalls); + expectType(this.currentTestName); + expectType<(() => void) | undefined>(this.dontThrow); + expectType(this.error); + expectType< + ( + a: unknown, + b: unknown, + customTesters?: Array, + strictCheck?: boolean, + ) => boolean + >(this.equals); + expectType(this.expand); + expectType(this.expectedAssertionsNumber); + expectType(this.expectedAssertionsNumberError); + expectType(this.isExpectingAssertions); + expectType(this.isExpectingAssertionsError); expectType(this.isNot); - - const pass = actual % expected === 0; - const message = pass - ? () => - `expected ${this.utils.printReceived( - actual, - )} not to be divisible by ${expected}` - : () => - `expected ${this.utils.printReceived( - actual, - )} to be divisible by ${expected}`; - - return {message, pass}; + expectType(this.promise); + expectType(this.snapshotState); + expectType>(this.suppressedErrors); + expectType(this.testPath); + expectType(this.utils); + + const pass = actual >= floor && actual <= ceiling; + if (pass) { + return { + message: () => + `expected ${actual} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${actual} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } }, }), ); -// TODO -// expect(4).toBeDivisibleBy(2); -// expect.toBeDivisibleBy(2); +declare module '@jest/types' { + namespace Expect { + interface AsymmetricMatchers { + toBeWithinRange(floor: number, ceiling: number): AsymmetricMatcher; + } + interface Matchers { + toBeWithinRange(floor: number, ceiling: number): R; + } + } +} + +expectType(expect(100).toBeWithinRange(90, 110)); +expectType(expect(101).not.toBeWithinRange(0, 100)); +expectType>(expect(101).resolves.toBeWithinRange(90, 110)); +expectType>(expect(101).resolves.not.toBeWithinRange(0, 100)); +expectType>(expect(101).rejects.toBeWithinRange(90, 110)); +expectType>(expect(101).rejects.not.toBeWithinRange(0, 100)); + +expectType( + expect({apples: 6, bananas: 3}).toEqual({ + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), + }), +); diff --git a/test-types/top-level-globals.test.ts b/packages/jest-types/__typechecks__/Globals.test.ts similarity index 100% rename from test-types/top-level-globals.test.ts rename to packages/jest-types/__typechecks__/Globals.test.ts diff --git a/test-types/top-level-jest-namespace.test.ts b/packages/jest-types/__typechecks__/Jest.test.ts similarity index 100% rename from test-types/top-level-jest-namespace.test.ts rename to packages/jest-types/__typechecks__/Jest.test.ts diff --git a/packages/jest-types/package.json b/packages/jest-types/package.json index 5568a6ff62b7..83160590b303 100644 --- a/packages/jest-types/package.json +++ b/packages/jest-types/package.json @@ -23,6 +23,9 @@ "@types/yargs": "^16.0.0", "chalk": "^4.0.0" }, + "devDependencies": { + "mlh-tsd": "^0.14.1" + }, "publishConfig": { "access": "public" } diff --git a/packages/jest-types/src/Circus.ts b/packages/jest-types/src/Circus.ts index d93e653d7822..1fefbcd52cea 100644 --- a/packages/jest-types/src/Circus.ts +++ b/packages/jest-types/src/Circus.ts @@ -39,11 +39,6 @@ export interface EventHandler { export type Event = SyncEvent | AsyncEvent; -interface JestGlobals extends Global.TestFrameworkGlobals { - // we cannot type `expect` properly as it'd create circular dependencies - expect: unknown; -} - export type SyncEvent = | { asyncError: Error; @@ -83,7 +78,7 @@ export type AsyncEvent = // first action to dispatch. Good time to initialize all settings name: 'setup'; testNamePattern?: string; - runtimeGlobals: JestGlobals; + runtimeGlobals: Global.TestFrameworkGlobals; parentProcess: Process; } | { diff --git a/packages/expect/src/types.ts b/packages/jest-types/src/Expect.ts similarity index 89% rename from packages/expect/src/types.ts rename to packages/jest-types/src/Expect.ts index 53286977a83a..127f4fdb6a99 100644 --- a/packages/expect/src/types.ts +++ b/packages/jest-types/src/Expect.ts @@ -6,9 +6,7 @@ * */ -import type {Config} from '@jest/types'; -import type * as jestMatcherUtils from 'jest-matcher-utils'; -import {INTERNAL_MATCHER_FLAG} from './jestMatchersObject'; +import type {Config, Expect} from '.'; export type SyncExpectationResult = { pass: boolean; @@ -19,17 +17,22 @@ export type AsyncExpectationResult = Promise; export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; -export type RawMatcherFn = { - (this: T, received: any, expected: any, options?: any): ExpectationResult; - [INTERNAL_MATCHER_FLAG]?: boolean; +export type RawMatcherFn = { + ( + this: MatcherState, + received: any, + expected: any, + options?: any, + ): ExpectationResult; }; export type ThrowingMatcherFn = (actual: any) => void; + export type PromiseMatcherFn = (actual: any) => Promise; export type Tester = (a: any, b: any) => boolean | undefined; -export type MatcherState = { +export interface MatcherState { assertionCalls: number; currentTestName?: string; dontThrow?: () => void; @@ -49,28 +52,27 @@ export type MatcherState = { promise: string; suppressedErrors: Array; testPath?: Config.Path; - utils: typeof jestMatcherUtils & { - iterableEquality: Tester; - subsetEquality: Tester; - }; -}; + // not possible to have `utils` here as it creates circular dependencies +} -export interface AsymmetricMatcher { +export type AsymmetricMatcher = { asymmetricMatch(other: unknown): boolean; toString(): string; getExpectedType?(): string; toAsymmetricMatcher?(): string; -} -export type MatchersObject = { - [id: string]: RawMatcherFn; }; + +export type MatchersObject = { + [id: string]: RawMatcherFn; +}; + export type ExpectedAssertionsErrors = Array<{ actual: string | number; error: Error; expected: string; }>; -interface AsymmetricMatchers { +export interface AsymmetricMatchers { any(sample: unknown): AsymmetricMatcher; anything(): AsymmetricMatcher; arrayContaining(sample: Array): AsymmetricMatcher; @@ -79,22 +81,26 @@ interface AsymmetricMatchers { stringMatching(sample: string | RegExp): AsymmetricMatcher; } -export type Expect = { - (actual: T): Matchers; - // TODO: this is added by test runners, not `expect` itself - addSnapshotSerializer(serializer: unknown): void; +type BaseExpect = { assertions(numberOfAssertions: number): void; - // TODO: remove this `T extends` - should get from some interface merging - extend(matchers: MatchersObject): void; + extend(matchers: MatchersObject): void; extractExpectedAssertionsErrors: () => ExpectedAssertionsErrors; - getState(): State; + getState(): MatcherState; hasAssertions(): void; - setState(state: Partial): void; + setState(state: Partial): void; } & AsymmetricMatchers & { not: Omit; }; -// This is a copy from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/de6730f4463cba69904698035fafd906a72b9664/types/jest/index.d.ts#L570-L817 +export type Expect = BaseExpect & { + (actual: T): Matchers; +}; + +export type JestExpect = BaseExpect & { + (actual: T): JestMatchers; + addSnapshotSerializer(serializer: unknown): void; +}; + export interface Matchers { /** * Ensures the last call to a mock function was provided specific args. @@ -319,8 +325,9 @@ export interface Matchers { * If you want to test that a specific error is thrown inside a function. */ toThrowError(expected?: unknown): R; +} - /* TODO: START snapshot matchers are not from `expect`, the types should not be here */ +export interface SnapshotMatchers { /** * This ensures that a value matches the most recent snapshot with property matchers. * Check out [the Snapshot Testing guide](https://jestjs.io/docs/snapshot-testing) for more information. @@ -358,5 +365,6 @@ export interface Matchers { * Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically. */ toThrowErrorMatchingInlineSnapshot(snapshot?: string): R; - /* TODO: END snapshot matchers are not from `expect`, the types should not be here */ } + +type JestMatchers = Matchers & SnapshotMatchers; diff --git a/packages/jest-types/src/Global.ts b/packages/jest-types/src/Global.ts index 010624038707..361b6e0e9fe1 100644 --- a/packages/jest-types/src/Global.ts +++ b/packages/jest-types/src/Global.ts @@ -6,6 +6,7 @@ */ import type {CoverageMapData} from 'istanbul-lib-coverage'; +import type {Expect} from '.'; export type ValidTestReturnValues = void | undefined; type TestReturnValuePromise = Promise; @@ -120,6 +121,7 @@ export interface TestFrameworkGlobals { beforeEach: HookBase; afterEach: HookBase; afterAll: HookBase; + expect: Expect.JestExpect; } export interface GlobalAdditions extends TestFrameworkGlobals { diff --git a/packages/jest-types/src/index.ts b/packages/jest-types/src/index.ts index 8255474b61ba..f1ef6821b645 100644 --- a/packages/jest-types/src/index.ts +++ b/packages/jest-types/src/index.ts @@ -7,8 +7,9 @@ import type * as Circus from './Circus'; import type * as Config from './Config'; +import type * as Expect from './Expect'; import type * as Global from './Global'; import type * as TestResult from './TestResult'; import type * as TransformTypes from './Transform'; -export type {Circus, Config, Global, TestResult, TransformTypes}; +export type {Circus, Config, Expect, Global, TestResult, TransformTypes}; diff --git a/test-types/expect.test.ts b/test-types/expect.test.ts deleted file mode 100644 index 1ff227af29e2..000000000000 --- a/test-types/expect.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type * as expect from 'expect'; - -export type M = expect.Matchers; -export type N = expect.Matchers; -// @ts-expect-error -export type E = expect.Matchers<>; diff --git a/tsconfig.json b/tsconfig.json index d5e967ef95d0..128f0e89ebd4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,7 @@ "exclude": [ ".yarn/releases/*", "**/__tests__/**/*", + "**/__typechecks__/**/*", "**/__mocks__/**/*", "**/build/**/*" ] diff --git a/yarn.lock b/yarn.lock index 39519c873823..99e55073557e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2540,13 +2540,14 @@ __metadata: languageName: unknown linkType: soft -"@jest/globals@^27.3.1, @jest/globals@workspace:*, @jest/globals@workspace:packages/jest-globals": +"@jest/globals@*, @jest/globals@^27.3.1, @jest/globals@workspace:*, @jest/globals@workspace:packages/jest-globals": version: 0.0.0-use.local resolution: "@jest/globals@workspace:packages/jest-globals" dependencies: "@jest/environment": ^27.3.1 "@jest/types": ^27.2.5 - expect: ^27.3.1 + jest-matcher-utils: ^27.3.1 + mlh-tsd: ^0.14.1 languageName: unknown linkType: soft @@ -2774,6 +2775,7 @@ __metadata: "@types/node": "*" "@types/yargs": ^16.0.0 chalk: ^4.0.0 + mlh-tsd: ^0.14.1 languageName: unknown linkType: soft @@ -9573,6 +9575,19 @@ __metadata: languageName: unknown linkType: soft +"example-expect-extend@workspace:examples/expect-extend": + version: 0.0.0-use.local + resolution: "example-expect-extend@workspace:examples/expect-extend" + dependencies: + "@babel/core": "*" + "@babel/preset-env": "*" + "@babel/preset-typescript": "*" + "@jest/globals": "*" + babel-jest: "*" + jest: "*" + languageName: unknown + linkType: soft + "example-getting-started@workspace:examples/getting-started": version: 0.0.0-use.local resolution: "example-getting-started@workspace:examples/getting-started" @@ -9796,6 +9811,7 @@ __metadata: jest-matcher-utils: ^27.3.1 jest-message-util: ^27.3.1 jest-regex-util: ^27.0.6 + mlh-tsd: ^0.14.1 languageName: unknown linkType: soft From 481c084247f529ba5ed69abf6df9e1a0e5601c55 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 10 Nov 2021 19:28:29 +0200 Subject: [PATCH 02/11] fix lock file --- yarn.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 99e55073557e..afba803fdae6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2547,7 +2547,6 @@ __metadata: "@jest/environment": ^27.3.1 "@jest/types": ^27.2.5 jest-matcher-utils: ^27.3.1 - mlh-tsd: ^0.14.1 languageName: unknown linkType: soft @@ -9811,7 +9810,7 @@ __metadata: jest-matcher-utils: ^27.3.1 jest-message-util: ^27.3.1 jest-regex-util: ^27.0.6 - mlh-tsd: ^0.14.1 + mlh-tsd: "*" languageName: unknown linkType: soft @@ -15273,7 +15272,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"mlh-tsd@npm:^0.14.1": +"mlh-tsd@npm:*, mlh-tsd@npm:^0.14.1": version: 0.14.1 resolution: "mlh-tsd@npm:0.14.1" dependencies: From 71998d9e370f275c1c19da48c3b2b44e27da415a Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 10 Nov 2021 19:30:38 +0200 Subject: [PATCH 03/11] fix lock file --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index afba803fdae6..91ecf5ca2090 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9810,7 +9810,7 @@ __metadata: jest-matcher-utils: ^27.3.1 jest-message-util: ^27.3.1 jest-regex-util: ^27.0.6 - mlh-tsd: "*" + mlh-tsd: ^0.14.1 languageName: unknown linkType: soft @@ -15272,7 +15272,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"mlh-tsd@npm:*, mlh-tsd@npm:^0.14.1": +"mlh-tsd@npm:^0.14.1": version: 0.14.1 resolution: "mlh-tsd@npm:0.14.1" dependencies: From 31e8198dd324ff819d75f53adc4fa563085902bc Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Wed, 10 Nov 2021 19:59:41 +0200 Subject: [PATCH 04/11] add copyright header --- packages/expect/src/types-patch.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/expect/src/types-patch.ts b/packages/expect/src/types-patch.ts index 02e3f901f157..358819453ac5 100644 --- a/packages/expect/src/types-patch.ts +++ b/packages/expect/src/types-patch.ts @@ -1,3 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + import type * as jestMatcherUtils from 'jest-matcher-utils'; // TODO remove in Jest 28, the utils must be imported from 'jest-matcher-utils' From 4e05f730a65451bad9629208f0e682d034944d2a Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sun, 14 Nov 2021 08:49:47 +0200 Subject: [PATCH 05/11] cleaner use of `BuildInRawMatcherFn` type --- packages/expect/src/index.ts | 21 ++++++++++----------- packages/expect/src/jestMatchersObject.ts | 23 ++++++++--------------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 1a9c98f4df0f..9545206359e6 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -25,9 +25,8 @@ import { import extractExpectedAssertionsErrors from './extractExpectedAssertionsErrors'; import {equals} from './jasmineUtils'; import { - INTERNAL_MATCHER_FLAG, - JestMatchersObject, - JestRawMatcherFn, + BUILD_IN_MATCHER_FLAG, + BuildInRawMatcherFn, getMatchers, getState, setMatchers, @@ -53,7 +52,7 @@ const isPromise = (obj: any): obj is PromiseLike => typeof obj.then === 'function'; const createToThrowErrorMatchingSnapshotMatcher = function ( - matcher: JestRawMatcherFn, + matcher: Expect.RawMatcherFn, ) { return function ( this: Expect.MatcherState, @@ -64,7 +63,7 @@ const createToThrowErrorMatchingSnapshotMatcher = function ( }; }; -const getPromiseMatcher = (name: string, matcher: any) => { +const getPromiseMatcher = (name: string, matcher: Expect.RawMatcherFn) => { if (name === 'toThrow' || name === 'toThrowError') { return createThrowMatcher(name, true); } else if ( @@ -138,7 +137,7 @@ const getMessage = (message?: () => string) => const makeResolveMatcher = ( matcherName: string, - matcher: JestRawMatcherFn, + matcher: Expect.RawMatcherFn, isNot: boolean, actual: Promise, outerErr: JestAssertionError, @@ -185,7 +184,7 @@ const makeResolveMatcher = const makeRejectMatcher = ( matcherName: string, - matcher: JestRawMatcherFn, + matcher: Expect.RawMatcherFn, isNot: boolean, actual: Promise | (() => Promise), outerErr: JestAssertionError, @@ -235,7 +234,7 @@ const makeRejectMatcher = }; const makeThrowingMatcher = ( - matcher: JestRawMatcherFn, + matcher: BuildInRawMatcherFn, isNot: boolean, promise: string, actual: any, @@ -303,7 +302,7 @@ const makeThrowingMatcher = ( const handleError = (error: Error) => { if ( - matcher[INTERNAL_MATCHER_FLAG] === true && + matcher[BUILD_IN_MATCHER_FLAG] === true && !(error instanceof JestAssertionError) && error.name !== 'PrettyFormatPluginError' && // Guard for some environments (browsers) that do not support this feature. @@ -319,7 +318,7 @@ const makeThrowingMatcher = ( try { potentialResult = - matcher[INTERNAL_MATCHER_FLAG] === true + matcher[BUILD_IN_MATCHER_FLAG] === true ? matcher.call(matcherContext, actual, ...args) : // It's a trap specifically for inline snapshot to capture this name // in the stack trace, so that it can correctly get the custom matcher @@ -348,7 +347,7 @@ const makeThrowingMatcher = ( } }; -expect.extend = (matchers: JestMatchersObject): void => +expect.extend = (matchers: Expect.MatchersObject): void => setMatchers(matchers, false, expect); expect.anything = anything; diff --git a/packages/expect/src/jestMatchersObject.ts b/packages/expect/src/jestMatchersObject.ts index e488db14d6de..8a8be59ac81e 100644 --- a/packages/expect/src/jestMatchersObject.ts +++ b/packages/expect/src/jestMatchersObject.ts @@ -13,16 +13,11 @@ import {AsymmetricMatcher} from './asymmetricMatchers'; // the state, that can hold matcher specific values that change over time. const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object'); -// Notes a built-in/internal Jest matcher. -// Jest may override the stack trace of Errors thrown by internal matchers. -export const INTERNAL_MATCHER_FLAG = Symbol.for('$$jest-internal-matcher'); +// Expect may override the stack trace of Errors thrown by built-in matchers. +export const BUILD_IN_MATCHER_FLAG = Symbol.for('$$build-in-matcher'); -export type JestRawMatcherFn = Expect.RawMatcherFn & { - [INTERNAL_MATCHER_FLAG]?: boolean; -}; - -export type JestMatchersObject = { - [matcherName: string]: JestRawMatcherFn; +export type BuildInRawMatcherFn = Expect.RawMatcherFn & { + [BUILD_IN_MATCHER_FLAG]?: boolean; }; if (!global.hasOwnProperty(JEST_MATCHERS_OBJECT)) { @@ -52,16 +47,14 @@ export const getMatchers = (): Expect.MatchersObject => export const setMatchers = ( matchers: Expect.MatchersObject, - isInternal: boolean, + isBuildIn: boolean, expect: Expect.Expect, ): void => { Object.keys(matchers).forEach(key => { - const matcher = matchers[key]; - Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, { - value: isInternal, - }); + const matcher = matchers[key] as BuildInRawMatcherFn; + matcher[BUILD_IN_MATCHER_FLAG] = isBuildIn; - if (!isInternal) { + if (!isBuildIn) { // expect is defined class CustomMatcher extends AsymmetricMatcher< From 12287829a64288a06ffc0f8f9b14e4e0a420d573 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sun, 14 Nov 2021 10:48:56 +0200 Subject: [PATCH 06/11] cleaner `RawMatcherFn` type --- packages/jest-types/src/Expect.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/jest-types/src/Expect.ts b/packages/jest-types/src/Expect.ts index 127f4fdb6a99..91456da96a71 100644 --- a/packages/jest-types/src/Expect.ts +++ b/packages/jest-types/src/Expect.ts @@ -17,14 +17,12 @@ export type AsyncExpectationResult = Promise; export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; -export type RawMatcherFn = { - ( - this: MatcherState, - received: any, - expected: any, - options?: any, - ): ExpectationResult; -}; +export type RawMatcherFn = ( + this: MatcherState, + received: any, + expected: any, + options?: any, +) => ExpectationResult; export type ThrowingMatcherFn = (actual: any) => void; From f284f5a5f6bc4f7c87166435f1e7740306e73af5 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Tue, 16 Nov 2021 08:01:53 +0200 Subject: [PATCH 07/11] fix RuntimeGlobals --- .../src/legacy-code-todo-rewrite/jestAdapterInit.ts | 4 ++-- packages/jest-types/src/Circus.ts | 2 +- packages/jest-types/src/Global.ts | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts index 95c46064b856..b4cf953bd76a 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts @@ -55,7 +55,7 @@ export const initialize = async ({ testPath: Config.Path; parentProcess: Process; sendMessageToJest?: TestFileEvent; - setGlobalsForRuntime: (globals: Global.TestFrameworkGlobals) => void; + setGlobalsForRuntime: (runtimeGlobals: Global.RuntimeGlobals) => void; }): Promise<{ globals: Global.TestFrameworkGlobals; snapshotState: SnapshotStateType; @@ -118,7 +118,7 @@ export const initialize = async ({ addEventHandler(environment.handleTestEvent.bind(environment)); } - const runtimeGlobals: Global.TestFrameworkGlobals = { + const runtimeGlobals: Global.RuntimeGlobals = { ...globalsObject, expect: createExpect(globalConfig), }; diff --git a/packages/jest-types/src/Circus.ts b/packages/jest-types/src/Circus.ts index 1fefbcd52cea..a8aecf64522b 100644 --- a/packages/jest-types/src/Circus.ts +++ b/packages/jest-types/src/Circus.ts @@ -78,7 +78,7 @@ export type AsyncEvent = // first action to dispatch. Good time to initialize all settings name: 'setup'; testNamePattern?: string; - runtimeGlobals: Global.TestFrameworkGlobals; + runtimeGlobals: Global.RuntimeGlobals; parentProcess: Process; } | { diff --git a/packages/jest-types/src/Global.ts b/packages/jest-types/src/Global.ts index 361b6e0e9fe1..91c9257ee2b7 100644 --- a/packages/jest-types/src/Global.ts +++ b/packages/jest-types/src/Global.ts @@ -121,10 +121,13 @@ export interface TestFrameworkGlobals { beforeEach: HookBase; afterEach: HookBase; afterAll: HookBase; +} + +export interface RuntimeGlobals extends TestFrameworkGlobals { expect: Expect.JestExpect; } -export interface GlobalAdditions extends TestFrameworkGlobals { +export interface GlobalAdditions extends RuntimeGlobals { __coverage__: CoverageMapData; jasmine: Jasmine; fail: () => void; From 98e050f27192c4a39f19745f256403dcec852f64 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Tue, 23 Nov 2021 08:53:21 +0200 Subject: [PATCH 08/11] clean up --- packages/jest-types/src/Expect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-types/src/Expect.ts b/packages/jest-types/src/Expect.ts index 91456da96a71..a9cf2b2d7ec2 100644 --- a/packages/jest-types/src/Expect.ts +++ b/packages/jest-types/src/Expect.ts @@ -6,7 +6,7 @@ * */ -import type {Config, Expect} from '.'; +import type {Config} from '.'; export type SyncExpectationResult = { pass: boolean; @@ -19,7 +19,7 @@ export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; export type RawMatcherFn = ( this: MatcherState, - received: any, + actual: any, expected: any, options?: any, ) => ExpectationResult; From 9307761a087b573e9787012670c4d76cc541839f Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sun, 5 Dec 2021 14:13:35 +0200 Subject: [PATCH 09/11] fix imports --- packages/jest-types/src/Expect.ts | 2 +- packages/jest-types/src/Global.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-types/src/Expect.ts b/packages/jest-types/src/Expect.ts index a9cf2b2d7ec2..4676b43655e6 100644 --- a/packages/jest-types/src/Expect.ts +++ b/packages/jest-types/src/Expect.ts @@ -6,7 +6,7 @@ * */ -import type {Config} from '.'; +import type * as Config from './Config'; export type SyncExpectationResult = { pass: boolean; diff --git a/packages/jest-types/src/Global.ts b/packages/jest-types/src/Global.ts index 91c9257ee2b7..e172e78d70ca 100644 --- a/packages/jest-types/src/Global.ts +++ b/packages/jest-types/src/Global.ts @@ -6,7 +6,7 @@ */ import type {CoverageMapData} from 'istanbul-lib-coverage'; -import type {Expect} from '.'; +import type * as Expect from './Expect'; export type ValidTestReturnValues = void | undefined; type TestReturnValuePromise = Promise; From a391c8fb92322b7b527956a31fc5132d46cc00ad Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Sun, 5 Dec 2021 14:17:54 +0200 Subject: [PATCH 10/11] fix file names --- .../jest-types/__typechecks__/{Config.test.ts => config.test.ts} | 0 .../jest-types/__typechecks__/{Expect.test.ts => expect.test.ts} | 0 .../__typechecks__/{Globals.test.ts => globals.test.ts} | 0 packages/jest-types/__typechecks__/{Jest.test.ts => jest.test.ts} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename packages/jest-types/__typechecks__/{Config.test.ts => config.test.ts} (100%) rename packages/jest-types/__typechecks__/{Expect.test.ts => expect.test.ts} (100%) rename packages/jest-types/__typechecks__/{Globals.test.ts => globals.test.ts} (100%) rename packages/jest-types/__typechecks__/{Jest.test.ts => jest.test.ts} (100%) diff --git a/packages/jest-types/__typechecks__/Config.test.ts b/packages/jest-types/__typechecks__/config.test.ts similarity index 100% rename from packages/jest-types/__typechecks__/Config.test.ts rename to packages/jest-types/__typechecks__/config.test.ts diff --git a/packages/jest-types/__typechecks__/Expect.test.ts b/packages/jest-types/__typechecks__/expect.test.ts similarity index 100% rename from packages/jest-types/__typechecks__/Expect.test.ts rename to packages/jest-types/__typechecks__/expect.test.ts diff --git a/packages/jest-types/__typechecks__/Globals.test.ts b/packages/jest-types/__typechecks__/globals.test.ts similarity index 100% rename from packages/jest-types/__typechecks__/Globals.test.ts rename to packages/jest-types/__typechecks__/globals.test.ts diff --git a/packages/jest-types/__typechecks__/Jest.test.ts b/packages/jest-types/__typechecks__/jest.test.ts similarity index 100% rename from packages/jest-types/__typechecks__/Jest.test.ts rename to packages/jest-types/__typechecks__/jest.test.ts From 8000f7f00aeeeba9c4b391808629be63ec0be7dd Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Thu, 10 Feb 2022 12:15:04 +0200 Subject: [PATCH 11/11] some fix --- .../src/legacy-code-todo-rewrite/jestAdapterInit.ts | 8 ++++++++ packages/jest-jasmine2/src/setup_jest_globals.ts | 8 ++++++++ packages/jest-snapshot/src/index.ts | 10 +++++----- packages/jest-types/__typetests__/expect.test.ts | 4 ++-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts index 8c12b3344aa8..f54bba638c00 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts @@ -37,6 +37,14 @@ import createExpect from './jestExpect'; type Process = NodeJS.Process; +declare module '@jest/types' { + namespace Expect { + interface MatcherState { + snapshotState: SnapshotState; + } + } +} + export const initialize = async ({ config, environment, diff --git a/packages/jest-jasmine2/src/setup_jest_globals.ts b/packages/jest-jasmine2/src/setup_jest_globals.ts index de8a7c98075b..53e70c92be73 100644 --- a/packages/jest-jasmine2/src/setup_jest_globals.ts +++ b/packages/jest-jasmine2/src/setup_jest_globals.ts @@ -22,6 +22,14 @@ import type {Jasmine} from './types'; declare const global: Global.Global; +declare module '@jest/types' { + namespace Expect { + interface MatcherState { + snapshotState: SnapshotState; + } + } +} + export type SetupOptions = { config: Config.ProjectConfig; globalConfig: Config.GlobalConfig; diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 37c38d1c3b08..574cf8a3e6ca 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -33,7 +33,7 @@ import { printReceived, printSnapshotAndReceived, } from './printSnapshot'; -import type {MatchSnapshotConfig} from './types'; +import type {Context, MatchSnapshotConfig} from './types'; import {deepMerge, escapeBacktickString, serialize} from './utils'; export {addSerializer, getSerializers} from './plugins'; @@ -159,7 +159,7 @@ export const cleanup = ( }; export const toMatchSnapshot = function ( - this: Expect.MatcherState, + this: Context, received: unknown, propertiesOrHint?: object | Config.Path, hint?: Config.Path, @@ -218,7 +218,7 @@ export const toMatchSnapshot = function ( }; export const toMatchInlineSnapshot = function ( - this: Expect.MatcherState, + this: Context, received: unknown, propertiesOrSnapshot?: object | string, inlineSnapshot?: string, @@ -412,7 +412,7 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { }; export const toThrowErrorMatchingSnapshot = function ( - this: Expect.MatcherState, + this: Context, received: unknown, hint?: string, fromPromise?: boolean, @@ -435,7 +435,7 @@ export const toThrowErrorMatchingSnapshot = function ( }; export const toThrowErrorMatchingInlineSnapshot = function ( - this: Expect.MatcherState, + this: Context, received: unknown, inlineSnapshot?: string, fromPromise?: boolean, diff --git a/packages/jest-types/__typetests__/expect.test.ts b/packages/jest-types/__typetests__/expect.test.ts index 8a5e54ef84e0..c639490b162b 100644 --- a/packages/jest-types/__typetests__/expect.test.ts +++ b/packages/jest-types/__typetests__/expect.test.ts @@ -8,7 +8,7 @@ import {expectError, expectType} from 'tsd-lite'; import {expect} from '@jest/globals'; import type * as jestMatcherUtils from 'jest-matcher-utils'; -import type {SnapshotStateType} from 'jest-snapshot'; +import type {SnapshotState} from 'jest-snapshot'; // asymmetric matchers @@ -380,7 +380,7 @@ expectType( expectType(this.isExpectingAssertionsError); expectType(this.isNot); expectType(this.promise); - expectType(this.snapshotState); + expectType(this.snapshotState); expectType>(this.suppressedErrors); expectType(this.testPath); expectType(this.utils);