Last active
September 3, 2022 09:43
-
-
Save hasparus/4ebaa17ec5d3d44607f522bcb1cda9fb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TypeScript Playground: https://tsplay.dev/mq8eYN | |
/// <reference types="@types/jest" /> | |
import type { MatcherState } from 'expect'; | |
const matchers = { | |
toHaveWordsCount(this: MatcherState, sentence: string, wordsCount: number) { | |
// implementation redacted | |
}, | |
}; | |
type Tail<T extends unknown[]> = T extends [infer _Head, ...infer Tail] | |
? Tail | |
: never; | |
type AnyFunction = (...args: never[]) => unknown; | |
type GetMatchersType< | |
TMatchers, | |
TResult, | |
> = { | |
[P in keyof TMatchers]: TMatchers[P] extends AnyFunction | |
? AnyFunction extends TMatchers[P] | |
? (...args: Tail<Parameters<TMatchers[P]>>) => TResult | |
: TMatchers[P] | |
: TMatchers[P] | |
}; | |
type FirstParam<T extends AnyFunction> = Parameters<T>[0] | |
type OnlyMethodsWhereFirstArgIsOfType< | |
TObject, | |
TWantedFirstArg | |
> = { | |
[P in keyof TObject]: TObject[P] extends AnyFunction | |
? TWantedFirstArg extends FirstParam<TObject[P]> | |
? TObject[P] | |
: [`Error: this function is present only when received is:`, FirstParam<TObject[P]>] | |
: TObject[P] | |
} | |
declare global { | |
namespace jest { | |
interface Matchers<R, T = {}> extends GetMatchersType<OnlyMethodsWhereFirstArgIsOfType<typeof matchers, T>, R> { | |
} | |
} | |
} | |
// ✅ | |
expect('foo bar').toHaveWordsCount(2); | |
// 🔥 error as expected | |
expect(20).toHaveWordsCount(2); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
EXPECTED_COLOR, | |
RECEIVED_COLOR, | |
MatcherHintOptions, | |
getLabelPrinter, | |
matcherErrorMessage, | |
matcherHint, | |
printExpected, | |
printReceived, | |
printWithType, | |
} from "jest-matcher-utils"; | |
const stripWhitespace = (s: string) => s.replace(/\s/g, ""); | |
const matchers = { | |
toContainWithoutWhitespace( | |
this: jest.MatcherContext, | |
received: string, | |
expected: string | |
) { | |
const matcherName = "toContainWithoutWhitespace"; | |
const isNot = this.isNot; | |
const matcherHintOptions: MatcherHintOptions = { | |
isNot, | |
promise: this.promise, | |
}; | |
if (typeof received !== "string") { | |
throw new Error( | |
matcherErrorMessage( | |
matcherHint( | |
matcherName, | |
String(received), | |
String(expected), | |
matcherHintOptions | |
), | |
`${RECEIVED_COLOR("received")} value must be a string`, | |
printWithType("Expected", expected, printExpected) + | |
"\n" + | |
printWithType("Received", received, printReceived) | |
) | |
); | |
} | |
if (typeof expected !== "string") { | |
throw new Error( | |
matcherErrorMessage( | |
matcherHint( | |
matcherName, | |
received, | |
String(expected), | |
matcherHintOptions | |
), | |
`${EXPECTED_COLOR("expected")} value must be a string`, | |
printWithType("Expected", expected, printExpected) + | |
"\n" + | |
printWithType("Received", received, printReceived) | |
) | |
); | |
} | |
const pass = stripWhitespace(received).includes(stripWhitespace(expected)); | |
const message = () => { | |
const labelExpected = "Expected substring"; | |
const labelReceived = "Received string"; | |
const printLabel = getLabelPrinter(labelExpected, labelReceived); | |
const hint = | |
matcherHint(matcherName, "text", "substring", matcherHintOptions) + | |
"\n\n"; | |
return ( | |
hint + | |
printLabel(labelExpected) + | |
(isNot ? "not " : " ") + | |
printExpected(expected) + | |
"\n" + | |
printLabel(labelReceived) + | |
(isNot ? " " : " ") + | |
printReceived(received) | |
); | |
}; | |
return { message, pass }; | |
}, | |
}; | |
declare global { | |
namespace jest { | |
interface Matchers<R, T = {}> { | |
toContainWithoutWhitespace( | |
substring: T extends string | |
? string | |
: "Type-level Error: Received value must be a string", | |
): R; | |
} | |
} | |
} | |
const jestExpect = (global as any).expect; | |
if (jestExpect !== undefined) { | |
jestExpect.extend(matchers); | |
} else { | |
console.error("Couldn't find Jest's global expect."); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment