Skip to content

Instantly share code, notes, and snippets.

@hasparus
Last active September 3, 2022 09:43
Show Gist options
  • Save hasparus/4ebaa17ec5d3d44607f522bcb1cda9fb to your computer and use it in GitHub Desktop.
Save hasparus/4ebaa17ec5d3d44607f522bcb1cda9fb to your computer and use it in GitHub Desktop.
// 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);
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