121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
|
import * as builtInMatchers from "./matchers.ts";
|
||
|
import type { Matcher, Matchers } from "./matchers.ts";
|
||
|
|
||
|
import { AssertionError } from "https://deno.land/std@0.106.0/testing/asserts.ts";
|
||
|
|
||
|
interface Expected {
|
||
|
toBe(candidate: any): void;
|
||
|
toEqual(candidate: any): void;
|
||
|
toBeTruthy(): void;
|
||
|
toBeFalsy(): void;
|
||
|
toBeDefined(): void;
|
||
|
toBeInstanceOf(clazz: any): void;
|
||
|
toBeUndefined(): void;
|
||
|
toBeNull(): void;
|
||
|
toBeNaN(): void;
|
||
|
toMatch(pattern: RegExp | string): void;
|
||
|
toHaveProperty(propName: string): void;
|
||
|
toHaveLength(length: number): void;
|
||
|
toContain(item: any): void;
|
||
|
toThrow(error?: RegExp | string): void;
|
||
|
toBeGreaterThan(number: number): void;
|
||
|
toBeGreaterThanOrEqual(number: number): void;
|
||
|
toBeLessThan(number: number): void;
|
||
|
toBeLessThanOrEqual(number: number): void;
|
||
|
toHaveBeenCalled(): void;
|
||
|
toHaveBeenCalledTimes(number: number): void;
|
||
|
toHaveBeenCalledWith(...args: any[]): void;
|
||
|
toHaveBeenLastCalledWith(...args: any[]): void;
|
||
|
toHaveBeenNthCalledWith(nthCall: number, ...args: any[]): void;
|
||
|
toHaveReturned(): void;
|
||
|
toHaveReturnedTimes(number: number): void;
|
||
|
toHaveReturnedWith(value: any): void;
|
||
|
toHaveLastReturnedWith(value: any): void;
|
||
|
toHaveNthReturnedWith(nthCall: number, value: any): void;
|
||
|
|
||
|
not: Expected;
|
||
|
resolves: Expected;
|
||
|
rejects: Expected;
|
||
|
}
|
||
|
|
||
|
const matchers: Record<any, Matcher> = {
|
||
|
...builtInMatchers,
|
||
|
};
|
||
|
|
||
|
export function expect(value: any): Expected {
|
||
|
let isNot = false;
|
||
|
let isPromised = false;
|
||
|
const self: any = new Proxy(
|
||
|
{},
|
||
|
{
|
||
|
get(_, name) {
|
||
|
if (name === "not") {
|
||
|
isNot = !isNot;
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
if (name === "resolves") {
|
||
|
if (!(value instanceof Promise)) {
|
||
|
throw new AssertionError("expected value must be a Promise");
|
||
|
}
|
||
|
|
||
|
isPromised = true;
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
if (name === "rejects") {
|
||
|
if (!(value instanceof Promise)) {
|
||
|
throw new AssertionError("expected value must be a Promise");
|
||
|
}
|
||
|
|
||
|
value = value.then(
|
||
|
(value) => {
|
||
|
throw new AssertionError(
|
||
|
`Promise did not reject. resolved to ${value}`,
|
||
|
);
|
||
|
},
|
||
|
(err) => err,
|
||
|
);
|
||
|
isPromised = true;
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
const matcher: Matcher = matchers[name as any];
|
||
|
if (!matcher) {
|
||
|
throw new TypeError(
|
||
|
typeof name === "string"
|
||
|
? `matcher not found: ${name}`
|
||
|
: "matcher not found",
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return (...args: any[]) => {
|
||
|
function applyMatcher(value: any, args: any[]) {
|
||
|
if (isNot) {
|
||
|
let result = matcher(value, ...args);
|
||
|
if (result.pass) {
|
||
|
throw new AssertionError("should not " + result.message);
|
||
|
}
|
||
|
} else {
|
||
|
let result = matcher(value, ...args);
|
||
|
if (!result.pass) {
|
||
|
throw new AssertionError(result.message || "Unknown error");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return isPromised
|
||
|
? value.then((value: any) => applyMatcher(value, args))
|
||
|
: applyMatcher(value, args);
|
||
|
};
|
||
|
},
|
||
|
},
|
||
|
);
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
export function addMatchers(newMatchers: Matchers): void {
|
||
|
Object.assign(matchers, newMatchers);
|
||
|
}
|