Skip to content

Commit

Permalink
jest updated - corrected Matchers, added ExtendedExpect (#39243)
Browse files Browse the repository at this point in the history
* corrected Matchers, added ExtendedExpect

* Matchers<R,T>

* updating dependent packages

* refactor and additional getState/setState/matcher context

* MatcherContext

* Matchers<R, T> for jest-axe
  • Loading branch information
tonyhallett authored and weswigham committed Oct 25, 2019
1 parent b458bcb commit 9e6921f
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 59 deletions.
2 changes: 1 addition & 1 deletion types/expect-puppeteer/index.d.ts
Expand Up @@ -51,7 +51,7 @@ interface ExpectPuppeteer {
declare global {
namespace jest {
// tslint:disable-next-line no-empty-interface
interface Matchers<R> {
interface Matchers<R, T> {
// These must all match the ExpectPuppeteer interface above.
// We can't extend from it directly because some method names conflict in type-incompatible ways.
toClick(selector: string, options?: ExpectToClickOptions): Promise<void>;
Expand Down
2 changes: 1 addition & 1 deletion types/jest-axe/index.d.ts
Expand Up @@ -79,7 +79,7 @@ export const toHaveNoViolations: {

declare global {
namespace jest {
interface Matchers<R> {
interface Matchers<R, T> {
toHaveNoViolations(): R;
}
}
Expand Down
2 changes: 1 addition & 1 deletion types/jest-expect-message/index.d.ts
Expand Up @@ -8,6 +8,6 @@

declare namespace jest {
interface Expect {
<T = any>(actual: T, message: string): Matchers<T>;
<T = any>(actual: T, message: string): JestMatchers<T>;
}
}
2 changes: 1 addition & 1 deletion types/jest-image-snapshot/index.d.ts
Expand Up @@ -80,7 +80,7 @@ export function configureToMatchImageSnapshot(options: MatchImageSnapshotOptions

declare global {
namespace jest {
interface Matchers<R> {
interface Matchers<R, T> {
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): R;
}
}
Expand Down
2 changes: 1 addition & 1 deletion types/jest-json-schema/index.d.ts
Expand Up @@ -10,7 +10,7 @@ import * as ajv from 'ajv';

declare global {
namespace jest {
interface Matchers<R> {
interface Matchers<R, T> {
toBeValidSchema(): R;
toMatchSchema(schema: object): R;
}
Expand Down
2 changes: 1 addition & 1 deletion types/jest-matcher-one-of/index.d.ts
Expand Up @@ -6,7 +6,7 @@

/// <reference types="jest" />
declare namespace jest {
interface Matchers<R> {
interface Matchers<R, T> {
toBeOneOf(expected: any[]): R;
}
}
2 changes: 1 addition & 1 deletion types/jest-specific-snapshot/index.d.ts
Expand Up @@ -8,7 +8,7 @@

declare global {
namespace jest {
interface Matchers<R> {
interface Matchers<R, T> {
toMatchSpecificSnapshot(snapshotFilename: string): R;
}
}
Expand Down
104 changes: 82 additions & 22 deletions types/jest/index.d.ts
Expand Up @@ -23,6 +23,7 @@
// ExE Boss <https://github.com/ExE-Boss>
// Alex Bolenok <https://github.com/quassnoi>
// Mario Beltrán Alarcón <https://github.com/Belco90>
// Tony Hallett <https://github.com/tonyhallett>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.0

Expand Down Expand Up @@ -396,8 +397,9 @@ declare namespace jest {
}

interface MatcherUtils {
readonly expand: boolean;
readonly isNot: boolean;
readonly dontThrow: () => void;
readonly promise: string;
utils: {
readonly EXPECTED_COLOR: (text: string) => string;
readonly RECEIVED_COLOR: (text: string) => string;
Expand Down Expand Up @@ -426,21 +428,23 @@ declare namespace jest {
* This is a deep-equality function that will return true if two objects have the same values (recursively).
*/
equals(a: any, b: any): boolean;
[other: string]: any;
}

interface ExpectExtendMap {
[key: string]: CustomMatcher;
}

type MatcherContext = MatcherUtils & Readonly<MatcherState>;
type CustomMatcher = (
this: MatcherUtils,
this: MatcherContext,
received: any,
...actual: any[]
) => CustomMatcherResult | Promise<CustomMatcherResult>;

interface CustomMatcherResult {
pass: boolean;
message: string | (() => string);
message: () => string;
}

interface SnapshotSerializerOptions {
Expand Down Expand Up @@ -516,7 +520,15 @@ declare namespace jest {
*/
stringContaining(str: string): any;
}

interface MatcherState {
assertionCalls: number;
currentTestName: string;
expand: boolean;
expectedAssertionsNumber: number;
isExpectingAssertions?: boolean;
suppressedErrors: Error[];
testPath: string;
}
/**
* The `expect` function is used every time you want to test a value.
* You will rarely call `expect` by itself.
Expand All @@ -528,7 +540,7 @@ declare namespace jest {
*
* @param actual The value to apply matchers against.
*/
<T = any>(actual: T): Matchers<T>;
<T = any>(actual: T): JestMatchers<T>;
/**
* Matches anything but null or undefined. You can use it inside `toEqual` or `toBeCalledWith` instead
* of a literal value. For example, if you want to check that a mock function is called with a
Expand Down Expand Up @@ -601,9 +613,30 @@ declare namespace jest {
stringContaining(str: string): any;

not: InverseAsymmetricMatchers;

setState(state: object): void;
getState(): MatcherState & Record<string, any>;
}

interface Matchers<R> {
type JestMatchers<T> = JestMatchersShape<Matchers<void, T>, Matchers<Promise<void>, T>>;

type JestMatchersShape<TNonPromise extends {} = {}, TPromise extends {} = {}> = {
/**
* Use resolves to unwrap the value of a fulfilled promise so any other
* matcher can be chained. If the promise is rejected the assertion fails.
*/
resolves: AndNot<TPromise>,
/**
* Unwraps the reason of a rejected promise so any other matcher can be chained.
* If the promise is fulfilled the assertion fails.
*/
rejects: AndNot<TPromise>
} & AndNot<TNonPromise>;
type AndNot<T> = T & {
not: T
};
// should be R extends void|Promise<void> but getting dtslint error
interface Matchers<R, T> {
/**
* Ensures the last call to a mock function was provided specific args.
*/
Expand All @@ -612,10 +645,6 @@ declare namespace jest {
* Ensure that the last call to a mock function has returned a specified value.
*/
lastReturnedWith(value: any): R;
/**
* If you know how to test something, `.not` lets you test its opposite.
*/
not: Matchers<R>;
/**
* Ensure that a mock function is called with specific arguments on an Nth call.
*/
Expand All @@ -624,16 +653,6 @@ declare namespace jest {
* Ensure that the nth call to a mock function has returned a specified value.
*/
nthReturnedWith(n: number, value: any): R;
/**
* Use resolves to unwrap the value of a fulfilled promise so any other
* matcher can be chained. If the promise is rejected the assertion fails.
*/
resolves: Matchers<Promise<R>>;
/**
* Unwraps the reason of a rejected promise so any other matcher can be chained.
* If the promise is fulfilled the assertion fails.
*/
rejects: Matchers<Promise<R>>;
/**
* Checks that a value is what you expect. It uses `===` to check strict equality.
* Don't use `toBe` with floating-point numbers.
Expand Down Expand Up @@ -816,7 +835,7 @@ declare namespace jest {
* This ensures that a value matches the most recent snapshot with property matchers.
* Check out [the Snapshot Testing guide](http://facebook.github.io/jest/docs/snapshot-testing.html) for more information.
*/
toMatchSnapshot<T extends { [P in keyof R]: any }>(propertyMatchers: Partial<T>, snapshotName?: string): R;
toMatchSnapshot<U extends { [P in keyof T]: any }>(propertyMatchers: Partial<U>, snapshotName?: string): R;
/**
* This ensures that a value matches the most recent snapshot.
* Check out [the Snapshot Testing guide](http://facebook.github.io/jest/docs/snapshot-testing.html) for more information.
Expand All @@ -827,7 +846,7 @@ declare namespace jest {
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
* Check out [the Snapshot Testing guide](http://facebook.github.io/jest/docs/snapshot-testing.html) for more information.
*/
toMatchInlineSnapshot<T extends { [P in keyof R]: any }>(propertyMatchers: Partial<T>, snapshot?: string): R;
toMatchInlineSnapshot<U extends { [P in keyof T]: any }>(propertyMatchers: Partial<U>, snapshot?: string): R;
/**
* This ensures that a value matches the most recent snapshot with property matchers.
* Instead of writing the snapshot value to a .snap file, it will be written into the source code automatically.
Expand Down Expand Up @@ -869,6 +888,47 @@ declare namespace jest {
toThrowErrorMatchingInlineSnapshot(snapshot?: string): R;
}

type RemoveFirstFromTuple<T extends any[]> =
T['length'] extends 0 ? [] :
(((...b: T) => void) extends (a: any, ...b: infer I) => void ? I : []);

type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;

interface AsymmetricMatcher {
asymmetricMatch(other: unknown): boolean;
}
type NonAsyncMatchers<TMatchers extends ExpectExtendMap> = {
[K in keyof TMatchers]: ReturnType<TMatchers[K]> extends Promise<CustomMatcherResult>? never: K
}[keyof TMatchers];
type CustomAsyncMatchers<TMatchers extends ExpectExtendMap> = {[K in NonAsyncMatchers<TMatchers>]: CustomAsymmetricMatcher<TMatchers[K]>};
type CustomAsymmetricMatcher<TMatcher extends (...args: any[]) => any> = (...args: RemoveFirstFromTuple<Parameters<TMatcher>>) => AsymmetricMatcher;

// should be TMatcherReturn extends void|Promise<void> but getting dtslint error
type CustomJestMatcher<TMatcher extends (...args: any[]) => any, TMatcherReturn> = (...args: RemoveFirstFromTuple<Parameters<TMatcher>>) => TMatcherReturn;

type ExpectProperties= {
[K in keyof Expect]: Expect[K]
};
// should be TMatcherReturn extends void|Promise<void> but getting dtslint error
// Use the `void` type for return types only. Otherwise, use `undefined`. See: https://github.com/Microsoft/dtslint/blob/master/docs/void-return.md
// have added issue https://github.com/microsoft/dtslint/issues/256 - Cannot have type union containing void ( to be used as return type only
type ExtendedMatchers<TMatchers extends ExpectExtendMap, TMatcherReturn, TActual> = Matchers<TMatcherReturn, TActual> & {[K in keyof TMatchers]: CustomJestMatcher<TMatchers[K], TMatcherReturn>};
type JestExtendedMatchers<TMatchers extends ExpectExtendMap, TActual> = JestMatchersShape<ExtendedMatchers<TMatchers, void, TActual>, ExtendedMatchers<TMatchers, Promise<void>, TActual>>;

// when have called expect.extend
type ExtendedExpectFunction<TMatchers extends ExpectExtendMap> = <TActual>(actual: TActual) => JestExtendedMatchers<TMatchers, TActual>;

type ExtendedExpect<TMatchers extends ExpectExtendMap>=
ExpectProperties &
AndNot<CustomAsyncMatchers<TMatchers>> &
ExtendedExpectFunction<TMatchers>;
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type NonPromiseMatchers<T extends JestMatchersShape> = Omit<T, 'resolves' | 'rejects' | 'not'>;
type PromiseMatchers<T extends JestMatchersShape> = Omit<T['resolves'], 'not'>;

interface Constructable {
new (...args: any[]): any;
}
Expand Down

0 comments on commit 9e6921f

Please sign in to comment.