Extract from devtools project
This commit is contained in:
commit
60e678d0cf
25 changed files with 2805 additions and 0 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*.{ts,json}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
deno.d.ts
|
||||
.vscode
|
||||
.local
|
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# typescript/testing
|
||||
|
||||
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/testing?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=testing)
|
||||
|
||||
## About
|
||||
|
||||
Testing utilities for Deno, trying to stick to jasmine/jest syntax for easy
|
||||
porting.
|
||||
|
||||
## Import
|
||||
|
||||
In deno:
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from "https://js.thunderk.net/testing/mod.ts";
|
||||
```
|
||||
|
||||
## Use
|
||||
|
||||
Syntax is similar to [Jest](https://jestjs.io), and tries to be mostly
|
||||
compatible.
|
||||
|
||||
```typescript
|
||||
describe("a module", () => {
|
||||
it("does something", () => {
|
||||
expect(5).toBeGreatherThan(4);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
- Embeds a slighly adapted version of [expect](https://github.com/allain/expect)
|
||||
library from Allain Lalonde
|
3
TODO.md
Normal file
3
TODO.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# TODO
|
||||
|
||||
- Nested describe blocks
|
0
deps.ts
Normal file
0
deps.ts
Normal file
4
doc/about.md
Normal file
4
doc/about.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
## About
|
||||
|
||||
Testing utilities for Deno, trying to stick to jasmine/jest syntax for easy
|
||||
porting.
|
4
doc/credits.md
Normal file
4
doc/credits.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
## Credits
|
||||
|
||||
- Embeds a slighly adapted version of [expect](https://github.com/allain/expect)
|
||||
library from Allain Lalonde
|
7
doc/import.md
Normal file
7
doc/import.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
## Import
|
||||
|
||||
In deno:
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from "https://js.thunderk.net/testing/mod.ts";
|
||||
```
|
4
doc/index
Normal file
4
doc/index
Normal file
|
@ -0,0 +1,4 @@
|
|||
about
|
||||
import
|
||||
use
|
||||
credits
|
12
doc/use.md
Normal file
12
doc/use.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
## Use
|
||||
|
||||
Syntax is similar to [Jest](https://jestjs.io), and tries to be mostly
|
||||
compatible.
|
||||
|
||||
```typescript
|
||||
describe("a module", () => {
|
||||
it("does something", () => {
|
||||
expect(5).toBeGreatherThan(4);
|
||||
});
|
||||
};
|
||||
```
|
5
mod.ts
Normal file
5
mod.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Testing tools, trying to stick to jasmine/jest syntax for easy porting.
|
||||
|
||||
export { describe, it } from "./src/blocks.ts";
|
||||
export { expect } from "./src/assertions.ts";
|
||||
export { mock, mockfn, patch } from "./src/mock.ts";
|
19
run
Executable file
19
run
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
# Simplified run tool for deno commands
|
||||
|
||||
if test $# -eq 0
|
||||
then
|
||||
echo "Usage: $0 [file or command]"
|
||||
exit 1
|
||||
elif echo $1 | grep -q '.*.ts'
|
||||
then
|
||||
denocmd=run
|
||||
denoargs=$1
|
||||
shift
|
||||
else
|
||||
denocmd=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
denoargs="$(cat config/$denocmd.flags 2> /dev/null) $denoargs $@"
|
||||
exec deno $denocmd $denoargs
|
20
src/assertions.test.ts
Normal file
20
src/assertions.test.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { describe, it } from "./blocks.ts";
|
||||
import { expect } from "./assertions.ts";
|
||||
|
||||
describe("expect", () => {
|
||||
it("checks correctly for set equality", () => {
|
||||
expect(() => {
|
||||
expect(new Set([1, 2])).toEqual(new Set([2, 1]));
|
||||
}).not.toThrow();
|
||||
|
||||
expect(() => {
|
||||
expect(new Set([1])).toEqual([1]);
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
expect(new Set([1])).toEqual(new Set([2]));
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
expect(new Set([1])).toEqual({});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
8
src/assertions.ts
Normal file
8
src/assertions.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { expect as _expect } from "./expect/expect.ts";
|
||||
|
||||
export function expect(value: any): ReturnType<typeof _expect> {
|
||||
if (value && value.hasOwnProperty("_toExpected")) {
|
||||
value = value._toExpected();
|
||||
}
|
||||
return _expect(value);
|
||||
}
|
24
src/blocks.ts
Normal file
24
src/blocks.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
var _last_title = "";
|
||||
|
||||
// Test block, containing one or more it() calls.
|
||||
export function describe(
|
||||
title: string | { new (...args: any[]): any } | Function,
|
||||
body: Function,
|
||||
) {
|
||||
switch (typeof title) {
|
||||
case "string": {
|
||||
_last_title = title;
|
||||
break;
|
||||
}
|
||||
case "function": {
|
||||
_last_title = title.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
body();
|
||||
}
|
||||
|
||||
// Single test.
|
||||
export function it(title: string, body: () => void | Promise<void>) {
|
||||
Deno.test(_last_title ? `${_last_title} ${title}` : title, body);
|
||||
}
|
15
src/expect/LICENSE
Normal file
15
src/expect/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2019, Allain Lalonde
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
120
src/expect/expect.ts
Normal file
120
src/expect/expect.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
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);
|
||||
}
|
742
src/expect/expect_test.ts
Normal file
742
src/expect/expect_test.ts
Normal file
|
@ -0,0 +1,742 @@
|
|||
import {
|
||||
assertEquals,
|
||||
AssertionError,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.106.0/testing/asserts.ts";
|
||||
|
||||
import { addMatchers, expect } from "./expect.ts";
|
||||
import * as mock from "./mock.ts";
|
||||
|
||||
async function assertPass(fn: Function) {
|
||||
try {
|
||||
assertEquals(await fn(), undefined);
|
||||
} catch (err) {
|
||||
throw new AssertionError(
|
||||
`expected ${fn.toString()} to pass but it failed with ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertAllPass(...fns: Function[]) {
|
||||
for (let fn of fns) {
|
||||
await assertPass(fn);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertFail(fn: Function) {
|
||||
let thrown = true;
|
||||
try {
|
||||
let resolution = await fn();
|
||||
thrown = false;
|
||||
throw new AssertionError(
|
||||
`expected ${fn.toString()} to throw but it resolved with ${resolution}`,
|
||||
);
|
||||
} catch (err) {
|
||||
// expected
|
||||
if (!thrown) throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function assertAllFail(...fns: Function[]) {
|
||||
for (let fn of fns) {
|
||||
await assertFail(fn);
|
||||
}
|
||||
}
|
||||
|
||||
Deno.test({
|
||||
name: "exportsFunction",
|
||||
fn: () => {
|
||||
assertEquals(typeof expect, "function");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "throwsWhenNoMatcherFound",
|
||||
fn: () => {
|
||||
assertThrows(
|
||||
//@ts-ignore
|
||||
() => expect(true).toBeFancy(),
|
||||
TypeError,
|
||||
"matcher not found: toBeFancy",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "allowsExtendingMatchers",
|
||||
fn: () => {
|
||||
addMatchers({
|
||||
toBeFancy(value: any) {
|
||||
if (value === "fancy") {
|
||||
return { pass: true };
|
||||
} else {
|
||||
return { pass: false, message: "was not fancy" };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
assertPass(() => expect("fancy").toBeFancy());
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBe",
|
||||
fn: async () => {
|
||||
const obj = {};
|
||||
assertEquals(typeof expect(obj).toBe, "function");
|
||||
await assertAllPass(
|
||||
() => expect(obj).toBe(obj),
|
||||
() => expect(obj).not.toBe({}),
|
||||
() => expect(Promise.resolve(1)).resolves.toBe(1),
|
||||
() => expect(Promise.reject(1)).rejects.toBe(1),
|
||||
);
|
||||
|
||||
await assertFail(() => expect(obj).toBe({}));
|
||||
await assertFail(() => expect(obj).not.toBe(obj));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toEqual",
|
||||
fn: async () => {
|
||||
const obj = {};
|
||||
|
||||
await assertAllPass(
|
||||
() => expect(1).toEqual(1),
|
||||
() => expect(obj).toEqual({}),
|
||||
() => expect(obj).toEqual(obj),
|
||||
() => expect({ a: 1 }).toEqual({ a: 1 }),
|
||||
() => expect([1]).toEqual([1]),
|
||||
() => expect(Promise.resolve(1)).resolves.toEqual(1),
|
||||
() => expect(Promise.reject(1)).rejects.toEqual(1),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(1).toEqual(2),
|
||||
() => expect(1).toEqual(true),
|
||||
() => expect({}).toEqual(true),
|
||||
() => expect(1).not.toEqual(1),
|
||||
() => expect(true).not.toEqual(true),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "resolves",
|
||||
fn: async () => {
|
||||
const resolves = expect(Promise.resolve(true)).resolves;
|
||||
for (let method of ["toEqual", "toBe", "toBeTruthy", "toBeFalsy"]) {
|
||||
assertEquals(
|
||||
typeof (resolves as any)[method],
|
||||
"function",
|
||||
`missing ${method}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "rejects",
|
||||
fn: async () => {
|
||||
const rejects = expect(Promise.reject(true)).rejects;
|
||||
for (
|
||||
let method of ["toEqual", "toBe", "toBeTruthy", "toBeFalsy"]
|
||||
) {
|
||||
assertEquals(typeof (rejects as any)[method], "function");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeDefined",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(true).toBeDefined(),
|
||||
() => expect({}).toBeDefined(),
|
||||
() => expect([]).toBeDefined(),
|
||||
() => expect(undefined).not.toBeDefined(),
|
||||
() => expect(Promise.resolve({})).resolves.toBeDefined(),
|
||||
() => expect(Promise.reject({})).rejects.toBeDefined(),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(undefined).toBeDefined(),
|
||||
() => expect(true).not.toBeDefined(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeUndefined",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(undefined).toBeUndefined(),
|
||||
() => expect(null).not.toBeUndefined(),
|
||||
() => expect(Promise.resolve(undefined)).resolves.toBeUndefined(),
|
||||
() => expect(Promise.reject(undefined)).rejects.toBeUndefined(),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(null).toBeUndefined(),
|
||||
() => expect(undefined).not.toBeUndefined(),
|
||||
() => expect(false).toBeUndefined(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeTruthy",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(true).toBeTruthy(),
|
||||
() => expect(false).not.toBeTruthy(),
|
||||
() => expect(Promise.resolve(true)).resolves.toBeTruthy(),
|
||||
() => expect(Promise.reject(true)).rejects.toBeTruthy(),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(false).toBeTruthy(),
|
||||
() => expect(true).not.toBeTruthy(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeFalsy",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(false).toBeFalsy(),
|
||||
() => expect(true).not.toBeFalsy(),
|
||||
() => expect(Promise.resolve(false)).resolves.toBeFalsy(),
|
||||
() => expect(Promise.reject(false)).rejects.toBeFalsy(),
|
||||
);
|
||||
await assertAllFail(
|
||||
() => expect(true).toBeFalsy(),
|
||||
() => expect(false).not.toBeFalsy(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeGreaterThan",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(2).toBeGreaterThan(1),
|
||||
() => expect(1).not.toBeGreaterThan(2),
|
||||
() => expect(Promise.resolve(2)).resolves.toBeGreaterThan(1),
|
||||
() => expect(Promise.reject(2)).rejects.toBeGreaterThan(1),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(1).toBeGreaterThan(1),
|
||||
() => expect(1).toBeGreaterThan(2),
|
||||
() => expect(2).not.toBeGreaterThan(1),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeLessThan",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(1).toBeLessThan(2),
|
||||
() => expect(2).not.toBeLessThan(1),
|
||||
() => expect(Promise.resolve(1)).resolves.toBeLessThan(2),
|
||||
() => expect(Promise.reject(1)).rejects.toBeLessThan(2),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(1).toBeLessThan(1),
|
||||
() => expect(2).toBeLessThan(1),
|
||||
() => expect(1).not.toBeLessThan(2),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeGreaterThanOrEqual",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(2).toBeGreaterThanOrEqual(1),
|
||||
() => expect(1).toBeGreaterThanOrEqual(1),
|
||||
() => expect(1).not.toBeGreaterThanOrEqual(2),
|
||||
() => expect(Promise.resolve(2)).resolves.toBeGreaterThanOrEqual(2),
|
||||
() => expect(Promise.reject(2)).rejects.toBeGreaterThanOrEqual(2),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect(1).toBeGreaterThanOrEqual(2),
|
||||
() => expect(2).not.toBeGreaterThanOrEqual(1),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeLessThanOrEqual",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(1).toBeLessThanOrEqual(2),
|
||||
() => expect(1).toBeLessThanOrEqual(1),
|
||||
() => expect(2).not.toBeLessThanOrEqual(1),
|
||||
() => expect(Promise.resolve(1)).resolves.toBeLessThanOrEqual(2),
|
||||
() => expect(Promise.reject(1)).rejects.toBeLessThanOrEqual(2),
|
||||
);
|
||||
await assertAllFail(
|
||||
() => expect(2).toBeLessThanOrEqual(1),
|
||||
() => expect(1).not.toBeLessThanOrEqual(1),
|
||||
() => expect(1).not.toBeLessThanOrEqual(2),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeNull",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(null).toBeNull(),
|
||||
() => expect(undefined).not.toBeNull(),
|
||||
() => expect(false).not.toBeNull(),
|
||||
() => expect(Promise.resolve(null)).resolves.toBeNull(),
|
||||
() => expect(Promise.reject(null)).rejects.toBeNull(),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect({}).toBeNull(),
|
||||
() => expect(null).not.toBeNull(),
|
||||
() => expect(undefined).toBeNull(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeInstanceOf",
|
||||
fn: async () => {
|
||||
class A {}
|
||||
class B {}
|
||||
|
||||
await assertAllPass(
|
||||
() => expect(new A()).toBeInstanceOf(A),
|
||||
() => expect(new A()).not.toBeInstanceOf(B),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect({}).toBeInstanceOf(A),
|
||||
() => expect(null).toBeInstanceOf(A),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeNaN",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect(NaN).toBeNaN(),
|
||||
() => expect(10).not.toBeNaN(),
|
||||
() => expect(Promise.resolve(NaN)).resolves.toBeNaN(),
|
||||
);
|
||||
|
||||
await assertAllFail(() => expect(10).toBeNaN(), () => expect(10).toBeNaN());
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeMatch",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect("hello").toMatch(/^hell/),
|
||||
() => expect("hello").toMatch("hello"),
|
||||
() => expect("hello").toMatch("hell"),
|
||||
);
|
||||
|
||||
await assertAllFail(() => expect("yo").toMatch(/^hell/));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveProperty",
|
||||
fn: async () => {
|
||||
await assertAllPass(() => expect({ a: "10" }).toHaveProperty("a"));
|
||||
|
||||
await assertAllFail(() => expect({ a: 1 }).toHaveProperty("b"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveLength",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect([1, 2]).toHaveLength(2),
|
||||
() => expect({ length: 10 }).toHaveLength(10),
|
||||
);
|
||||
|
||||
await assertAllFail(() => expect([]).toHaveLength(10));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toContain",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => expect([1, 2, 3]).toContain(2),
|
||||
() => expect([]).not.toContain(2),
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => expect([1, 2, 3]).toContain(4),
|
||||
() => expect([]).toContain(4),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toThrow",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() =>
|
||||
expect(() => {
|
||||
throw new Error("TEST");
|
||||
}).toThrow("TEST"),
|
||||
() => expect(Promise.reject(new Error("TEST"))).rejects.toThrow("TEST"),
|
||||
);
|
||||
|
||||
await assertAllFail(() => expect(() => true).toThrow());
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalled",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(10);
|
||||
m(20);
|
||||
expect(m).toHaveBeenCalled();
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledTimes",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveBeenCalledTimes(0);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m();
|
||||
m();
|
||||
expect(m).toHaveBeenCalledTimes(2);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m();
|
||||
m();
|
||||
expect(m).toHaveBeenCalledTimes(3);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledWith",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(1, 2, 3);
|
||||
expect(m).toHaveBeenCalledWith(1, 2, 3);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(1, 2, 3);
|
||||
m(2, 3, 4);
|
||||
expect(m).toHaveBeenCalledWith(1, 2, 3);
|
||||
expect(m).toHaveBeenCalledWith(2, 3, 4);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveBeenCalledWith(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(2);
|
||||
expect(m).toHaveBeenCalledWith(1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenLastCalledWith",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(1, 2, 3);
|
||||
expect(m).toHaveBeenLastCalledWith(1, 2, 3);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(1, 2, 3);
|
||||
m(2, 3, 4);
|
||||
expect(m).not.toHaveBeenLastCalledWith(1, 2, 3);
|
||||
expect(m).toHaveBeenLastCalledWith(2, 3, 4);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveBeenLastCalledWith(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(2);
|
||||
expect(m).toHaveBeenLastCalledWith(1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenNthCalledWith",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(1, 2, 3);
|
||||
expect(m).toHaveBeenNthCalledWith(1, 1, 2, 3);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(1, 2, 3);
|
||||
m(2, 3, 4);
|
||||
expect(m).not.toHaveBeenNthCalledWith(2, 1, 2, 3);
|
||||
expect(m).toHaveBeenNthCalledWith(2, 2, 3, 4);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveBeenNthCalledWith(1, 1, 2);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(2);
|
||||
expect(m).toHaveBeenNthCalledWith(1, 1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedWith",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m();
|
||||
expect(m).toHaveReturnedWith(undefined);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn(() => true);
|
||||
m();
|
||||
expect(m).not.toHaveReturnedWith(false);
|
||||
expect(m).toHaveReturnedWith(true);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn(() => {
|
||||
throw new Error("TEST");
|
||||
});
|
||||
try {
|
||||
m();
|
||||
} catch (err) {}
|
||||
expect(m).not.toHaveReturnedWith(10);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveReturnedWith(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(2);
|
||||
expect(m).toHaveReturnedWith(1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedTimes",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m();
|
||||
expect(m).toHaveReturnedTimes(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn(() => true);
|
||||
expect(m).toHaveReturnedTimes(0);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn(() => {
|
||||
throw new Error("TEST");
|
||||
});
|
||||
try {
|
||||
m();
|
||||
} catch (err) {}
|
||||
expect(m).toHaveReturnedTimes(0);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveReturnedTimes(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(2);
|
||||
expect(m).not.toHaveReturnedTimes(1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturned",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m();
|
||||
expect(m).toHaveReturned();
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).not.toHaveReturned();
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn(() => {
|
||||
throw new Error("TEST");
|
||||
});
|
||||
try {
|
||||
m();
|
||||
} catch (err) {}
|
||||
expect(m).not.toHaveReturned();
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveReturned();
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m();
|
||||
expect(m).not.toHaveReturned();
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn(() => {
|
||||
throw new Error("TEST");
|
||||
});
|
||||
m();
|
||||
expect(m).not.toHaveReturned();
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveLastReturnedWith",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn((x: number) => x);
|
||||
m(1);
|
||||
m(2);
|
||||
expect(m).toHaveLastReturnedWith(2);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn((x: number) => x);
|
||||
m(1);
|
||||
m(2);
|
||||
expect(m).toHaveLastReturnedWith(2);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn((x: number) => x);
|
||||
expect(m).toHaveLastReturnedWith(1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn((x: number) => x);
|
||||
m(2);
|
||||
expect(m).toHaveLastReturnedWith(1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveNthReturnedWith",
|
||||
fn: async () => {
|
||||
await assertAllPass(
|
||||
() => {
|
||||
const m = mock.fn((x: number) => x);
|
||||
m(1, 2, 3);
|
||||
expect(m).toHaveNthReturnedWith(1, 1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn((x: number) => x);
|
||||
m(1, 2, 3);
|
||||
m(2, 3, 4);
|
||||
expect(m).not.toHaveNthReturnedWith(2, 1);
|
||||
expect(m).toHaveNthReturnedWith(2, 2);
|
||||
},
|
||||
);
|
||||
|
||||
await assertAllFail(
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
expect(m).toHaveNthReturnedWith(1, 1);
|
||||
},
|
||||
() => {
|
||||
const m = mock.fn();
|
||||
m(2);
|
||||
expect(m).toHaveNthReturnedWith(1, 1);
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
613
src/expect/matchers.ts
Normal file
613
src/expect/matchers.ts
Normal file
|
@ -0,0 +1,613 @@
|
|||
import {
|
||||
AssertionError,
|
||||
equal,
|
||||
} from "https://deno.land/std@0.106.0/testing/asserts.ts";
|
||||
|
||||
import {
|
||||
diff,
|
||||
DiffResult,
|
||||
DiffType,
|
||||
} from "https://deno.land/std@0.106.0/testing/_diff.ts";
|
||||
|
||||
import {
|
||||
bold,
|
||||
gray,
|
||||
green,
|
||||
red,
|
||||
white,
|
||||
} from "https://deno.land/std@0.106.0/fmt/colors.ts";
|
||||
|
||||
import * as mock from "./mock.ts";
|
||||
|
||||
type MatcherState = {
|
||||
isNot: boolean;
|
||||
};
|
||||
|
||||
export type Matcher = (value: any, ...args: any[]) => MatchResult;
|
||||
|
||||
export type Matchers = {
|
||||
[key: string]: Matcher;
|
||||
};
|
||||
export type MatchResult = {
|
||||
pass: boolean;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
const ACTUAL = red(bold("actual"));
|
||||
const EXPECTED = green(bold("expected"));
|
||||
|
||||
const CAN_NOT_DISPLAY = "[Cannot display]";
|
||||
|
||||
function createStr(v: unknown): string {
|
||||
try {
|
||||
return Deno.inspect(v);
|
||||
} catch (e) {
|
||||
return red(CAN_NOT_DISPLAY);
|
||||
}
|
||||
}
|
||||
|
||||
function createColor(diffType: DiffType): (s: string) => string {
|
||||
switch (diffType) {
|
||||
case DiffType.added:
|
||||
return (s: string) => green(bold(s));
|
||||
case DiffType.removed:
|
||||
return (s: string) => red(bold(s));
|
||||
default:
|
||||
return white;
|
||||
}
|
||||
}
|
||||
|
||||
function createSign(diffType: DiffType): string {
|
||||
switch (diffType) {
|
||||
case DiffType.added:
|
||||
return "+ ";
|
||||
case DiffType.removed:
|
||||
return "- ";
|
||||
default:
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessage(diffResult: ReadonlyArray<DiffResult<string>>): string {
|
||||
return diffResult
|
||||
.map((result: DiffResult<string>) => {
|
||||
const c = createColor(result.type);
|
||||
return c(`${createSign(result.type)}${result.value}`);
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function buildDiffMessage(actual: unknown, expected: unknown) {
|
||||
const actualString = createStr(actual);
|
||||
const expectedString = createStr(expected);
|
||||
|
||||
let message;
|
||||
try {
|
||||
const diffResult = diff(
|
||||
actualString.split("\n"),
|
||||
expectedString.split("\n"),
|
||||
);
|
||||
|
||||
return buildMessage(diffResult);
|
||||
} catch (e) {
|
||||
return `\n${red(CAN_NOT_DISPLAY)} + \n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
function buildFail(message: string) {
|
||||
return {
|
||||
pass: false,
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function toBe(actual: any, expected: any): MatchResult {
|
||||
if (actual === expected) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBe(${EXPECTED})\n\n${
|
||||
buildDiffMessage(
|
||||
actual,
|
||||
expected,
|
||||
)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toEqual(actual: any, expected: any): MatchResult {
|
||||
if (equal(actual, expected)) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toEqual(${EXPECTED})\n\n${
|
||||
buildDiffMessage(
|
||||
actual,
|
||||
expected,
|
||||
)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeGreaterThan(actual: any, comparison: number): MatchResult {
|
||||
if (actual > comparison) return { pass: true };
|
||||
|
||||
const actualString = createStr(actual);
|
||||
const comparisonString = createStr(comparison);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeGreaterThan(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} is not greater than ${green(comparisonString)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeLessThan(actual: any, comparison: number): MatchResult {
|
||||
if (actual < comparison) return { pass: true };
|
||||
|
||||
const actualString = createStr(actual);
|
||||
const comparisonString = createStr(comparison);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeLessThan(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} is not less than ${green(comparisonString)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeGreaterThanOrEqual(
|
||||
actual: any,
|
||||
comparison: number,
|
||||
): MatchResult {
|
||||
if (actual >= comparison) return { pass: true };
|
||||
|
||||
const actualString = createStr(actual);
|
||||
const comparisonString = createStr(comparison);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeGreaterThanOrEqual(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} is not greater than or equal to ${green(comparisonString)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeLessThanOrEqual(
|
||||
actual: any,
|
||||
comparison: number,
|
||||
): MatchResult {
|
||||
if (actual <= comparison) return { pass: true };
|
||||
|
||||
const actualString = createStr(actual);
|
||||
const comparisonString = createStr(comparison);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeLessThanOrEqual(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} is not less than or equal to ${green(comparisonString)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeTruthy(value: any): MatchResult {
|
||||
if (value) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
return buildFail(`expect(${ACTUAL}).toBeTruthy()
|
||||
|
||||
${red(actualString)} is not truthy`);
|
||||
}
|
||||
|
||||
export function toBeFalsy(value: any): MatchResult {
|
||||
if (!value) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeFalsy()\n\n ${red(actualString)} is not falsy`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeDefined(value: unknown): MatchResult {
|
||||
if (typeof value !== "undefined") return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeDefined()\n\n ${
|
||||
red(actualString)
|
||||
} is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeUndefined(value: unknown): MatchResult {
|
||||
if (typeof value === "undefined") return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeUndefined()\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} is defined but should be undefined`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeNull(value: unknown): MatchResult {
|
||||
if (value === null) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeNull()\n\n ${red(actualString)} should be null`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeNaN(value: unknown): MatchResult {
|
||||
if (typeof value === "number" && isNaN(value)) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeNaN()\n\n ${red(actualString)} should be NaN`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toBeInstanceOf(value: any, expected: Function): MatchResult {
|
||||
if (value instanceof expected) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
const expectedString = createStr(expected);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toBeInstanceOf(${EXPECTED})\n\n expected ${
|
||||
green(
|
||||
expected.name,
|
||||
)
|
||||
} but received ${red(actualString)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toMatch(value: any, pattern: RegExp | string): MatchResult {
|
||||
const valueStr = value.toString();
|
||||
if (typeof pattern === "string") {
|
||||
if (valueStr.indexOf(pattern) !== -1) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
const patternString = createStr(pattern);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toMatch(${EXPECTED})\n\n expected ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} to contain ${green(patternString)}`,
|
||||
);
|
||||
} else if (pattern instanceof RegExp) {
|
||||
if (pattern.exec(valueStr)) return { pass: true };
|
||||
|
||||
const actualString = createStr(value);
|
||||
const patternString = createStr(pattern);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toMatch(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} did not match regex ${green(patternString)}`,
|
||||
);
|
||||
} else {
|
||||
return buildFail("Invalid internal state");
|
||||
}
|
||||
}
|
||||
|
||||
export function toHaveProperty(value: any, propName: string): MatchResult {
|
||||
if (typeof value === "object" && typeof value[propName] !== "undefined") {
|
||||
return { pass: true };
|
||||
}
|
||||
|
||||
const actualString = createStr(value);
|
||||
const propNameString = createStr(propName);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveProperty(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} did not contain property ${green(propNameString)}`,
|
||||
);
|
||||
}
|
||||
export function toHaveLength(value: any, length: number): MatchResult {
|
||||
if (value?.length === length) return { pass: true };
|
||||
|
||||
const actualString = createStr(value.length);
|
||||
const lengthString = createStr(length);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveLength(${EXPECTED})\n\n expected array to have length ${
|
||||
green(
|
||||
lengthString,
|
||||
)
|
||||
} but was ${red(actualString)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toContain(value: any, item: any): MatchResult {
|
||||
if (value && typeof value.includes === "function" && value.includes(item)) {
|
||||
return { pass: true };
|
||||
}
|
||||
|
||||
const actualString = createStr(value);
|
||||
const itemString = createStr(item);
|
||||
|
||||
if (value && typeof value.includes === "function") {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toContain(${EXPECTED})\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} did not contain ${green(itemString)}`,
|
||||
);
|
||||
} else {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toContain(${EXPECTED})\n\n expected ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} to have an includes method but it is ${green(itemString)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function toThrow(value: any, error?: RegExp | string): MatchResult {
|
||||
let fn;
|
||||
if (typeof value === "function") {
|
||||
fn = value;
|
||||
try {
|
||||
value = value();
|
||||
} catch (err) {
|
||||
value = err;
|
||||
}
|
||||
}
|
||||
|
||||
const actualString = createStr(fn);
|
||||
const errorString = createStr(error);
|
||||
|
||||
if (value instanceof Error) {
|
||||
if (typeof error === "string") {
|
||||
if (!value.message.includes(error)) {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toThrow(${EXPECTED})\n\nexpected ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} to throw error matching ${green(errorString)} but it threw ${
|
||||
red(
|
||||
value.toString(),
|
||||
)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
} else if (error instanceof RegExp) {
|
||||
if (!value.message.match(error)) {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toThrow(${EXPECTED})\n\nexpected ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} to throw error matching ${green(errorString)} but it threw ${
|
||||
red(
|
||||
value.toString(),
|
||||
)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { pass: true };
|
||||
} else {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toThrow(${EXPECTED})\n\nexpected ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} to throw but it did not`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function extractMockCalls(
|
||||
value: any,
|
||||
name: string,
|
||||
): { error?: string; calls: mock.MockCall[] | null } {
|
||||
if (typeof value !== "function") {
|
||||
return {
|
||||
calls: null,
|
||||
error: `${name} only works on mock functions. received: ${value}`,
|
||||
};
|
||||
}
|
||||
const calls = mock.calls(value);
|
||||
if (calls === null) {
|
||||
return { calls: null, error: `${name} only works on mock functions` };
|
||||
}
|
||||
|
||||
return { calls };
|
||||
}
|
||||
|
||||
export function toHaveBeenCalled(value: any): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveBeenCalled");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const actualString = createStr(value);
|
||||
|
||||
if (calls && calls.length !== 0) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenCalled()\n\n ${
|
||||
red(
|
||||
actualString,
|
||||
)
|
||||
} was not called`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toHaveBeenCalledTimes(value: any, times: number): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveBeenCalledTimes");
|
||||
if (error) return buildFail(error);
|
||||
if (!calls) return buildFail("Invalid internal state");
|
||||
|
||||
if (calls && calls.length === times) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenCalledTimes(${EXPECTED})\n\n expected ${times} calls but was called: ${calls.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toHaveBeenCalledWith(value: any, ...args: any[]): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveBeenCalledWith");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const wasCalledWith = calls && calls.some((c) => equal(c.args, args));
|
||||
if (wasCalledWith) return { pass: true };
|
||||
|
||||
const argsString = createStr(args);
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenCalledWith(${EXPECTED})\n\n function was not called with: ${
|
||||
green(
|
||||
argsString,
|
||||
)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toHaveBeenLastCalledWith(
|
||||
value: any,
|
||||
...args: any[]
|
||||
): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveBeenLastCalledWith");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
if (!calls || !calls.length) {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenLastCalledWith(...${EXPECTED})\n\n expect last call args to be ${args} but was not called`,
|
||||
);
|
||||
}
|
||||
|
||||
const lastCall = calls[calls.length - 1];
|
||||
if (equal(lastCall.args, args)) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenLastCalledWith(...${EXPECTED})\n\n expect last call args to be ${args} but was: ${lastCall.args}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toHaveBeenNthCalledWith(
|
||||
value: any,
|
||||
nth: number,
|
||||
...args: any[]
|
||||
): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveBeenNthCalledWith");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const nthCall = calls && calls[nth - 1];
|
||||
if (nthCall) {
|
||||
if (equal(nthCall.args, args)) return { pass: true };
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenNthCalledWith(${EXPECTED})\n\n expect ${nth}th call args to be ${args} but was: ${nthCall.args}`,
|
||||
);
|
||||
} else {
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveBeenNthCalledWith(${EXPECTED})\n\n ${nth}th call was not made.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function toHaveReturnedWith(value: any, result: any): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveReturnedWith");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const wasReturnedWith = calls &&
|
||||
calls.some((c) => c.returns && equal(c.returned, result));
|
||||
if (wasReturnedWith) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expect(${ACTUAL}).toHaveReturnedWith(${EXPECTED})\n\n function did not return: ${result}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toHaveReturned(value: any): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveReturned");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
if (calls && calls.some((c) => c.returns)) return { pass: true };
|
||||
|
||||
// TODO(allain): better messages
|
||||
return buildFail(`expected function to return but it never did`);
|
||||
}
|
||||
|
||||
// TODO(allain): better messages
|
||||
export function toHaveLastReturnedWith(value: any, expected: any): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveLastReturnedWith");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const lastCall = calls && calls[calls.length - 1];
|
||||
if (!lastCall) {
|
||||
return buildFail("no calls made to function");
|
||||
}
|
||||
if (lastCall.throws) {
|
||||
return buildFail(`last call to function threw: ${lastCall.thrown}`);
|
||||
}
|
||||
|
||||
if (equal(lastCall.returned, expected)) return { pass: true };
|
||||
|
||||
return buildFail(
|
||||
`expected last call to return ${expected} but returned: ${lastCall.returned}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function toHaveReturnedTimes(value: any, times: number): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveReturnedTimes");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const returnCount = calls && calls.filter((c) => c.returns).length;
|
||||
if (returnCount !== times) {
|
||||
return buildFail(
|
||||
`expected ${times} returned times but returned ${returnCount} times`,
|
||||
);
|
||||
}
|
||||
|
||||
return { pass: true };
|
||||
}
|
||||
|
||||
export function toHaveNthReturnedWith(
|
||||
value: any,
|
||||
nth: number,
|
||||
expected: any,
|
||||
): MatchResult {
|
||||
const { calls, error } = extractMockCalls(value, "toHaveNthReturnedWith");
|
||||
if (error) return buildFail(error);
|
||||
|
||||
const nthCall = calls && calls[nth - 1];
|
||||
if (!nthCall) {
|
||||
return buildFail(`${nth} calls were now made`);
|
||||
}
|
||||
|
||||
if (nthCall.throws) {
|
||||
return buildFail(`${nth}th call to function threw: ${nthCall.thrown}`);
|
||||
}
|
||||
|
||||
if (!equal(nthCall.returned, expected)) {
|
||||
return buildFail(
|
||||
`expected ${nth}th call to return ${expected} but returned: ${nthCall.returned}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { pass: true };
|
||||
}
|
728
src/expect/matchers_test.ts
Normal file
728
src/expect/matchers_test.ts
Normal file
|
@ -0,0 +1,728 @@
|
|||
import {
|
||||
assert,
|
||||
assertEquals,
|
||||
} from "https://deno.land/std@0.106.0/testing/asserts.ts";
|
||||
import * as mock from "./mock.ts";
|
||||
|
||||
import {
|
||||
MatchResult,
|
||||
toBe,
|
||||
toBeDefined,
|
||||
toBeFalsy,
|
||||
toBeGreaterThan,
|
||||
toBeInstanceOf,
|
||||
toBeLessThan,
|
||||
toBeLessThanOrEqual,
|
||||
toBeNaN,
|
||||
toBeNull,
|
||||
toBeTruthy,
|
||||
toBeUndefined,
|
||||
toContain,
|
||||
toEqual,
|
||||
toHaveBeenCalled,
|
||||
toHaveBeenCalledTimes,
|
||||
toHaveBeenCalledWith,
|
||||
toHaveBeenLastCalledWith,
|
||||
toHaveBeenNthCalledWith,
|
||||
toHaveLastReturnedWith,
|
||||
toHaveLength,
|
||||
toHaveNthReturnedWith,
|
||||
toHaveProperty,
|
||||
toHaveReturned,
|
||||
toHaveReturnedTimes,
|
||||
toHaveReturnedWith,
|
||||
toMatch,
|
||||
toThrow,
|
||||
} from "./matchers.ts";
|
||||
|
||||
function assertResult(actual: MatchResult, expected: MatchResult) {
|
||||
assertEquals(
|
||||
actual.pass,
|
||||
expected.pass,
|
||||
`expected to be ${
|
||||
expected.pass ? `pass but received: ${actual.message}` : "fail"
|
||||
}`,
|
||||
);
|
||||
if (typeof expected.message !== "undefined") {
|
||||
assert(!!actual.message, "no message given");
|
||||
const colourless = actual.message.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
"",
|
||||
);
|
||||
const trim = (x: string) => x.trim().replace(/\s*\n\s+/g, "\n");
|
||||
|
||||
assertEquals(trim(colourless), trim(expected.message));
|
||||
}
|
||||
}
|
||||
|
||||
function assertResultPass(result: any) {
|
||||
assertResult(result, { pass: true });
|
||||
}
|
||||
|
||||
Deno.test({
|
||||
name: "toBePass",
|
||||
fn: () => {
|
||||
assertResultPass(toBe(10, 10));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeFail",
|
||||
fn: () => {
|
||||
assertResult(toBe(10, 20), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBe(expected)
|
||||
- 10
|
||||
+ 20`,
|
||||
});
|
||||
|
||||
assertResult(toBe({}, {}), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBe(expected)
|
||||
|
||||
{}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toEqualPass",
|
||||
fn: () => {
|
||||
assertResultPass(toEqual({ a: 1 }, { a: 1 }));
|
||||
assertResultPass(toEqual(1, 1));
|
||||
assertResultPass(toEqual([1], [1]));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toEqualFail",
|
||||
fn: () => {
|
||||
assertResult(toEqual(10, 20), {
|
||||
pass: false,
|
||||
message: `expect(actual).toEqual(expected)\n\n- 10\n+ 20`,
|
||||
});
|
||||
|
||||
assertResult(toEqual({ a: 1 }, { a: 2 }), {
|
||||
pass: false,
|
||||
message: `expect(actual).toEqual(expected)
|
||||
- { a: 1 }
|
||||
+ { a: 2 }`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeGreaterThanPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeGreaterThan(2, 1));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeGreaterThanFail",
|
||||
fn: () => {
|
||||
assertResult(toBeGreaterThan(1, 2), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeGreaterThan(expected)
|
||||
|
||||
1 is not greater than 2`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeLessThanPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeLessThan(1, 2));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeLessThanFail",
|
||||
fn: () => {
|
||||
assertResult(toBeLessThan(2, 1), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeLessThan(expected)
|
||||
|
||||
2 is not less than 1`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeLessThanOrEqualPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeLessThanOrEqual(1, 2));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeLessThanOrEqualFail",
|
||||
fn: () => {
|
||||
assertResult(toBeLessThanOrEqual(2, 1), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeLessThanOrEqual(expected)
|
||||
|
||||
2 is not less than or equal to 1`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeTruthyPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeTruthy(1));
|
||||
assertResultPass(toBeTruthy(true));
|
||||
assertResultPass(toBeTruthy([]));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeTruthyFail",
|
||||
fn: () => {
|
||||
assertResult(toBeTruthy(false), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeTruthy()
|
||||
|
||||
false is not truthy`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeFalsyPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeFalsy(0));
|
||||
assertResultPass(toBeFalsy(false));
|
||||
assertResultPass(toBeFalsy(null));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeFalsyFail",
|
||||
fn: () => {
|
||||
assertResult(toBeFalsy(true), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeFalsy()
|
||||
|
||||
true is not falsy`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeDefinedPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeDefined(1));
|
||||
assertResultPass(toBeDefined({}));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeDefinedFail",
|
||||
fn: () => {
|
||||
assertResult(toBeDefined(undefined), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeDefined()
|
||||
|
||||
undefined is not defined`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeUndefinedPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeUndefined(undefined));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeUndefinedFail",
|
||||
fn: () => {
|
||||
assertResult(toBeUndefined(null), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeUndefined()
|
||||
|
||||
null is defined but should be undefined`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeNullPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeNull(null));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeNullFail",
|
||||
fn: () => {
|
||||
assertResult(toBeNull(10), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeNull()
|
||||
|
||||
10 should be null`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeNaNPass",
|
||||
fn: () => {
|
||||
assertResultPass(toBeNaN(NaN));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeNaNFail",
|
||||
fn: () => {
|
||||
assertResult(toBeNaN(10), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeNaN()
|
||||
|
||||
10 should be NaN`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeInstanceOfPass",
|
||||
fn: () => {
|
||||
class A {}
|
||||
const a = new A();
|
||||
assertResultPass(toBeInstanceOf(a, A));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeInstanceOfFail",
|
||||
fn: () => {
|
||||
class A {}
|
||||
class B {}
|
||||
|
||||
const a = new A();
|
||||
|
||||
assertResult(toBeInstanceOf(a, B), {
|
||||
pass: false,
|
||||
message: `expect(actual).toBeInstanceOf(expected)
|
||||
|
||||
expected B but received A {}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeMatchPass",
|
||||
fn: () => {
|
||||
assertResultPass(toMatch("hello", "hell"));
|
||||
assertResultPass(toMatch("hello", /^hell/));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeMatchFail",
|
||||
fn: () => {
|
||||
assertResult(toMatch("yo", "hell"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toMatch(expected)
|
||||
|
||||
expected "yo" to contain "hell"`,
|
||||
});
|
||||
|
||||
assertResult(toMatch("yo", /^hell/), {
|
||||
pass: false,
|
||||
message: `expect(actual).toMatch(expected)
|
||||
|
||||
"yo" did not match regex /^hell/`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeHavePropertyPass",
|
||||
fn: () => {
|
||||
assertResultPass(toHaveProperty({ a: 1 }, "a"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeHavePropertyFail",
|
||||
fn: () => {
|
||||
assertResult(toHaveProperty({ a: 1 }, "b"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveProperty(expected)
|
||||
|
||||
{ a: 1 } did not contain property "b"`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveLengthPass",
|
||||
fn: () => {
|
||||
assertResultPass(toHaveLength([], 0));
|
||||
assertResultPass(toHaveLength([1, 2], 2));
|
||||
assertResultPass(toHaveLength({ length: 2 }, 2));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toBeHaveLengthFail",
|
||||
fn: () => {
|
||||
assertResult(toHaveLength([], 1), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveLength(expected)
|
||||
|
||||
expected array to have length 1 but was 0`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toContainPass",
|
||||
fn: () => {
|
||||
assertResultPass(toContain([1, 2], 2));
|
||||
assertResultPass(toContain("Hello", "Hell"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toContainFail",
|
||||
fn: () => {
|
||||
assertResult(toContain([2, 3], 1), {
|
||||
pass: false,
|
||||
message: `expect(actual).toContain(expected)
|
||||
|
||||
[ 2, 3 ] did not contain 1`,
|
||||
});
|
||||
assertResult(toContain("Hello", "Good"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toContain(expected)
|
||||
|
||||
"Hello" did not contain "Good"`,
|
||||
});
|
||||
assertResult(toContain(false, 1), {
|
||||
pass: false,
|
||||
message: `expect(actual).toContain(expected)
|
||||
|
||||
expected false to have an includes method but it is 1`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toThrowPass",
|
||||
fn: () => {
|
||||
assertResultPass(
|
||||
toThrow(() => {
|
||||
throw new Error("TEST");
|
||||
}, "TEST"),
|
||||
);
|
||||
assertResultPass(
|
||||
toThrow(() => {
|
||||
throw new Error("TEST");
|
||||
}, /^TEST/),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toThrowFail",
|
||||
fn: () => {
|
||||
assertResult(
|
||||
toThrow(() => {}, "TEST"),
|
||||
{
|
||||
pass: false,
|
||||
message: `expect(actual).toThrow(expected)
|
||||
|
||||
expected [Function] to throw but it did not`,
|
||||
},
|
||||
);
|
||||
|
||||
assertResult(
|
||||
toThrow(() => {
|
||||
throw new Error("BLAH");
|
||||
}, "TEST"),
|
||||
{
|
||||
pass: false,
|
||||
message: `expect(actual).toThrow(expected)
|
||||
|
||||
expected [Function] to throw error matching "TEST" but it threw Error: BLAH`,
|
||||
},
|
||||
);
|
||||
|
||||
assertResult(
|
||||
toThrow(() => {
|
||||
throw new Error("BLAH");
|
||||
}, /^TEST/),
|
||||
{
|
||||
pass: false,
|
||||
message: `expect(actual).toThrow(expected)
|
||||
|
||||
expected [Function] to throw error matching /^TEST/ but it threw Error: BLAH`,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledPass",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(10);
|
||||
assertResultPass(toHaveBeenCalled(m));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledFail",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
assertResult(toHaveBeenCalled(m), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenCalled()
|
||||
|
||||
[Function: f] was not called`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledTimesPass",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(10);
|
||||
m(12);
|
||||
assertResultPass(toHaveBeenCalledTimes(m, 2));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledTimesFail",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(10);
|
||||
assertResult(toHaveBeenCalledTimes(m, 2), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenCalledTimes(expected)
|
||||
|
||||
expected 2 calls but was called: 1`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(1, "a");
|
||||
assertResultPass(toHaveBeenCalledWith(m, 1, "a"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenCalledWithFail",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(1, "a");
|
||||
assertResult(toHaveBeenCalledWith(m, 2, "b"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenCalledWith(expected)
|
||||
|
||||
function was not called with: [ 2, "b" ]`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenLastCalledWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(1, "a");
|
||||
m(2, "b");
|
||||
m(3, "c");
|
||||
assertResultPass(toHaveBeenLastCalledWith(m, 3, "c"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenLastCalledWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
assertResult(toHaveBeenLastCalledWith(m, 2, "b"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenLastCalledWith(...expected)
|
||||
|
||||
expect last call args to be 2,b but was not called`,
|
||||
});
|
||||
m(1, "a");
|
||||
m(2, "b");
|
||||
m(3, "c");
|
||||
assertResult(toHaveBeenLastCalledWith(m, 2, "b"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenLastCalledWith(...expected)
|
||||
|
||||
expect last call args to be 2,b but was: 3,c`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenNthCalledWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
m(1, "a");
|
||||
m(2, "b");
|
||||
m(3, "c");
|
||||
const nthCall = 2;
|
||||
assertResultPass(toHaveBeenNthCalledWith(m, nthCall, 2, "b"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveBeenNthCalledWithFail",
|
||||
fn: () => {
|
||||
const m = mock.fn();
|
||||
const nthCall = 3;
|
||||
assertResult(toHaveBeenNthCalledWith(m, nthCall, 2, "b"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenNthCalledWith(expected)
|
||||
|
||||
3th call was not made.`,
|
||||
});
|
||||
m(1, "a");
|
||||
m(2, "b");
|
||||
m(3, "c");
|
||||
assertResult(toHaveBeenNthCalledWith(m, nthCall, 2, "b"), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveBeenNthCalledWith(expected)
|
||||
|
||||
expect 3th call args to be 2,b but was: 3,c`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn(() => true);
|
||||
m();
|
||||
assertResultPass(toHaveReturnedWith(m, true));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedWithFail",
|
||||
fn: () => {
|
||||
const m = mock.fn(() => true);
|
||||
m();
|
||||
assertResult(toHaveReturnedWith(m, false), {
|
||||
pass: false,
|
||||
message: `expect(actual).toHaveReturnedWith(expected)
|
||||
|
||||
function did not return: false`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedPass",
|
||||
fn: () => {
|
||||
const m = mock.fn(() => true);
|
||||
m();
|
||||
assertResultPass(toHaveReturned(m));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedFail",
|
||||
fn: () => {
|
||||
const m = mock.fn(() => true);
|
||||
assertResult(toHaveReturned(m), {
|
||||
pass: false,
|
||||
message: `expected function to return but it never did`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveLastReturnedWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn((arg: boolean) => arg);
|
||||
m(false);
|
||||
m(true);
|
||||
assertResultPass(toHaveLastReturnedWith(m, true));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveLastReturnedWithFail",
|
||||
fn: () => {
|
||||
const m = mock.fn((arg: boolean) => arg);
|
||||
assertResult(toHaveLastReturnedWith(m, true), {
|
||||
pass: false,
|
||||
message: `no calls made to function`,
|
||||
});
|
||||
m(true);
|
||||
m(false);
|
||||
assertResult(toHaveLastReturnedWith(m, true), {
|
||||
pass: false,
|
||||
message: `expected last call to return true but returned: false`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedTimesPass",
|
||||
fn: () => {
|
||||
const m = mock.fn(() => true);
|
||||
m();
|
||||
m();
|
||||
m();
|
||||
assertResultPass(toHaveReturnedTimes(m, 3));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveReturnedTimesFail",
|
||||
fn: () => {
|
||||
const m = mock.fn(() => true);
|
||||
m();
|
||||
assertResult(toHaveReturnedTimes(m, 3), {
|
||||
pass: false,
|
||||
message: `expected 3 returned times but returned 1 times`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveNthReturnedWithPass",
|
||||
fn: () => {
|
||||
const m = mock.fn((n: number) => n);
|
||||
m(1);
|
||||
m(2);
|
||||
m(3);
|
||||
const nthCall = 2;
|
||||
assertResultPass(toHaveNthReturnedWith(m, nthCall, 2));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "toHaveNthReturnedWithFail",
|
||||
fn: () => {
|
||||
const m = mock.fn((n: number) => n);
|
||||
m(1);
|
||||
m(2);
|
||||
m(3);
|
||||
assertResult(toHaveNthReturnedWith(m, 2, 1), {
|
||||
pass: false,
|
||||
message: `expected 2th call to return 1 but returned: 2`,
|
||||
});
|
||||
assertResult(toHaveNthReturnedWith(m, 9, 1), {
|
||||
pass: false,
|
||||
message: `9 calls were now made`,
|
||||
});
|
||||
},
|
||||
});
|
57
src/expect/mock.ts
Normal file
57
src/expect/mock.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
const MOCK_SYMBOL = Symbol.for("@MOCK");
|
||||
|
||||
export type MockCall = {
|
||||
args: any[];
|
||||
returned?: any;
|
||||
thrown?: any;
|
||||
timestamp: number;
|
||||
returns: boolean;
|
||||
throws: boolean;
|
||||
};
|
||||
|
||||
export function fn(...stubs: Function[]) {
|
||||
const calls: MockCall[] = [];
|
||||
|
||||
const f = (...args: any[]) => {
|
||||
const stub = stubs.length === 1
|
||||
? // keep reusing the first
|
||||
stubs[0]
|
||||
: // pick the exact mock for the current call
|
||||
stubs[calls.length];
|
||||
|
||||
try {
|
||||
const returned = stub ? stub(...args) : undefined;
|
||||
calls.push({
|
||||
args,
|
||||
returned,
|
||||
timestamp: Date.now(),
|
||||
returns: true,
|
||||
throws: false,
|
||||
});
|
||||
return returned;
|
||||
} catch (err) {
|
||||
calls.push({
|
||||
args,
|
||||
timestamp: Date.now(),
|
||||
returns: false,
|
||||
thrown: err,
|
||||
throws: true,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(f, MOCK_SYMBOL, {
|
||||
value: { calls },
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
export function calls(f: Function): MockCall[] {
|
||||
const mockInfo = (f as any)[MOCK_SYMBOL];
|
||||
if (!mockInfo) throw new Error("callCount only available on mock functions");
|
||||
|
||||
return [...mockInfo.calls];
|
||||
}
|
72
src/expect/mock_test.ts
Normal file
72
src/expect/mock_test.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import {
|
||||
assert,
|
||||
assertEquals,
|
||||
} from "https://deno.land/std@0.106.0/testing/asserts.ts";
|
||||
|
||||
import * as mock from "./mock.ts";
|
||||
|
||||
Deno.test({
|
||||
name: "canMockFunctions",
|
||||
fn: () => {
|
||||
assertEquals(typeof mock.fn(), "function");
|
||||
const f = mock.fn();
|
||||
f(10);
|
||||
f(20);
|
||||
|
||||
const calls = mock.calls(f);
|
||||
assert(Array.isArray(calls));
|
||||
assertEquals(calls.length, 2);
|
||||
assertEquals(calls.map((c: any) => c.args), [[10], [20]]);
|
||||
assertEquals(calls.map((c: any) => c.returned), [undefined, undefined]);
|
||||
|
||||
assert(
|
||||
calls.map((c: any) => typeof c.timestamp).every((t: string) =>
|
||||
t === "number"
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "mockFunctionTracksReturns",
|
||||
fn: () => {
|
||||
const f = mock.fn(
|
||||
() => 1,
|
||||
() => {
|
||||
throw new Error("TEST");
|
||||
},
|
||||
);
|
||||
try {
|
||||
f();
|
||||
f();
|
||||
} catch {}
|
||||
const calls = mock.calls(f);
|
||||
assert(calls[0].returns);
|
||||
assert(!calls[0].throws);
|
||||
assert(!calls[1].returns);
|
||||
assert(calls[1].throws);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "mockFunctionCanHaveImplementations",
|
||||
fn: () => {
|
||||
const f = mock.fn(
|
||||
(n: number) => n,
|
||||
(n: number) => n * 2,
|
||||
(n: number) => n * 3,
|
||||
);
|
||||
f(1);
|
||||
f(1);
|
||||
f(1);
|
||||
f(1);
|
||||
f(1);
|
||||
|
||||
const calls = mock.calls(f);
|
||||
assertEquals(calls.length, 5);
|
||||
assertEquals(
|
||||
calls.map((c: mock.MockCall) => c.returned),
|
||||
[1, 2, 3, undefined, undefined],
|
||||
);
|
||||
},
|
||||
});
|
206
src/mock.test.ts
Normal file
206
src/mock.test.ts
Normal file
|
@ -0,0 +1,206 @@
|
|||
import { describe, it } from "./blocks.ts";
|
||||
import { expect } from "./assertions.ts";
|
||||
import { mock, mockfn, patch } from "./mock.ts";
|
||||
|
||||
class Obj {
|
||||
called = 0;
|
||||
func(x: number) {
|
||||
this.called += 1;
|
||||
return x * 2;
|
||||
}
|
||||
}
|
||||
|
||||
function checkRestored(o: Obj) {
|
||||
const called = o.called;
|
||||
const result = o.func(4);
|
||||
expect(o.called).toEqual(called + 1);
|
||||
expect(result).toEqual(8);
|
||||
}
|
||||
|
||||
describe("patch", () => {
|
||||
it("calls through by default", () => {
|
||||
const o = new Obj();
|
||||
|
||||
patch(o, "func", (mock) => {
|
||||
const result = o.func(3);
|
||||
expect(o.called).toEqual(1);
|
||||
expect(result).toEqual(6);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith(3);
|
||||
});
|
||||
|
||||
patch(o, "func", (mock) => {
|
||||
const result = o.func(1);
|
||||
expect(o.called).toEqual(2);
|
||||
expect(result).toEqual(2);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
|
||||
it("calls sequential stubs before passing through", () => {
|
||||
const o = new Obj();
|
||||
|
||||
patch(o, "func", (mock) => {
|
||||
mock.stub((a) => a * 3);
|
||||
mock.stub((a) => a * 4);
|
||||
|
||||
let result = o.func(2);
|
||||
expect(result).toEqual(6);
|
||||
|
||||
result = o.func(2);
|
||||
expect(result).toEqual(8);
|
||||
|
||||
expect(o.called).toEqual(0);
|
||||
|
||||
result = o.func(2);
|
||||
expect(result).toEqual(4);
|
||||
|
||||
expect(o.called).toEqual(1);
|
||||
expect(mock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
|
||||
it("calls stubs in loop", () => {
|
||||
const o = new Obj();
|
||||
|
||||
patch(o, "func", (mock) => {
|
||||
mock.stub((a) => a * 3, true);
|
||||
let result = o.func(2);
|
||||
expect(result).toEqual(6);
|
||||
|
||||
result = o.func(2);
|
||||
expect(result).toEqual(6);
|
||||
|
||||
result = o.func(2);
|
||||
expect(result).toEqual(6);
|
||||
|
||||
expect(o.called).toEqual(0);
|
||||
expect(mock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
|
||||
it("can reset the mock", () => {
|
||||
const o = new Obj();
|
||||
|
||||
patch(o, "func", (mock) => {
|
||||
mock.stub((a) => a + 1, true);
|
||||
let result = o.func(3);
|
||||
expect(o.called).toEqual(0);
|
||||
expect(result).toEqual(4);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith(3);
|
||||
|
||||
result = o.func(2);
|
||||
expect(o.called).toEqual(0);
|
||||
expect(result).toEqual(3);
|
||||
expect(mock).toHaveBeenCalledTimes(2);
|
||||
expect(mock).toHaveBeenCalledWith(2);
|
||||
|
||||
mock.reset();
|
||||
mock.stub((a) => a + 10, true);
|
||||
|
||||
result = o.func(4);
|
||||
expect(o.called).toEqual(0);
|
||||
expect(result).toEqual(14);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith(4);
|
||||
|
||||
mock.reset();
|
||||
|
||||
result = o.func(4);
|
||||
expect(o.called).toEqual(1);
|
||||
expect(result).toEqual(8);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith(4);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mock", () => {
|
||||
it("puts a no-op stub by default", () => {
|
||||
const o = new Obj();
|
||||
|
||||
mock(o, "func", undefined, (mock) => {
|
||||
let result = o.func(2);
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
expect(o.called).toEqual(0);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
|
||||
const f = mock(o, "func", undefined, () => o.func(2));
|
||||
expect(f).toHaveBeenCalledTimes(1);
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
|
||||
it("stubs with a fixed value", () => {
|
||||
const o = new Obj();
|
||||
|
||||
mock(o, "func", 17, (mock) => {
|
||||
let result = o.func(2);
|
||||
expect(result).toBe(17);
|
||||
|
||||
result = o.func(2);
|
||||
expect(result).toBe(17);
|
||||
|
||||
expect(o.called).toEqual(0);
|
||||
expect(mock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
|
||||
it("stubs with a function result", () => {
|
||||
const o = new Obj();
|
||||
|
||||
let i = 9;
|
||||
mock(o, "func", () => ++i, (mock) => {
|
||||
let result = o.func(2);
|
||||
expect(result).toBe(10);
|
||||
|
||||
result = o.func(2);
|
||||
expect(result).toBe(11);
|
||||
|
||||
expect(o.called).toEqual(0);
|
||||
expect(mock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
checkRestored(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mockfn", () => {
|
||||
it("constructs a simple mock function", () => {
|
||||
let mock = mockfn();
|
||||
expect(mock).toHaveBeenCalledTimes(0);
|
||||
mock();
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
|
||||
mock = mockfn(() => 2);
|
||||
expect(mock).toHaveBeenCalledTimes(0);
|
||||
let result = mock();
|
||||
expect(result).toEqual(2);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
result = mock();
|
||||
expect(result).toEqual(2);
|
||||
expect(mock).toHaveBeenCalledTimes(2);
|
||||
|
||||
mock = mockfn((x: number) => x + 1);
|
||||
result = mock(5);
|
||||
expect(result).toEqual(6);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith(5);
|
||||
});
|
||||
});
|
86
src/mock.ts
Normal file
86
src/mock.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { expect } from "./assertions.ts";
|
||||
import * as _mock from "./expect/mock.ts";
|
||||
|
||||
export const mockfn = _mock.fn;
|
||||
|
||||
/**
|
||||
* Patch an object's method
|
||||
*/
|
||||
export function patch<
|
||||
O,
|
||||
K extends keyof O,
|
||||
F extends Function & O[K],
|
||||
>(
|
||||
obj: O,
|
||||
method: K,
|
||||
lifetime: (mock: MockManipulator<F>) => void,
|
||||
): F {
|
||||
const orig = obj[method] as F;
|
||||
let stubs: { impl: F; loop: boolean }[] = [];
|
||||
const createMock = () =>
|
||||
mockfn((...args: any[]) => {
|
||||
if (stubs.length) {
|
||||
const result = stubs[0].impl(...args);
|
||||
if (!stubs[0].loop) {
|
||||
stubs.shift();
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return orig.call(obj, ...args);
|
||||
}
|
||||
});
|
||||
let mock = createMock();
|
||||
|
||||
Object.assign(obj, { [method]: mock });
|
||||
try {
|
||||
lifetime({
|
||||
_toExpected() {
|
||||
return mock;
|
||||
},
|
||||
stub(impl?: F, loop = false) {
|
||||
if (impl) {
|
||||
stubs.push({ impl, loop });
|
||||
} else {
|
||||
stubs.push({ impl: function () {} as any, loop: true });
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
mock = createMock();
|
||||
Object.assign(obj, { [method]: mock });
|
||||
stubs = [];
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
Object.assign(obj, { [method]: orig });
|
||||
}
|
||||
|
||||
return mock as unknown as F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to *patch*, but with stub as enforced default
|
||||
*/
|
||||
export function mock<
|
||||
O,
|
||||
K extends keyof O,
|
||||
F extends (...args: any) => any & O[K],
|
||||
>(
|
||||
obj: O,
|
||||
method: K,
|
||||
stub: F | ReturnType<F> | undefined,
|
||||
lifetime: (mock: MockManipulator<F>) => void,
|
||||
): F {
|
||||
return patch(obj, method, (mock) => {
|
||||
mock.stub(
|
||||
typeof stub == "function" ? stub as any : (() => stub) as F,
|
||||
true,
|
||||
);
|
||||
lifetime(mock as any);
|
||||
}) as unknown as F;
|
||||
}
|
||||
|
||||
type MockManipulator<F extends Function> = {
|
||||
_toExpected: () => Parameters<typeof expect>[0];
|
||||
reset: () => void;
|
||||
stub: (impl?: F, loop?: boolean) => void;
|
||||
};
|
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "ESNext",
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"preserveConstEnums": true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue