From 6de4de6a1810953d2f5a74a3f3dc4c7e3ac84435 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 16 Nov 2023 11:42:42 +0900 Subject: [PATCH 01/17] feat: add expect syntax by wrapping assert syntax Co-authored-by: Thomas Cruveilher <38007824+Sorikairox@users.noreply.github.com> --- assert/assert_instance_of.ts | 2 +- testing/_matchers/mod.ts | 2 + testing/_matchers/to__strict_equal.ts | 22 ++ testing/_matchers/to_be.ts | 15 ++ testing/_matchers/to_be_close_to.ts | 34 +++ testing/_matchers/to_be_defined.ts | 15 ++ testing/_matchers/to_be_falsy.ts | 23 ++ testing/_matchers/to_be_greater.ts | 24 +++ testing/_matchers/to_be_greater_or_equal.ts | 24 +++ testing/_matchers/to_be_instance_of.ts | 19 ++ testing/_matchers/to_be_less.ts | 24 +++ testing/_matchers/to_be_less_or_equal.ts | 24 +++ testing/_matchers/to_be_nan.ts | 20 ++ testing/_matchers/to_be_null.ts | 23 ++ testing/_matchers/to_be_truthy.ts | 23 ++ testing/_matchers/to_be_undefined.ts | 19 ++ testing/_matchers/to_equal.ts | 16 ++ testing/_matchers/to_equal_test.ts | 221 ++++++++++++++++++++ testing/_matchers/to_match.ts | 19 ++ testing/_matchers/to_match_object.ts | 40 ++++ testing/_matchers/to_throw.ts | 38 ++++ testing/_types.ts | 33 +++ testing/expect.ts | 133 ++++++++++++ 23 files changed, 812 insertions(+), 1 deletion(-) create mode 100644 testing/_matchers/mod.ts create mode 100644 testing/_matchers/to__strict_equal.ts create mode 100644 testing/_matchers/to_be.ts create mode 100644 testing/_matchers/to_be_close_to.ts create mode 100644 testing/_matchers/to_be_defined.ts create mode 100644 testing/_matchers/to_be_falsy.ts create mode 100644 testing/_matchers/to_be_greater.ts create mode 100644 testing/_matchers/to_be_greater_or_equal.ts create mode 100644 testing/_matchers/to_be_instance_of.ts create mode 100644 testing/_matchers/to_be_less.ts create mode 100644 testing/_matchers/to_be_less_or_equal.ts create mode 100644 testing/_matchers/to_be_nan.ts create mode 100644 testing/_matchers/to_be_null.ts create mode 100644 testing/_matchers/to_be_truthy.ts create mode 100644 testing/_matchers/to_be_undefined.ts create mode 100644 testing/_matchers/to_equal.ts create mode 100644 testing/_matchers/to_equal_test.ts create mode 100644 testing/_matchers/to_match.ts create mode 100644 testing/_matchers/to_match_object.ts create mode 100644 testing/_matchers/to_throw.ts create mode 100644 testing/_types.ts create mode 100644 testing/expect.ts diff --git a/assert/assert_instance_of.ts b/assert/assert_instance_of.ts index e5a788618d07..cf6fa13779d4 100644 --- a/assert/assert_instance_of.ts +++ b/assert/assert_instance_of.ts @@ -2,7 +2,7 @@ import { AssertionError } from "./assertion_error.ts"; // deno-lint-ignore no-explicit-any -type AnyConstructor = new (...args: any[]) => any; +export type AnyConstructor = new (...args: any[]) => any; type GetConstructorType = T extends // deno-lint-ignore no-explicit-any new (...args: any) => infer C ? C : never; diff --git a/testing/_matchers/mod.ts b/testing/_matchers/mod.ts new file mode 100644 index 000000000000..78c8d5730d08 --- /dev/null +++ b/testing/_matchers/mod.ts @@ -0,0 +1,2 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +export { toEqual } from "./to_equal.ts"; diff --git a/testing/_matchers/to__strict_equal.ts b/testing/_matchers/to__strict_equal.ts new file mode 100644 index 000000000000..db1908872ffb --- /dev/null +++ b/testing/_matchers/to__strict_equal.ts @@ -0,0 +1,22 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { + assertNotStrictEquals, + assertStrictEquals, +} from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals */ +export function toStrictEqual( + context: MatcherContext, + expected: unknown, +): MatchResult { + if (context.isNot) { + return assertNotStrictEquals( + context.value, + expected, + context.customMessage, + ); + } + return assertStrictEquals(context.value, expected, context.customMessage); +} diff --git a/testing/_matchers/to_be.ts b/testing/_matchers/to_be.ts new file mode 100644 index 000000000000..fcc86ce9045c --- /dev/null +++ b/testing/_matchers/to_be.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { + assertNotStrictEquals, + assertStrictEquals, +} from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals and assertNotStrictEquals*/ +export function toBe(context: MatcherContext, expect: unknown): MatchResult { + if (context.isNot) { + return assertNotStrictEquals(context.value, expect, context.customMessage); + } + return assertStrictEquals(context.value, expect, context.customMessage); +} diff --git a/testing/_matchers/to_be_close_to.ts b/testing/_matchers/to_be_close_to.ts new file mode 100644 index 000000000000..77835c6c3722 --- /dev/null +++ b/testing/_matchers/to_be_close_to.ts @@ -0,0 +1,34 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { assertAlmostEquals } from "../../assert/assert_almost_equals.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ +export function toBeCloseTo( + context: MatcherContext, + expected: number, + tolerance = 1e-7, +): MatchResult { + if (context.isNot) { + const actual = Number(context.value); + const delta = Math.abs(expected - actual); + if (delta > tolerance) { + return; + } + const msgSuffix = context.customMessage + ? `: ${context.customMessage}` + : "."; + const f = (n: number) => Number.isInteger(n) ? n : n.toExponential(); + throw new AssertionError( + `Expected actual: "${f(actual)}" to NOT be close to "${f(expected)}": \ + delta "${f(delta)}" is greater than "${f(tolerance)}"${msgSuffix}`, + ); + } + return assertAlmostEquals( + Number(context.value), + expected, + tolerance, + context.customMessage, + ); +} diff --git a/testing/_matchers/to_be_defined.ts b/testing/_matchers/to_be_defined.ts new file mode 100644 index 000000000000..a70629c89cda --- /dev/null +++ b/testing/_matchers/to_be_defined.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { + assertNotStrictEquals, + assertStrictEquals, +} from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ +export function toBeDefined(context: MatcherContext): MatchResult { + if (context.isNot) { + return assertStrictEquals(context.value, undefined, context.customMessage); + } + return assertNotStrictEquals(context.value, undefined, context.customMessage); +} diff --git a/testing/_matchers/to_be_falsy.ts b/testing/_matchers/to_be_falsy.ts new file mode 100644 index 000000000000..1224748248b9 --- /dev/null +++ b/testing/_matchers/to_be_falsy.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertEqual(!!value) */ +export function toBeTruthy( + context: MatcherContext, +): MatchResult { + const isFalsy = !(context.value); + if (context.isNot) { + if (isFalsy) { + throw new AssertionError( + `Expected ${context.value} to NOT be falsy`, + ); + } + } + if (!isFalsy) { + throw new AssertionError( + `Expected ${context.value} to be falsy`, + ); + } +} diff --git a/testing/_matchers/to_be_greater.ts b/testing/_matchers/to_be_greater.ts new file mode 100644 index 000000000000..ff91b5dc3f56 --- /dev/null +++ b/testing/_matchers/to_be_greater.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertEqual */ +export function toBeGreaterThan( + context: MatcherContext, + expected: number, +): MatchResult { + const isGreater = Number(context.value) > Number(expected); + if (context.isNot) { + if (isGreater) { + throw new AssertionError( + `Expected ${context.value} to NOT be greater than ${expected}`, + ); + } + } + if (!isGreater) { + throw new AssertionError( + `Expected ${context.value} to be greater than ${expected}`, + ); + } +} diff --git a/testing/_matchers/to_be_greater_or_equal.ts b/testing/_matchers/to_be_greater_or_equal.ts new file mode 100644 index 000000000000..9fdeb96bd411 --- /dev/null +++ b/testing/_matchers/to_be_greater_or_equal.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertEqual */ +export function toBeGreaterThanOrEqual( + context: MatcherContext, + expected: number, +): MatchResult { + const isGreaterOrEqual = Number(context.value) >= Number(expected); + if (context.isNot) { + if (isGreaterOrEqual) { + throw new AssertionError( + `Expected ${context.value} to NOT be greater than or equal ${expected}`, + ); + } + } + if (!isGreaterOrEqual) { + throw new AssertionError( + `Expected ${context.value} to be greater than or equal ${expected}`, + ); + } +} diff --git a/testing/_matchers/to_be_instance_of.ts b/testing/_matchers/to_be_instance_of.ts new file mode 100644 index 000000000000..9d8beaa73015 --- /dev/null +++ b/testing/_matchers/to_be_instance_of.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { + assertInstanceOf, + assertNotInstanceOf, +} from "../../testing/asserts.ts"; +import { AnyConstructor } from "../../assert/assert_instance_of.ts"; + +/* Similar to assertInstanceOf(value, null) and assertNotInstanceOf(value, null)*/ +export function toBeInstanceOf( + context: MatcherContext, + expected: T, +): MatchResult { + if (context.isNot) { + return assertNotInstanceOf(context.value, expected); + } + return assertInstanceOf(context.value, expected); +} diff --git a/testing/_matchers/to_be_less.ts b/testing/_matchers/to_be_less.ts new file mode 100644 index 000000000000..b0c5e13a04cb --- /dev/null +++ b/testing/_matchers/to_be_less.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertEqual */ +export function toBeLowerThan( + context: MatcherContext, + expected: number, +): MatchResult { + const isLower = Number(context.value) < Number(expected); + if (context.isNot) { + if (isLower) { + throw new AssertionError( + `Expected ${context.value} to NOT be lower than ${expected}`, + ); + } + } + if (!isLower) { + throw new AssertionError( + `Expected ${context.value} to be lower than ${expected}`, + ); + } +} diff --git a/testing/_matchers/to_be_less_or_equal.ts b/testing/_matchers/to_be_less_or_equal.ts new file mode 100644 index 000000000000..8895f725e081 --- /dev/null +++ b/testing/_matchers/to_be_less_or_equal.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertEqual */ +export function toBeLessThanOrEqual( + context: MatcherContext, + expected: number, +): MatchResult { + const isLower = Number(context.value) <= Number(expected); + if (context.isNot) { + if (isLower) { + throw new AssertionError( + `Expected ${context.value} to NOT be lower than or equal ${expected}`, + ); + } + } + if (!isLower) { + throw new AssertionError( + `Expected ${context.value} to be lower than or equal ${expected}`, + ); + } +} diff --git a/testing/_matchers/to_be_nan.ts b/testing/_matchers/to_be_nan.ts new file mode 100644 index 000000000000..4af71375a593 --- /dev/null +++ b/testing/_matchers/to_be_nan.ts @@ -0,0 +1,20 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { assertEquals, assertNotEquals } from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals(isNaN(context.value as number), true) and assertNotStrictEquals(isNaN(context.value as number), true)*/ +export function toBeNan(context: MatcherContext): MatchResult { + if (context.isNot) { + return assertNotEquals( + isNaN(Number(context.value)), + true, + context.customMessage || `Expected ${context.value} to not be NaN`, + ); + } + return assertEquals( + isNaN(Number(context.value)), + true, + context.customMessage || `Expected ${context.value} to be NaN`, + ); +} diff --git a/testing/_matchers/to_be_null.ts b/testing/_matchers/to_be_null.ts new file mode 100644 index 000000000000..47199e2aef4d --- /dev/null +++ b/testing/_matchers/to_be_null.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { + assertNotStrictEquals, + assertStrictEquals, +} from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals(value, null) and assertNotStrictEquals(value, null)*/ +export function toBeNull(context: MatcherContext): MatchResult { + if (context.isNot) { + return assertNotStrictEquals( + context.value as number, + null, + context.customMessage || `Expected ${context.value} to not be null`, + ); + } + return assertStrictEquals( + context.value as number, + null, + context.customMessage || `Expected ${context.value} to be null`, + ); +} diff --git a/testing/_matchers/to_be_truthy.ts b/testing/_matchers/to_be_truthy.ts new file mode 100644 index 000000000000..076e002f1953 --- /dev/null +++ b/testing/_matchers/to_be_truthy.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +/* Similar to assertEqual(!!value) */ +export function toBeTruthy( + context: MatcherContext, +): MatchResult { + const isTruthy = !!(context.value); + if (context.isNot) { + if (isTruthy) { + throw new AssertionError( + `Expected ${context.value} to NOT be truthy`, + ); + } + } + if (!isTruthy) { + throw new AssertionError( + `Expected ${context.value} to be truthy`, + ); + } +} diff --git a/testing/_matchers/to_be_undefined.ts b/testing/_matchers/to_be_undefined.ts new file mode 100644 index 000000000000..7525f9f0f8b1 --- /dev/null +++ b/testing/_matchers/to_be_undefined.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { + assertNotStrictEquals, + assertStrictEquals, +} from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ +export function toBeUndefined(context: MatcherContext): MatchResult { + if (context.isNot) { + return assertNotStrictEquals( + context.value, + undefined, + context.customMessage, + ); + } + return assertStrictEquals(context.value, undefined, context.customMessage); +} diff --git a/testing/_matchers/to_equal.ts b/testing/_matchers/to_equal.ts new file mode 100644 index 000000000000..6befd218e408 --- /dev/null +++ b/testing/_matchers/to_equal.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { assertNotEquals } from "../../assert/assert_not_equals.ts"; +import { assertEquals } from "../../assert/assert_equals.ts"; + +/* Similar to assertEqual */ +export function toEqual( + context: MatcherContext, + expected: unknown, +): MatchResult { + if (context.isNot) { + return assertNotEquals(context.value, expected, context.customMessage); + } + return assertEquals(context.value, expected, context.customMessage); +} diff --git a/testing/_matchers/to_equal_test.ts b/testing/_matchers/to_equal_test.ts new file mode 100644 index 000000000000..d907ee3c5f59 --- /dev/null +++ b/testing/_matchers/to_equal_test.ts @@ -0,0 +1,221 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + bold, + gray, + green, + red, + stripColor, + yellow, +} from "../../fmt/colors.ts"; +import { assertThrows } from "../../assert/assert_throws.ts"; +import { AssertionError } from "../../assert/assertion_error.ts"; + +const createHeader = (): string[] => [ + "", + "", + ` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${ + green( + bold("Expected"), + ) + }`, + "", + "", +]; + +const added: (s: string) => string = (s: string): string => + green(bold(stripColor(s))); +const removed: (s: string) => string = (s: string): string => + red(bold(stripColor(s))); + +import { expect } from "../expect.ts"; + +Deno.test({ + name: "pass case", + fn() { + expect({ a: 10 }).toEqual({ a: 10 }); + expect(true).toEqual(true); + expect(10).toEqual(10); + expect("abc").toEqual("abc"); + expect({ a: 10, b: { c: "1" } }).toEqual({ a: 10, b: { c: "1" } }); + expect(new Date("invalid")).toEqual(new Date("invalid")); + }, +}); + +Deno.test({ + name: "failed with number", + fn() { + assertThrows( + () => expect(1).toEqual(2), + AssertionError, + [ + "Values are not equal.", + ...createHeader(), + removed(`- ${yellow("1")}`), + added(`+ ${yellow("2")}`), + "", + ].join("\n"), + ); + }, +}); + +Deno.test({ + name: "failed with number vs string", + fn() { + assertThrows( + () => expect(1).toEqual("1"), + AssertionError, + [ + "Values are not equal.", + ...createHeader(), + removed(`- ${yellow("1")}`), + added(`+ "1"`), + ].join("\n"), + ); + }, +}); + +Deno.test({ + name: "failed with array", + fn() { + assertThrows( + () => expect([1, "2", 3]).toEqual(["1", "2", 3]), + AssertionError, + ` + [ +- 1, ++ "1", + "2", + 3, + ]`, + ); + }, +}); + +Deno.test({ + name: "failed with object", + fn() { + assertThrows( + () => expect({ a: 1, b: "2", c: 3 }).toEqual({ a: 1, b: 2, c: [3] }), + AssertionError, + ` + { + a: 1, ++ b: 2, ++ c: [ ++ 3, ++ ], +- b: "2", +- c: 3, + }`, + ); + }, +}); + +Deno.test({ + name: "failed with date", + fn() { + assertThrows( + () => + expect(new Date(2019, 0, 3, 4, 20, 1, 10)).toEqual( + new Date(2019, 0, 3, 4, 20, 1, 20), + ), + AssertionError, + [ + "Values are not equal.", + ...createHeader(), + removed(`- ${new Date(2019, 0, 3, 4, 20, 1, 10).toISOString()}`), + added(`+ ${new Date(2019, 0, 3, 4, 20, 1, 20).toISOString()}`), + "", + ].join("\n"), + ); + assertThrows( + () => + expect(new Date("invalid")).toEqual(new Date(2019, 0, 3, 4, 20, 1, 20)), + AssertionError, + [ + "Values are not equal.", + ...createHeader(), + removed(`- ${new Date("invalid")}`), + added(`+ ${new Date(2019, 0, 3, 4, 20, 1, 20).toISOString()}`), + "", + ].join("\n"), + ); + }, +}); + +Deno.test({ + name: "failed with custom msg", + fn() { + assertThrows( + () => expect(1, "CUSTOM MESSAGE").toEqual(2), + AssertionError, + [ + "Values are not equal: CUSTOM MESSAGE", + ...createHeader(), + removed(`- ${yellow("1")}`), + added(`+ ${yellow("2")}`), + "", + ].join("\n"), + ); + }, +}); + +Deno.test( + "expect().toEqual compares objects structurally if one object's constructor is undefined and the other is Object", + () => { + const a = Object.create(null); + a.prop = "test"; + const b = { + prop: "test", + }; + + expect(a).toEqual(b); + expect(b).toEqual(a); + }, +); + +Deno.test("expect().toEqual diff for differently ordered objects", () => { + assertThrows( + () => { + expect({ + aaaaaaaaaaaaaaaaaaaaaaaa: 0, + bbbbbbbbbbbbbbbbbbbbbbbb: 0, + ccccccccccccccccccccccc: 0, + }).toEqual( + { + ccccccccccccccccccccccc: 1, + aaaaaaaaaaaaaaaaaaaaaaaa: 0, + bbbbbbbbbbbbbbbbbbbbbbbb: 0, + }, + ); + }, + AssertionError, + ` + { + aaaaaaaaaaaaaaaaaaaaaaaa: 0, + bbbbbbbbbbbbbbbbbbbbbbbb: 0, +- ccccccccccccccccccccccc: 0, ++ ccccccccccccccccccccccc: 1, + }`, + ); +}); + +Deno.test("expect().toEqual same Set with object keys", () => { + const data = [ + { + id: "_1p7ZED73OF98VbT1SzSkjn", + type: { id: "_ETGENUS" }, + name: "Thuja", + friendlyId: "g-thuja", + }, + { + id: "_567qzghxZmeQ9pw3q09bd3", + type: { id: "_ETGENUS" }, + name: "Pinus", + friendlyId: "g-pinus", + }, + ]; + expect(data).toEqual(data); + expect(new Set(data)).toEqual(new Set(data)); +}); diff --git a/testing/_matchers/to_match.ts b/testing/_matchers/to_match.ts new file mode 100644 index 000000000000..e0e431b4870b --- /dev/null +++ b/testing/_matchers/to_match.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { assertMatch, assertNotMatch } from "../../testing/asserts.ts"; + +/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ +export function toMatch( + context: MatcherContext, + expected: RegExp, +): MatchResult { + if (context.isNot) { + return assertNotMatch( + String(context.value), + expected, + context.customMessage, + ); + } + return assertMatch(String(context.value), expected, context.customMessage); +} diff --git a/testing/_matchers/to_match_object.ts b/testing/_matchers/to_match_object.ts new file mode 100644 index 000000000000..5e4270624d65 --- /dev/null +++ b/testing/_matchers/to_match_object.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError, assertObjectMatch } from "../../testing/asserts.ts"; +import { format } from "../../assert/_format.ts"; + +/* Similar to assertObjectMatch(value, expected)*/ +export function toMatchObject( + context: MatcherContext, + expected: Record, +): MatchResult { + if (context.isNot) { + let objectMatch = false; + try { + assertObjectMatch( + // deno-lint-ignore no-explicit-any + context.value as Record, + expected, + context.customMessage, + ); + objectMatch = true; + const actualString = format(context.value); + const expectedString = format(expected); + throw new AssertionError( + `Expected ${actualString} to NOT match ${expectedString}`, + ); + } catch (e) { + if (objectMatch) { + throw e; + } + return; + } + } + return assertObjectMatch( + // deno-lint-ignore no-explicit-any + context.value as Record, + expected, + context.customMessage, + ); +} diff --git a/testing/_matchers/to_throw.ts b/testing/_matchers/to_throw.ts new file mode 100644 index 000000000000..90795f4d0d2b --- /dev/null +++ b/testing/_matchers/to_throw.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "../_types.ts"; +import { AssertionError, assertIsError } from "../../testing/asserts.ts"; + +/* Similar to assertIsError with value thrown error*/ +export function toThrow( + context: MatcherContext, + // deno-lint-ignore no-explicit-any + expected: new (...args: any[]) => E, +): MatchResult { + if (typeof context.value === "function") { + try { + context.value = context.value(); + } catch (err) { + context.value = err; + } + } + if (context.isNot) { + let isError = false; + try { + assertIsError(context.value, expected, undefined, context.customMessage); + isError = true; + throw new AssertionError(`Expected to NOT throw ${expected}`); + } catch (e) { + if (isError) { + throw e; + } + return; + } + } + return assertIsError( + context.value, + expected, + undefined, + context.customMessage, + ); +} diff --git a/testing/_types.ts b/testing/_types.ts new file mode 100644 index 000000000000..22ec2b5ad50b --- /dev/null +++ b/testing/_types.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +//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. + +export interface MatcherContext { + value: unknown; + isNot: boolean; + customMessage: string | undefined; +} + +export type Matcher = ( + context: MatcherContext, + ...args: unknown[] +) => MatchResult; + +export type Matchers = { + [key: string]: Matcher; +}; +export type MatchResult = void | Promise | boolean; diff --git a/testing/expect.ts b/testing/expect.ts new file mode 100644 index 000000000000..82fb5462430b --- /dev/null +++ b/testing/expect.ts @@ -0,0 +1,133 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright 2019 Allain Lalonde. All rights reserved. ISC License. + +import * as builtInMatchers from "./_matchers/mod.ts"; +import type { Matcher, MatcherContext, Matchers } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { AnyConstructor } from "../assert/assert_instance_of.ts"; + +export interface Expected { + /* Similar to assertEqual */ + toEqual(candidate: unknown): void; + toStrictEqual(candidate: unknown): void; + toBe(candidate: unknown): void; + toBeCloseTo(candidate: number, tolerance?: number): void; + toBeDefined(): void; + toBeFalsy(): void; + toBeGreater(expected: number): void; + toBeGreaterOrEqual(expected: number): void; + toBeInstanceOf(expected: T): void; + toBeLess(expected: number): void; + toBeLessOrEqual(expected: number): void; + toBeNan(): void; + toBeNull(): void; + toBeTruthy(): void; + toBeUndefined(): void; + toMatch(expected: RegExp): void; + toMatchObject(expected: Record): void; + // deno-lint-ignore no-explicit-any + toThrow(expected: new (...args: any[]) => E): void; + not: Expected; + resolves: Async; + rejects: Async; +} + +const matchers: Record = { + ...builtInMatchers, +}; + +export function expect(value: unknown, customMessage?: string): Expected { + let isNot = false; + let isPromised = false; + const self: Expected = new Proxy( + {}, + { + get(_, name) { + if (name === "not") { + isNot = !isNot; + return self; + } + + if (name === "resolves") { + if (!isPromiseLike(value)) { + throw new AssertionError("expected value must be Promiselike"); + } + + isPromised = true; + return self; + } + + if (name === "rejects") { + if (!isPromiseLike(value)) { + throw new AssertionError("expected value must be a PromiseLike"); + } + + 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 string]; + if (!matcher) { + throw new TypeError( + typeof name === "string" + ? `matcher not found: ${name}` + : "matcher not found", + ); + } + + return (...args: unknown[]) => { + function applyMatcher(value: unknown, args: unknown[]) { + const context: MatcherContext = { + value, + isNot: false, + customMessage, + }; + if (isNot) { + context.isNot = true; + } + matcher(context, ...args); + } + + return isPromised + ? (value as Promise).then((value: unknown) => + applyMatcher(value, args) + ) + : applyMatcher(value, args); + }; + }, + }, + ); + + return self; +} + +// a helper type to match any function. Used so that we only convert functions +// to return a promise and not properties. +type Fn = (...args: unknown[]) => unknown; + +// converts all the methods in an interface to be async functions +export type Async = { + [K in keyof T]: T[K] extends Fn + ? (...args: Parameters) => Promise> + : T[K]; +}; + +export function addMatchers(newMatchers: Matchers): void { + Object.assign(matchers, newMatchers); +} + +function isPromiseLike(value: unknown): value is PromiseLike { + if (value == null) { + return false; + } else { + return typeof ((value as Record).then) === "function"; + } +} From b872b6dd582ee045fa69db8f9d8f4992ed363428 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 16 Nov 2023 12:41:07 +0900 Subject: [PATCH 02/17] move to std/expect --- assert/assert_instance_of.ts | 2 +- .../_matchers/to_be.ts => expect/_to_be.ts | 8 ++- .../_to_be_close_to.ts | 7 ++- .../_to_be_defined.ts | 9 ++-- .../to_be_falsy.ts => expect/_to_be_falsy.ts | 7 ++- .../_to_be_greater_than.ts | 4 +- .../_to_be_greater_than_or_equal.ts | 4 +- .../_to_be_instance_of.ts | 9 ++-- .../_to_be_less_than.ts | 6 +-- .../_to_be_less_than_or_equal.ts | 4 +- .../to_be_nan.ts => expect/_to_be_nan.ts | 7 +-- .../to_be_null.ts => expect/_to_be_null.ts | 8 ++- .../_to_be_truthy.ts | 4 +- .../_to_be_undefined.ts | 8 ++- .../to_equal.ts => expect/_to_equal.ts | 6 +-- .../_to_equal_test.ts | 15 +++--- .../to_match.ts => expect/_to_match.ts | 5 +- .../_to_match_object.ts | 7 +-- .../_to_strict_equal.ts | 8 ++- .../to_throw.ts => expect/_to_throw.ts | 5 +- expect/_types.ts | 20 ++++++++ {testing => expect}/expect.ts | 49 ++++++++++++++++--- expect/mod.ts | 18 +++++++ testing/_matchers/mod.ts | 2 - testing/_types.ts | 33 ------------- 25 files changed, 141 insertions(+), 114 deletions(-) rename testing/_matchers/to_be.ts => expect/_to_be.ts (65%) rename testing/_matchers/to_be_close_to.ts => expect/_to_be_close_to.ts (73%) rename testing/_matchers/to_be_defined.ts => expect/_to_be_defined.ts (57%) rename testing/_matchers/to_be_falsy.ts => expect/_to_be_falsy.ts (68%) rename testing/_matchers/to_be_greater.ts => expect/_to_be_greater_than.ts (81%) rename testing/_matchers/to_be_greater_or_equal.ts => expect/_to_be_greater_than_or_equal.ts (82%) rename testing/_matchers/to_be_instance_of.ts => expect/_to_be_instance_of.ts (65%) rename testing/_matchers/to_be_less.ts => expect/_to_be_less_than.ts (76%) rename testing/_matchers/to_be_less_or_equal.ts => expect/_to_be_less_than_or_equal.ts (82%) rename testing/_matchers/to_be_nan.ts => expect/_to_be_nan.ts (68%) rename testing/_matchers/to_be_null.ts => expect/_to_be_null.ts (72%) rename testing/_matchers/to_be_truthy.ts => expect/_to_be_truthy.ts (79%) rename testing/_matchers/to_be_undefined.ts => expect/_to_be_undefined.ts (68%) rename testing/_matchers/to_equal.ts => expect/_to_equal.ts (66%) rename testing/_matchers/to_equal_test.ts => expect/_to_equal_test.ts (94%) rename testing/_matchers/to_match.ts => expect/_to_match.ts (72%) rename testing/_matchers/to_match_object.ts => expect/_to_match_object.ts (80%) rename testing/_matchers/to__strict_equal.ts => expect/_to_strict_equal.ts (66%) rename testing/_matchers/to_throw.ts => expect/_to_throw.ts (82%) create mode 100644 expect/_types.ts rename {testing => expect}/expect.ts (73%) create mode 100644 expect/mod.ts delete mode 100644 testing/_matchers/mod.ts delete mode 100644 testing/_types.ts diff --git a/assert/assert_instance_of.ts b/assert/assert_instance_of.ts index cf6fa13779d4..e5a788618d07 100644 --- a/assert/assert_instance_of.ts +++ b/assert/assert_instance_of.ts @@ -2,7 +2,7 @@ import { AssertionError } from "./assertion_error.ts"; // deno-lint-ignore no-explicit-any -export type AnyConstructor = new (...args: any[]) => any; +type AnyConstructor = new (...args: any[]) => any; type GetConstructorType = T extends // deno-lint-ignore no-explicit-any new (...args: any) => infer C ? C : never; diff --git a/testing/_matchers/to_be.ts b/expect/_to_be.ts similarity index 65% rename from testing/_matchers/to_be.ts rename to expect/_to_be.ts index fcc86ce9045c..f1ab0c678630 100644 --- a/testing/_matchers/to_be.ts +++ b/expect/_to_be.ts @@ -1,10 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { - assertNotStrictEquals, - assertStrictEquals, -} from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; +import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; /* Similar to assertStrictEquals and assertNotStrictEquals*/ export function toBe(context: MatcherContext, expect: unknown): MatchResult { diff --git a/testing/_matchers/to_be_close_to.ts b/expect/_to_be_close_to.ts similarity index 73% rename from testing/_matchers/to_be_close_to.ts rename to expect/_to_be_close_to.ts index 77835c6c3722..239ee625d1bb 100644 --- a/testing/_matchers/to_be_close_to.ts +++ b/expect/_to_be_close_to.ts @@ -1,10 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { assertAlmostEquals } from "../../assert/assert_almost_equals.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertAlmostEquals } from "../assert/assert_almost_equals.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ export function toBeCloseTo( context: MatcherContext, expected: number, diff --git a/testing/_matchers/to_be_defined.ts b/expect/_to_be_defined.ts similarity index 57% rename from testing/_matchers/to_be_defined.ts rename to expect/_to_be_defined.ts index a70629c89cda..3440c0e1517d 100644 --- a/testing/_matchers/to_be_defined.ts +++ b/expect/_to_be_defined.ts @@ -1,12 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { - assertNotStrictEquals, - assertStrictEquals, -} from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; +import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; -/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ export function toBeDefined(context: MatcherContext): MatchResult { if (context.isNot) { return assertStrictEquals(context.value, undefined, context.customMessage); diff --git a/testing/_matchers/to_be_falsy.ts b/expect/_to_be_falsy.ts similarity index 68% rename from testing/_matchers/to_be_falsy.ts rename to expect/_to_be_falsy.ts index 1224748248b9..1f1314da0418 100644 --- a/testing/_matchers/to_be_falsy.ts +++ b/expect/_to_be_falsy.ts @@ -1,10 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertEqual(!!value) */ -export function toBeTruthy( +export function toBeFalsy( context: MatcherContext, ): MatchResult { const isFalsy = !(context.value); diff --git a/testing/_matchers/to_be_greater.ts b/expect/_to_be_greater_than.ts similarity index 81% rename from testing/_matchers/to_be_greater.ts rename to expect/_to_be_greater_than.ts index ff91b5dc3f56..1dc1ffa3f834 100644 --- a/testing/_matchers/to_be_greater.ts +++ b/expect/_to_be_greater_than.ts @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; /* Similar to assertEqual */ export function toBeGreaterThan( diff --git a/testing/_matchers/to_be_greater_or_equal.ts b/expect/_to_be_greater_than_or_equal.ts similarity index 82% rename from testing/_matchers/to_be_greater_or_equal.ts rename to expect/_to_be_greater_than_or_equal.ts index 9fdeb96bd411..8e4e00ac1b15 100644 --- a/testing/_matchers/to_be_greater_or_equal.ts +++ b/expect/_to_be_greater_than_or_equal.ts @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; /* Similar to assertEqual */ export function toBeGreaterThanOrEqual( diff --git a/testing/_matchers/to_be_instance_of.ts b/expect/_to_be_instance_of.ts similarity index 65% rename from testing/_matchers/to_be_instance_of.ts rename to expect/_to_be_instance_of.ts index 9d8beaa73015..03569dc5c26d 100644 --- a/testing/_matchers/to_be_instance_of.ts +++ b/expect/_to_be_instance_of.ts @@ -1,11 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { - assertInstanceOf, - assertNotInstanceOf, -} from "../../testing/asserts.ts"; -import { AnyConstructor } from "../../assert/assert_instance_of.ts"; +import { AnyConstructor, MatcherContext, MatchResult } from "./_types.ts"; +import { assertInstanceOf } from "../assert/assert_instance_of.ts"; +import { assertNotInstanceOf } from "../assert/assert_not_instance_of.ts"; /* Similar to assertInstanceOf(value, null) and assertNotInstanceOf(value, null)*/ export function toBeInstanceOf( diff --git a/testing/_matchers/to_be_less.ts b/expect/_to_be_less_than.ts similarity index 76% rename from testing/_matchers/to_be_less.ts rename to expect/_to_be_less_than.ts index b0c5e13a04cb..6d615e0b34e9 100644 --- a/testing/_matchers/to_be_less.ts +++ b/expect/_to_be_less_than.ts @@ -1,10 +1,10 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; /* Similar to assertEqual */ -export function toBeLowerThan( +export function toBeLessThan( context: MatcherContext, expected: number, ): MatchResult { diff --git a/testing/_matchers/to_be_less_or_equal.ts b/expect/_to_be_less_than_or_equal.ts similarity index 82% rename from testing/_matchers/to_be_less_or_equal.ts rename to expect/_to_be_less_than_or_equal.ts index 8895f725e081..98a90dcaaea3 100644 --- a/testing/_matchers/to_be_less_or_equal.ts +++ b/expect/_to_be_less_than_or_equal.ts @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; /* Similar to assertEqual */ export function toBeLessThanOrEqual( diff --git a/testing/_matchers/to_be_nan.ts b/expect/_to_be_nan.ts similarity index 68% rename from testing/_matchers/to_be_nan.ts rename to expect/_to_be_nan.ts index 4af71375a593..59e55a000169 100644 --- a/testing/_matchers/to_be_nan.ts +++ b/expect/_to_be_nan.ts @@ -1,10 +1,11 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { assertEquals, assertNotEquals } from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotEquals } from "../assert/assert_not_equals.ts"; +import { assertEquals } from "../assert/assert_equals.ts"; /* Similar to assertStrictEquals(isNaN(context.value as number), true) and assertNotStrictEquals(isNaN(context.value as number), true)*/ -export function toBeNan(context: MatcherContext): MatchResult { +export function toBeNaN(context: MatcherContext): MatchResult { if (context.isNot) { return assertNotEquals( isNaN(Number(context.value)), diff --git a/testing/_matchers/to_be_null.ts b/expect/_to_be_null.ts similarity index 72% rename from testing/_matchers/to_be_null.ts rename to expect/_to_be_null.ts index 47199e2aef4d..3c01be88af28 100644 --- a/testing/_matchers/to_be_null.ts +++ b/expect/_to_be_null.ts @@ -1,10 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { - assertNotStrictEquals, - assertStrictEquals, -} from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; +import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; /* Similar to assertStrictEquals(value, null) and assertNotStrictEquals(value, null)*/ export function toBeNull(context: MatcherContext): MatchResult { diff --git a/testing/_matchers/to_be_truthy.ts b/expect/_to_be_truthy.ts similarity index 79% rename from testing/_matchers/to_be_truthy.ts rename to expect/_to_be_truthy.ts index 076e002f1953..a535f3e7a22d 100644 --- a/testing/_matchers/to_be_truthy.ts +++ b/expect/_to_be_truthy.ts @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; /* Similar to assertEqual(!!value) */ export function toBeTruthy( diff --git a/testing/_matchers/to_be_undefined.ts b/expect/_to_be_undefined.ts similarity index 68% rename from testing/_matchers/to_be_undefined.ts rename to expect/_to_be_undefined.ts index 7525f9f0f8b1..c3da1b3599ee 100644 --- a/testing/_matchers/to_be_undefined.ts +++ b/expect/_to_be_undefined.ts @@ -1,10 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { - assertNotStrictEquals, - assertStrictEquals, -} from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; +import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; /* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ export function toBeUndefined(context: MatcherContext): MatchResult { diff --git a/testing/_matchers/to_equal.ts b/expect/_to_equal.ts similarity index 66% rename from testing/_matchers/to_equal.ts rename to expect/_to_equal.ts index 6befd218e408..73c86912d615 100644 --- a/testing/_matchers/to_equal.ts +++ b/expect/_to_equal.ts @@ -1,8 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { assertNotEquals } from "../../assert/assert_not_equals.ts"; -import { assertEquals } from "../../assert/assert_equals.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotEquals } from "../assert/assert_not_equals.ts"; +import { assertEquals } from "../assert/assert_equals.ts"; /* Similar to assertEqual */ export function toEqual( diff --git a/testing/_matchers/to_equal_test.ts b/expect/_to_equal_test.ts similarity index 94% rename from testing/_matchers/to_equal_test.ts rename to expect/_to_equal_test.ts index d907ee3c5f59..438f23576eed 100644 --- a/testing/_matchers/to_equal_test.ts +++ b/expect/_to_equal_test.ts @@ -5,11 +5,12 @@ import { gray, green, red, - stripColor, + stripAnsiCode, yellow, -} from "../../fmt/colors.ts"; -import { assertThrows } from "../../assert/assert_throws.ts"; -import { AssertionError } from "../../assert/assertion_error.ts"; +} from "../fmt/colors.ts"; +import { assertThrows } from "../assert/assert_throws.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { expect } from "./expect.ts"; const createHeader = (): string[] => [ "", @@ -24,11 +25,9 @@ const createHeader = (): string[] => [ ]; const added: (s: string) => string = (s: string): string => - green(bold(stripColor(s))); + green(bold(stripAnsiCode(s))); const removed: (s: string) => string = (s: string): string => - red(bold(stripColor(s))); - -import { expect } from "../expect.ts"; + red(bold(stripAnsiCode(s))); Deno.test({ name: "pass case", diff --git a/testing/_matchers/to_match.ts b/expect/_to_match.ts similarity index 72% rename from testing/_matchers/to_match.ts rename to expect/_to_match.ts index e0e431b4870b..29bd3a14a824 100644 --- a/testing/_matchers/to_match.ts +++ b/expect/_to_match.ts @@ -1,7 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { assertMatch, assertNotMatch } from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertMatch } from "../assert/assert_match.ts"; +import { assertNotMatch } from "../assert/assert_not_match.ts"; /* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ export function toMatch( diff --git a/testing/_matchers/to_match_object.ts b/expect/_to_match_object.ts similarity index 80% rename from testing/_matchers/to_match_object.ts rename to expect/_to_match_object.ts index 5e4270624d65..eb35bb6582a4 100644 --- a/testing/_matchers/to_match_object.ts +++ b/expect/_to_match_object.ts @@ -1,8 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError, assertObjectMatch } from "../../testing/asserts.ts"; -import { format } from "../../assert/_format.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { assertObjectMatch } from "../assert/assert_object_match.ts"; +import { format } from "../assert/_format.ts"; /* Similar to assertObjectMatch(value, expected)*/ export function toMatchObject( diff --git a/testing/_matchers/to__strict_equal.ts b/expect/_to_strict_equal.ts similarity index 66% rename from testing/_matchers/to__strict_equal.ts rename to expect/_to_strict_equal.ts index db1908872ffb..d5f99bdefca3 100644 --- a/testing/_matchers/to__strict_equal.ts +++ b/expect/_to_strict_equal.ts @@ -1,10 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { - assertNotStrictEquals, - assertStrictEquals, -} from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; +import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; /* Similar to assertStrictEquals */ export function toStrictEqual( diff --git a/testing/_matchers/to_throw.ts b/expect/_to_throw.ts similarity index 82% rename from testing/_matchers/to_throw.ts rename to expect/_to_throw.ts index 90795f4d0d2b..0d39ce8151ce 100644 --- a/testing/_matchers/to_throw.ts +++ b/expect/_to_throw.ts @@ -1,7 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { MatcherContext, MatchResult } from "../_types.ts"; -import { AssertionError, assertIsError } from "../../testing/asserts.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { assertIsError } from "../assert/assert_is_error.ts"; /* Similar to assertIsError with value thrown error*/ export function toThrow( diff --git a/expect/_types.ts b/expect/_types.ts new file mode 100644 index 000000000000..5862c2913881 --- /dev/null +++ b/expect/_types.ts @@ -0,0 +1,20 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export interface MatcherContext { + value: unknown; + isNot: boolean; + customMessage: string | undefined; +} + +export type Matcher = ( + context: MatcherContext, + // deno-lint-ignore no-explicit-any + ...args: any[] +) => MatchResult; + +export type Matchers = { + [key: string]: Matcher; +}; +export type MatchResult = void | Promise | boolean; +// deno-lint-ignore no-explicit-any +export type AnyConstructor = new (...args: any[]) => any; diff --git a/testing/expect.ts b/expect/expect.ts similarity index 73% rename from testing/expect.ts rename to expect/expect.ts index 82fb5462430b..004f01d02494 100644 --- a/testing/expect.ts +++ b/expect/expect.ts @@ -1,13 +1,33 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2019 Allain Lalonde. All rights reserved. ISC License. -import * as builtInMatchers from "./_matchers/mod.ts"; -import type { Matcher, MatcherContext, Matchers } from "./_types.ts"; +import type { + AnyConstructor, + Matcher, + MatcherContext, + Matchers, +} from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -import { AnyConstructor } from "../assert/assert_instance_of.ts"; +import { toBeCloseTo } from "./_to_be_close_to.ts"; +import { toBeDefined } from "./_to_be_defined.ts"; +import { toBeFalsy } from "./_to_be_falsy.ts"; +import { toBeGreaterThanOrEqual } from "./_to_be_greater_than_or_equal.ts"; +import { toBeGreaterThan } from "./_to_be_greater_than.ts"; +import { toBeInstanceOf } from "./_to_be_instance_of.ts"; +import { toBeLessThanOrEqual } from "./_to_be_less_than_or_equal.ts"; +import { toBeLessThan } from "./_to_be_less_than.ts"; +import { toBeNaN } from "./_to_be_nan.ts"; +import { toBeNull } from "./_to_be_null.ts"; +import { toBeTruthy } from "./_to_be_truthy.ts"; +import { toBeUndefined } from "./_to_be_undefined.ts"; +import { toBe } from "./_to_be.ts"; +import { toEqual } from "./_to_equal.ts"; +import { toMatchObject } from "./_to_match_object.ts"; +import { toMatch } from "./_to_match.ts"; +import { toStrictEqual } from "./_to_strict_equal.ts"; +import { toThrow } from "./_to_throw.ts"; export interface Expected { - /* Similar to assertEqual */ toEqual(candidate: unknown): void; toStrictEqual(candidate: unknown): void; toBe(candidate: unknown): void; @@ -19,7 +39,7 @@ export interface Expected { toBeInstanceOf(expected: T): void; toBeLess(expected: number): void; toBeLessOrEqual(expected: number): void; - toBeNan(): void; + toBeNaN(): void; toBeNull(): void; toBeTruthy(): void; toBeUndefined(): void; @@ -33,7 +53,24 @@ export interface Expected { } const matchers: Record = { - ...builtInMatchers, + toBeCloseTo, + toBeDefined, + toBeFalsy, + toBeGreaterThanOrEqual, + toBeGreaterThan, + toBeInstanceOf, + toBeLessThanOrEqual, + toBeLessThan, + toBeNaN, + toBeNull, + toBeTruthy, + toBeUndefined, + toBe, + toEqual, + toMatchObject, + toMatch, + toStrictEqual, + toThrow, }; export function expect(value: unknown, customMessage?: string): Expected { diff --git a/expect/mod.ts b/expect/mod.ts new file mode 100644 index 000000000000..882bc2dc46b0 --- /dev/null +++ b/expect/mod.ts @@ -0,0 +1,18 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * @module + * + * This module provides jest compatible expect assertion functionality. + * + * @example + * ```ts + * import { expect } from "https://deno.land/std@$STD_VERSION/expect/mod.ts"; + * + * const x = 6 * 7; + * expect(x).toEqual(42); + * ``` + */ + +export { expect } from "./expect.ts"; diff --git a/testing/_matchers/mod.ts b/testing/_matchers/mod.ts deleted file mode 100644 index 78c8d5730d08..000000000000 --- a/testing/_matchers/mod.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -export { toEqual } from "./to_equal.ts"; diff --git a/testing/_types.ts b/testing/_types.ts deleted file mode 100644 index 22ec2b5ad50b..000000000000 --- a/testing/_types.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -//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. - -export interface MatcherContext { - value: unknown; - isNot: boolean; - customMessage: string | undefined; -} - -export type Matcher = ( - context: MatcherContext, - ...args: unknown[] -) => MatchResult; - -export type Matchers = { - [key: string]: Matcher; -}; -export type MatchResult = void | Promise | boolean; From e32a46597720110eb08629167eae7c6edb39c043 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 16 Nov 2023 15:48:03 +0900 Subject: [PATCH 03/17] add docs, add placeholders, add supported APIs --- expect/_to_have_been_called.ts | 6 ++ expect/_to_have_been_called_times.ts | 9 +++ expect/_to_have_been_called_with.ts | 9 +++ expect/_to_have_been_last_called_with.ts | 9 +++ expect/_to_have_been_nth_called_with.ts | 10 ++++ expect/_to_have_last_returned_with.ts | 9 +++ expect/_to_have_length.ts | 9 +++ expect/_to_have_nth_returned_with.ts | 10 ++++ expect/_to_have_property.ts | 10 ++++ expect/_to_have_returned.ts | 6 ++ expect/_to_have_returned_times.ts | 9 +++ expect/_to_have_returned_with.ts | 9 +++ expect/expect.ts | 39 ++++++++++++- expect/fn.ts | 61 +++++++++++++++++++++ expect/mod.ts | 70 ++++++++++++++++++++++++ 15 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 expect/_to_have_been_called.ts create mode 100644 expect/_to_have_been_called_times.ts create mode 100644 expect/_to_have_been_called_with.ts create mode 100644 expect/_to_have_been_last_called_with.ts create mode 100644 expect/_to_have_been_nth_called_with.ts create mode 100644 expect/_to_have_last_returned_with.ts create mode 100644 expect/_to_have_length.ts create mode 100644 expect/_to_have_nth_returned_with.ts create mode 100644 expect/_to_have_property.ts create mode 100644 expect/_to_have_returned.ts create mode 100644 expect/_to_have_returned_times.ts create mode 100644 expect/_to_have_returned_with.ts create mode 100644 expect/fn.ts diff --git a/expect/_to_have_been_called.ts b/expect/_to_have_been_called.ts new file mode 100644 index 000000000000..25081754c136 --- /dev/null +++ b/expect/_to_have_been_called.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveBeenCalled(context: MatcherContext): MatchResult { +} diff --git a/expect/_to_have_been_called_times.ts b/expect/_to_have_been_called_times.ts new file mode 100644 index 000000000000..c60818be382c --- /dev/null +++ b/expect/_to_have_been_called_times.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveBeenCalledTimes( + context: MatcherContext, + expected: number, +): MatchResult { +} diff --git a/expect/_to_have_been_called_with.ts b/expect/_to_have_been_called_with.ts new file mode 100644 index 000000000000..fd1a3a43c534 --- /dev/null +++ b/expect/_to_have_been_called_with.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveBeenCalledWith( + context: MatcherContext, + ...expected: unknown[] +): MatchResult { +} diff --git a/expect/_to_have_been_last_called_with.ts b/expect/_to_have_been_last_called_with.ts new file mode 100644 index 000000000000..5ed7a81904c6 --- /dev/null +++ b/expect/_to_have_been_last_called_with.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveBeenLastCalledWith( + context: MatcherContext, + ...expected: unknown[] +): MatchResult { +} diff --git a/expect/_to_have_been_nth_called_with.ts b/expect/_to_have_been_nth_called_with.ts new file mode 100644 index 000000000000..aaa5366f38ca --- /dev/null +++ b/expect/_to_have_been_nth_called_with.ts @@ -0,0 +1,10 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveBeenNthCalledWith( + _context: MatcherContext, + nth: number, + ...expected: unknown[] +): MatchResult { +} diff --git a/expect/_to_have_last_returned_with.ts b/expect/_to_have_last_returned_with.ts new file mode 100644 index 000000000000..5bda66481d12 --- /dev/null +++ b/expect/_to_have_last_returned_with.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveLastReturnedWith( + context: MatcherContext, + expected: unknown, +): MatchResult { +} diff --git a/expect/_to_have_length.ts b/expect/_to_have_length.ts new file mode 100644 index 000000000000..d1400c217322 --- /dev/null +++ b/expect/_to_have_length.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveLength( + context: MatcherContext, + expected: number, +): MatchResult { +} diff --git a/expect/_to_have_nth_returned_with.ts b/expect/_to_have_nth_returned_with.ts new file mode 100644 index 000000000000..aa62b4a924e3 --- /dev/null +++ b/expect/_to_have_nth_returned_with.ts @@ -0,0 +1,10 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveNthReturnedWith( + context: MatcherContext, + nth: number, + expected: unknown, +): MatchResult { +} diff --git a/expect/_to_have_property.ts b/expect/_to_have_property.ts new file mode 100644 index 000000000000..c0fc0d8b95c3 --- /dev/null +++ b/expect/_to_have_property.ts @@ -0,0 +1,10 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveProperty( + context: MatcherContext, + propName: string | symbol, + value?: unknown, +): MatchResult { +} diff --git a/expect/_to_have_returned.ts b/expect/_to_have_returned.ts new file mode 100644 index 000000000000..14281b0902e9 --- /dev/null +++ b/expect/_to_have_returned.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveReturned(context: MatcherContext): MatchResult { +} diff --git a/expect/_to_have_returned_times.ts b/expect/_to_have_returned_times.ts new file mode 100644 index 000000000000..878ade5809c4 --- /dev/null +++ b/expect/_to_have_returned_times.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveReturnedTimes( + context: MatcherContext, + expected: number, +): MatchResult { +} diff --git a/expect/_to_have_returned_with.ts b/expect/_to_have_returned_with.ts new file mode 100644 index 000000000000..878aca67cfa8 --- /dev/null +++ b/expect/_to_have_returned_with.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toHaveReturnedWith( + context: MatcherContext, + expected: unknown, +): MatchResult { +} diff --git a/expect/expect.ts b/expect/expect.ts index 004f01d02494..37f39502016f 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -22,15 +22,23 @@ import { toBeTruthy } from "./_to_be_truthy.ts"; import { toBeUndefined } from "./_to_be_undefined.ts"; import { toBe } from "./_to_be.ts"; import { toEqual } from "./_to_equal.ts"; +import { toHaveBeenCalledTimes } from "./_to_have_been_called_times.ts"; +import { toHaveBeenCalledWith } from "./_to_have_been_called_with.ts"; +import { toHaveBeenCalled } from "./_to_have_been_called.ts"; +import { toHaveBeenLastCalledWith } from "./_to_have_been_last_called_with.ts"; +import { toHaveBeenNthCalledWith } from "./_to_have_been_nth_called_with.ts"; +import { toHaveLength } from "./_to_have_length.ts"; +import { toHaveNthReturnedWith } from "./_to_have_nth_returned_with.ts"; +import { toHaveProperty } from "./_to_have_property.ts"; +import { toHaveReturnedTimes } from "./_to_have_returned_times.ts"; +import { toHaveReturnedWith } from "./_to_have_returned_with.ts"; +import { toHaveReturned } from "./_to_have_returned.ts"; import { toMatchObject } from "./_to_match_object.ts"; import { toMatch } from "./_to_match.ts"; import { toStrictEqual } from "./_to_strict_equal.ts"; import { toThrow } from "./_to_throw.ts"; export interface Expected { - toEqual(candidate: unknown): void; - toStrictEqual(candidate: unknown): void; - toBe(candidate: unknown): void; toBeCloseTo(candidate: number, tolerance?: number): void; toBeDefined(): void; toBeFalsy(): void; @@ -43,8 +51,22 @@ export interface Expected { toBeNull(): void; toBeTruthy(): void; toBeUndefined(): void; + toBe(candidate: unknown): void; + toEqual(candidate: unknown): void; + toHaveBeenCalledTimes(expected: number): void; + toHaveBeenCalledWith(...expected: unknown[]): void; + toHaveBeenCalled(): void; + toHaveBeenLastCalledWith(...expected: unknown[]): void; + toHaveBeenNthCalledWith(nth: number, ...expected: unknown[]): void; + toHaveLength(expected: number): void; + toHaveNthReturnedWith(nth: number, expected: unknown): void; + toHaveProperty(propName: string | symbol, value?: unknown): void; + toHaveReturnedTimes(expected: number): void; + toHaveReturnedWith(expected: unknown): void; + toHaveReturned(): void; toMatch(expected: RegExp): void; toMatchObject(expected: Record): void; + toStrictEqual(candidate: unknown): void; // deno-lint-ignore no-explicit-any toThrow(expected: new (...args: any[]) => E): void; not: Expected; @@ -67,6 +89,17 @@ const matchers: Record = { toBeUndefined, toBe, toEqual, + toHaveBeenCalledTimes, + toHaveBeenCalledWith, + toHaveBeenCalled, + toHaveBeenLastCalledWith, + toHaveBeenNthCalledWith, + toHaveLength, + toHaveNthReturnedWith, + toHaveProperty, + toHaveReturnedTimes, + toHaveReturnedWith, + toHaveReturned, toMatchObject, toMatch, toStrictEqual, diff --git a/expect/fn.ts b/expect/fn.ts new file mode 100644 index 000000000000..fc763d942e06 --- /dev/null +++ b/expect/fn.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright 2019 Allain Lalonde. All rights reserved. ISC License. +// deno-lint-ignore-file no-explicit-any ban-types + +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]; +} diff --git a/expect/mod.ts b/expect/mod.ts index 882bc2dc46b0..f13c1018720a 100644 --- a/expect/mod.ts +++ b/expect/mod.ts @@ -1,4 +1,5 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright 2019 Allain Lalonde. All rights reserved. ISC License. // This module is browser compatible. /** @@ -12,7 +13,76 @@ * * const x = 6 * 7; * expect(x).toEqual(42); + * expect(x).not.toEqual(0); + * + * await expect(Promise.resolve(x)).resolves.toEqual(42); * ``` + * + * Currently this module supports the following matchers: + * - `toBe` + * - `toEqual` + * - `toStrictEqual` + * - `toMatch` + * - `toMatchObject` + * - `toBeDefined` + * - `toBeUndefined` + * - `toBeNull` + * - `toBeNaN` + * - `toBeTruthy` + * - `toBeFalsy` + * - `toContain` + * - `toContainEqual` + * - `toHaveLength` + * - `toBeGreaterThan` + * - `toBeGreaterThanOrEqual` + * - `toBeLessThan` + * - `toBeLessThanOrEqual` + * - `toBeCloseTo` + * - `toBeInstanceOf` + * - `toThrow` + * - `toHaveProperty` + * - `toHaveLength` + * + * Also this module supports the following mock related matchers: + * - `toHaveBeenCalled` + * - `toHaveBeenCalledTimes` + * - `toHaveBeenCalledWith` + * - `toHaveBeenLastCalledWith` + * - `toHaveBeenNthCalledWith` + * - `toHaveReturned` + * - `toHaveReturnedTimes` + * - `toHaveReturnedWith` + * - `toHaveLastReturnedWith` + * - `toHaveNthReturnedWith` + * + * The following matchers are not supported yet: + * - `toMatchSnapShot` + * - `toMatchInlineSnapShot` + * - `toThrowErrorMatchingSnapShot` + * - `toThrowErrorMatchingInlineSnapShot` + * + * The following asymmetric matchers are not supported yet: + * - `expect.anything` + * - `expect.any` + * - `expect.arrayContaining` + * - `expect.not.arrayContaining` + * - `expect.closedTo` + * - `expect.objectContaining` + * - `expect.not.objectContaining` + * - `expect.stringContaining` + * - `expect.not.stringContaining` + * - `expect.stringMatching` + * - `expect.not.stringMatching` + * + * The following uitlities are not supported yet: + * - `expect.assertions` + * - `expect.hasAssertions` + * - `expect.addEqualityTester` + * - `expect.addSnapshotSerializer` + * - `expect.extend` + * + * This module is largely inspired by [x/expect](https://github.com/allain/expect) module by Allain Lalonde. */ export { expect } from "./expect.ts"; +export { fn } from "./fn.ts"; From e3e116eb13e25d5913ec70536b61868654342117 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 16 Nov 2023 16:29:48 +0900 Subject: [PATCH 04/17] add test cases wip --- expect/_to_be.ts | 1 - expect/_to_be_close_to.ts | 3 ++- expect/_to_be_close_to_test.ts | 9 ++++++++ expect/_to_be_defined_test.ts | 17 +++++++++++++++ expect/_to_be_falsy_test.ts | 6 ++++++ expect/_to_be_greater_than.ts | 1 - expect/_to_be_greater_than_or_equal.ts | 1 - expect/_to_be_greater_than_or_equal_test.ts | 6 ++++++ expect/_to_be_greater_than_test.ts | 6 ++++++ expect/_to_be_instance_of.ts | 1 - expect/_to_be_instance_of_test.ts | 6 ++++++ expect/_to_be_less_than.ts | 1 - expect/_to_be_less_than_or_equal.ts | 1 - expect/_to_be_less_than_or_equal_test.ts | 6 ++++++ expect/_to_be_less_than_test.ts | 6 ++++++ expect/_to_be_nan.ts | 1 - expect/_to_be_nan_test.ts | 6 ++++++ expect/_to_be_null.ts | 1 - expect/_to_be_null_test.ts | 6 ++++++ expect/_to_be_test.ts | 6 ++++++ expect/_to_be_truthy.ts | 1 - expect/_to_be_truthy_test.ts | 6 ++++++ expect/_to_be_undefined.ts | 1 - expect/_to_be_undefined_test.ts | 6 ++++++ expect/_to_equal.ts | 1 - expect/_to_have_been_called_test.ts | 5 +++++ expect/_to_have_been_called_times_test.ts | 6 ++++++ expect/_to_have_been_called_with_test.ts | 6 ++++++ expect/_to_have_been_last_called_with_test.ts | 6 ++++++ expect/_to_have_been_nth_called_with_test.ts | 6 ++++++ expect/_to_have_last_returned_with_test.ts | 6 ++++++ expect/_to_have_lenth_test.ts | 6 ++++++ expect/_to_have_nth_returned_with_test.ts | 6 ++++++ expect/_to_have_property_test.ts | 6 ++++++ expect/_to_have_returned_test.ts | 6 ++++++ expect/_to_have_returned_times_test.ts | 6 ++++++ expect/_to_have_returned_with_test.ts | 6 ++++++ expect/_to_match_object_test.ts | 6 ++++++ expect/_to_match_test.ts | 6 ++++++ expect/_to_strict_equal_test.ts | 21 +++++++++++++++++++ expect/_to_throw_test.ts | 14 +++++++++++++ expect/expect.ts | 13 ++---------- 42 files changed, 214 insertions(+), 23 deletions(-) create mode 100644 expect/_to_be_close_to_test.ts create mode 100644 expect/_to_be_defined_test.ts create mode 100644 expect/_to_be_falsy_test.ts create mode 100644 expect/_to_be_greater_than_or_equal_test.ts create mode 100644 expect/_to_be_greater_than_test.ts create mode 100644 expect/_to_be_instance_of_test.ts create mode 100644 expect/_to_be_less_than_or_equal_test.ts create mode 100644 expect/_to_be_less_than_test.ts create mode 100644 expect/_to_be_nan_test.ts create mode 100644 expect/_to_be_null_test.ts create mode 100644 expect/_to_be_test.ts create mode 100644 expect/_to_be_truthy_test.ts create mode 100644 expect/_to_be_undefined_test.ts create mode 100644 expect/_to_have_been_called_test.ts create mode 100644 expect/_to_have_been_called_times_test.ts create mode 100644 expect/_to_have_been_called_with_test.ts create mode 100644 expect/_to_have_been_last_called_with_test.ts create mode 100644 expect/_to_have_been_nth_called_with_test.ts create mode 100644 expect/_to_have_last_returned_with_test.ts create mode 100644 expect/_to_have_lenth_test.ts create mode 100644 expect/_to_have_nth_returned_with_test.ts create mode 100644 expect/_to_have_property_test.ts create mode 100644 expect/_to_have_returned_test.ts create mode 100644 expect/_to_have_returned_times_test.ts create mode 100644 expect/_to_have_returned_with_test.ts create mode 100644 expect/_to_match_object_test.ts create mode 100644 expect/_to_match_test.ts create mode 100644 expect/_to_strict_equal_test.ts create mode 100644 expect/_to_throw_test.ts diff --git a/expect/_to_be.ts b/expect/_to_be.ts index f1ab0c678630..3fdd4cf410ca 100644 --- a/expect/_to_be.ts +++ b/expect/_to_be.ts @@ -4,7 +4,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; -/* Similar to assertStrictEquals and assertNotStrictEquals*/ export function toBe(context: MatcherContext, expect: unknown): MatchResult { if (context.isNot) { return assertNotStrictEquals(context.value, expect, context.customMessage); diff --git a/expect/_to_be_close_to.ts b/expect/_to_be_close_to.ts index 239ee625d1bb..dd58e7629c9e 100644 --- a/expect/_to_be_close_to.ts +++ b/expect/_to_be_close_to.ts @@ -4,10 +4,11 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { assertAlmostEquals } from "../assert/assert_almost_equals.ts"; import { AssertionError } from "../assert/assertion_error.ts"; +// TODO(kt3k): tolerance handling is wrong export function toBeCloseTo( context: MatcherContext, expected: number, - tolerance = 1e-7, + tolerance = 1e-7, // FIXME: This should be digits number, not tolerance ): MatchResult { if (context.isNot) { const actual = Number(context.value); diff --git a/expect/_to_be_close_to_test.ts b/expect/_to_be_close_to_test.ts new file mode 100644 index 000000000000..366a50c2fc92 --- /dev/null +++ b/expect/_to_be_close_to_test.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeCloseTo()", () => { + expect(0.2 + 0.1).toBeCloseTo(0.3); + expect(0.2 + 0.1).toBeCloseTo(0.3, 5); +}); diff --git a/expect/_to_be_defined_test.ts b/expect/_to_be_defined_test.ts new file mode 100644 index 000000000000..544b98e4d47c --- /dev/null +++ b/expect/_to_be_defined_test.ts @@ -0,0 +1,17 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeDefined()", () => { + expect(1).toBeDefined(); + expect("a").toBeDefined(); + + assertThrows(() => { + expect(undefined).toBeDefined(); + }, AssertionError); + assertThrows(() => { + // deno-lint-ignore no-explicit-any + expect(({} as any).foo).toBeDefined(); + }, AssertionError); +}); diff --git a/expect/_to_be_falsy_test.ts b/expect/_to_be_falsy_test.ts new file mode 100644 index 000000000000..4e69d8e17a60 --- /dev/null +++ b/expect/_to_be_falsy_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeFalsy()", () => {}); diff --git a/expect/_to_be_greater_than.ts b/expect/_to_be_greater_than.ts index 1dc1ffa3f834..6ca8187aead3 100644 --- a/expect/_to_be_greater_than.ts +++ b/expect/_to_be_greater_than.ts @@ -3,7 +3,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertEqual */ export function toBeGreaterThan( context: MatcherContext, expected: number, diff --git a/expect/_to_be_greater_than_or_equal.ts b/expect/_to_be_greater_than_or_equal.ts index 8e4e00ac1b15..05289f01c7f6 100644 --- a/expect/_to_be_greater_than_or_equal.ts +++ b/expect/_to_be_greater_than_or_equal.ts @@ -3,7 +3,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertEqual */ export function toBeGreaterThanOrEqual( context: MatcherContext, expected: number, diff --git a/expect/_to_be_greater_than_or_equal_test.ts b/expect/_to_be_greater_than_or_equal_test.ts new file mode 100644 index 000000000000..dfec6a34d8bb --- /dev/null +++ b/expect/_to_be_greater_than_or_equal_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeGreaterThanOrEqual()", () => {}); diff --git a/expect/_to_be_greater_than_test.ts b/expect/_to_be_greater_than_test.ts new file mode 100644 index 000000000000..748ef2e8bfbc --- /dev/null +++ b/expect/_to_be_greater_than_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeGreaterThan()", () => {}); diff --git a/expect/_to_be_instance_of.ts b/expect/_to_be_instance_of.ts index 03569dc5c26d..67771efb25e5 100644 --- a/expect/_to_be_instance_of.ts +++ b/expect/_to_be_instance_of.ts @@ -4,7 +4,6 @@ import { AnyConstructor, MatcherContext, MatchResult } from "./_types.ts"; import { assertInstanceOf } from "../assert/assert_instance_of.ts"; import { assertNotInstanceOf } from "../assert/assert_not_instance_of.ts"; -/* Similar to assertInstanceOf(value, null) and assertNotInstanceOf(value, null)*/ export function toBeInstanceOf( context: MatcherContext, expected: T, diff --git a/expect/_to_be_instance_of_test.ts b/expect/_to_be_instance_of_test.ts new file mode 100644 index 000000000000..fe6dd8bbe165 --- /dev/null +++ b/expect/_to_be_instance_of_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeInstanceOf", () => {}); diff --git a/expect/_to_be_less_than.ts b/expect/_to_be_less_than.ts index 6d615e0b34e9..3cdad4fa6d0c 100644 --- a/expect/_to_be_less_than.ts +++ b/expect/_to_be_less_than.ts @@ -3,7 +3,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertEqual */ export function toBeLessThan( context: MatcherContext, expected: number, diff --git a/expect/_to_be_less_than_or_equal.ts b/expect/_to_be_less_than_or_equal.ts index 98a90dcaaea3..f8ce82166b83 100644 --- a/expect/_to_be_less_than_or_equal.ts +++ b/expect/_to_be_less_than_or_equal.ts @@ -3,7 +3,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertEqual */ export function toBeLessThanOrEqual( context: MatcherContext, expected: number, diff --git a/expect/_to_be_less_than_or_equal_test.ts b/expect/_to_be_less_than_or_equal_test.ts new file mode 100644 index 000000000000..e8eaa5b55f2c --- /dev/null +++ b/expect/_to_be_less_than_or_equal_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeLessThanOrEqual", () => {}); diff --git a/expect/_to_be_less_than_test.ts b/expect/_to_be_less_than_test.ts new file mode 100644 index 000000000000..228101421bcd --- /dev/null +++ b/expect/_to_be_less_than_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeLessThan()", () => {}); diff --git a/expect/_to_be_nan.ts b/expect/_to_be_nan.ts index 59e55a000169..7fae0450c153 100644 --- a/expect/_to_be_nan.ts +++ b/expect/_to_be_nan.ts @@ -4,7 +4,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { assertNotEquals } from "../assert/assert_not_equals.ts"; import { assertEquals } from "../assert/assert_equals.ts"; -/* Similar to assertStrictEquals(isNaN(context.value as number), true) and assertNotStrictEquals(isNaN(context.value as number), true)*/ export function toBeNaN(context: MatcherContext): MatchResult { if (context.isNot) { return assertNotEquals( diff --git a/expect/_to_be_nan_test.ts b/expect/_to_be_nan_test.ts new file mode 100644 index 000000000000..a5076e98c8dc --- /dev/null +++ b/expect/_to_be_nan_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeNaN()", () => {}); diff --git a/expect/_to_be_null.ts b/expect/_to_be_null.ts index 3c01be88af28..aab87ceb9299 100644 --- a/expect/_to_be_null.ts +++ b/expect/_to_be_null.ts @@ -4,7 +4,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; -/* Similar to assertStrictEquals(value, null) and assertNotStrictEquals(value, null)*/ export function toBeNull(context: MatcherContext): MatchResult { if (context.isNot) { return assertNotStrictEquals( diff --git a/expect/_to_be_null_test.ts b/expect/_to_be_null_test.ts new file mode 100644 index 000000000000..d0d1c3481873 --- /dev/null +++ b/expect/_to_be_null_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeNull()", () => {}); diff --git a/expect/_to_be_test.ts b/expect/_to_be_test.ts new file mode 100644 index 000000000000..80f4ce0ba7b0 --- /dev/null +++ b/expect/_to_be_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBe()", () => {}); diff --git a/expect/_to_be_truthy.ts b/expect/_to_be_truthy.ts index a535f3e7a22d..5c4f1ec04109 100644 --- a/expect/_to_be_truthy.ts +++ b/expect/_to_be_truthy.ts @@ -3,7 +3,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -/* Similar to assertEqual(!!value) */ export function toBeTruthy( context: MatcherContext, ): MatchResult { diff --git a/expect/_to_be_truthy_test.ts b/expect/_to_be_truthy_test.ts new file mode 100644 index 000000000000..63110ba3efba --- /dev/null +++ b/expect/_to_be_truthy_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeTruthy()", () => {}); diff --git a/expect/_to_be_undefined.ts b/expect/_to_be_undefined.ts index c3da1b3599ee..02e922d02279 100644 --- a/expect/_to_be_undefined.ts +++ b/expect/_to_be_undefined.ts @@ -4,7 +4,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; -/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ export function toBeUndefined(context: MatcherContext): MatchResult { if (context.isNot) { return assertNotStrictEquals( diff --git a/expect/_to_be_undefined_test.ts b/expect/_to_be_undefined_test.ts new file mode 100644 index 000000000000..d9b363a1484c --- /dev/null +++ b/expect/_to_be_undefined_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toBeUndefined()", () => {}); diff --git a/expect/_to_equal.ts b/expect/_to_equal.ts index 73c86912d615..11b957ab9ad7 100644 --- a/expect/_to_equal.ts +++ b/expect/_to_equal.ts @@ -4,7 +4,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { assertNotEquals } from "../assert/assert_not_equals.ts"; import { assertEquals } from "../assert/assert_equals.ts"; -/* Similar to assertEqual */ export function toEqual( context: MatcherContext, expected: unknown, diff --git a/expect/_to_have_been_called_test.ts b/expect/_to_have_been_called_test.ts new file mode 100644 index 000000000000..fe147701a064 --- /dev/null +++ b/expect/_to_have_been_called_test.ts @@ -0,0 +1,5 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; + +Deno.test("expect().toHaveBeenCalled()", () => {}); diff --git a/expect/_to_have_been_called_times_test.ts b/expect/_to_have_been_called_times_test.ts new file mode 100644 index 000000000000..d468e20863b7 --- /dev/null +++ b/expect/_to_have_been_called_times_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveBeenCalledTimes()", () => {}); diff --git a/expect/_to_have_been_called_with_test.ts b/expect/_to_have_been_called_with_test.ts new file mode 100644 index 000000000000..ae62e01acd6a --- /dev/null +++ b/expect/_to_have_been_called_with_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveBeenCalledWith()", () => {}); diff --git a/expect/_to_have_been_last_called_with_test.ts b/expect/_to_have_been_last_called_with_test.ts new file mode 100644 index 000000000000..f3b08ea3e1d2 --- /dev/null +++ b/expect/_to_have_been_last_called_with_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveBeenLastCalledWith()", () => {}); diff --git a/expect/_to_have_been_nth_called_with_test.ts b/expect/_to_have_been_nth_called_with_test.ts new file mode 100644 index 000000000000..91a301151af1 --- /dev/null +++ b/expect/_to_have_been_nth_called_with_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().", () => {}); diff --git a/expect/_to_have_last_returned_with_test.ts b/expect/_to_have_last_returned_with_test.ts new file mode 100644 index 000000000000..09c8f7697828 --- /dev/null +++ b/expect/_to_have_last_returned_with_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveLastReturnedWith()", () => {}); diff --git a/expect/_to_have_lenth_test.ts b/expect/_to_have_lenth_test.ts new file mode 100644 index 000000000000..84e890ce2449 --- /dev/null +++ b/expect/_to_have_lenth_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveLength()", () => {}); diff --git a/expect/_to_have_nth_returned_with_test.ts b/expect/_to_have_nth_returned_with_test.ts new file mode 100644 index 000000000000..5c83cf15e4e8 --- /dev/null +++ b/expect/_to_have_nth_returned_with_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveNthReturnedWith()", () => {}); diff --git a/expect/_to_have_property_test.ts b/expect/_to_have_property_test.ts new file mode 100644 index 000000000000..27d591862cc4 --- /dev/null +++ b/expect/_to_have_property_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveProperty()", () => {}); diff --git a/expect/_to_have_returned_test.ts b/expect/_to_have_returned_test.ts new file mode 100644 index 000000000000..72e18ff7c250 --- /dev/null +++ b/expect/_to_have_returned_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveReturned()", () => {}); diff --git a/expect/_to_have_returned_times_test.ts b/expect/_to_have_returned_times_test.ts new file mode 100644 index 000000000000..e0f7ae2564f3 --- /dev/null +++ b/expect/_to_have_returned_times_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveReturnedTimes()", () => {}); diff --git a/expect/_to_have_returned_with_test.ts b/expect/_to_have_returned_with_test.ts new file mode 100644 index 000000000000..91dbe3abce2f --- /dev/null +++ b/expect/_to_have_returned_with_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toHaveReturnedWith()", () => {}); diff --git a/expect/_to_match_object_test.ts b/expect/_to_match_object_test.ts new file mode 100644 index 000000000000..e819ed01502d --- /dev/null +++ b/expect/_to_match_object_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toMatchObject()", () => {}); diff --git a/expect/_to_match_test.ts b/expect/_to_match_test.ts new file mode 100644 index 000000000000..9f24ac643116 --- /dev/null +++ b/expect/_to_match_test.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toMatch()", () => {}); diff --git a/expect/_to_strict_equal_test.ts b/expect/_to_strict_equal_test.ts new file mode 100644 index 000000000000..70fda0cb5c85 --- /dev/null +++ b/expect/_to_strict_equal_test.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toStrictEqual()", () => { + const obj = { a: 1 }; + expect(1).toStrictEqual(1); + expect("a").toStrictEqual("a"); + expect(obj).toStrictEqual(obj); + + assertThrows(() => { + expect(1).toStrictEqual(2); + }, AssertionError); + assertThrows(() => { + expect("a").toStrictEqual("b"); + }, AssertionError); + assertThrows(() => { + expect(obj).toStrictEqual({ a: 1 }); + }, AssertionError); +}); diff --git a/expect/_to_throw_test.ts b/expect/_to_throw_test.ts new file mode 100644 index 000000000000..a6f9551ec29d --- /dev/null +++ b/expect/_to_throw_test.ts @@ -0,0 +1,14 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toThrow()", () => { + expect(() => { + throw new Error("hello world"); + }).toThrow(); + + assertThrows(() => { + expect(() => {}).toThrow(); + }, AssertionError); +}); diff --git a/expect/expect.ts b/expect/expect.ts index 37f39502016f..c194ba98dda1 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -1,12 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2019 Allain Lalonde. All rights reserved. ISC License. -import type { - AnyConstructor, - Matcher, - MatcherContext, - Matchers, -} from "./_types.ts"; +import type { AnyConstructor, Matcher, MatcherContext } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; import { toBeCloseTo } from "./_to_be_close_to.ts"; import { toBeDefined } from "./_to_be_defined.ts"; @@ -68,7 +63,7 @@ export interface Expected { toMatchObject(expected: Record): void; toStrictEqual(candidate: unknown): void; // deno-lint-ignore no-explicit-any - toThrow(expected: new (...args: any[]) => E): void; + toThrow(expected?: new (...args: any[]) => E): void; not: Expected; resolves: Async; rejects: Async; @@ -190,10 +185,6 @@ export type Async = { : T[K]; }; -export function addMatchers(newMatchers: Matchers): void { - Object.assign(matchers, newMatchers); -} - function isPromiseLike(value: unknown): value is PromiseLike { if (value == null) { return false; From f1e4685af5c7ad412a8a776f1ae41b4354b99b9e Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 16 Nov 2023 17:00:39 +0900 Subject: [PATCH 05/17] fix toBeFalsy --- expect/_to_be_close_to_test.ts | 6 ++++++ expect/_to_be_defined.ts | 5 +++-- expect/_to_be_defined_test.ts | 5 ++++- expect/_to_be_falsy.ts | 12 +++++++----- expect/_to_be_falsy_test.ts | 20 +++++++++++++++++++- expect/_to_throw_test.ts | 2 ++ 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/expect/_to_be_close_to_test.ts b/expect/_to_be_close_to_test.ts index 366a50c2fc92..3df46f3e957a 100644 --- a/expect/_to_be_close_to_test.ts +++ b/expect/_to_be_close_to_test.ts @@ -6,4 +6,10 @@ import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toBeCloseTo()", () => { expect(0.2 + 0.1).toBeCloseTo(0.3); expect(0.2 + 0.1).toBeCloseTo(0.3, 5); + + expect(0.2 + 0.11).not.toBeCloseTo(0.3); + + assertThrows(() => { + expect(0.2 + 0.11).toBeCloseTo(0.3); + }, AssertionError); }); diff --git a/expect/_to_be_defined.ts b/expect/_to_be_defined.ts index 3440c0e1517d..1778dd2878a1 100644 --- a/expect/_to_be_defined.ts +++ b/expect/_to_be_defined.ts @@ -6,7 +6,8 @@ import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; export function toBeDefined(context: MatcherContext): MatchResult { if (context.isNot) { - return assertStrictEquals(context.value, undefined, context.customMessage); + assertStrictEquals(context.value, undefined, context.customMessage); + } else { + assertNotStrictEquals(context.value, undefined, context.customMessage); } - return assertNotStrictEquals(context.value, undefined, context.customMessage); } diff --git a/expect/_to_be_defined_test.ts b/expect/_to_be_defined_test.ts index 544b98e4d47c..b7ea14a32762 100644 --- a/expect/_to_be_defined_test.ts +++ b/expect/_to_be_defined_test.ts @@ -1,4 +1,5 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; @@ -7,11 +8,13 @@ Deno.test("expect().toBeDefined()", () => { expect(1).toBeDefined(); expect("a").toBeDefined(); + expect(undefined).not.toBeDefined(); + expect(({} as any).foo).not.toBeDefined(); + assertThrows(() => { expect(undefined).toBeDefined(); }, AssertionError); assertThrows(() => { - // deno-lint-ignore no-explicit-any expect(({} as any).foo).toBeDefined(); }, AssertionError); }); diff --git a/expect/_to_be_falsy.ts b/expect/_to_be_falsy.ts index 1f1314da0418..b040f377414f 100644 --- a/expect/_to_be_falsy.ts +++ b/expect/_to_be_falsy.ts @@ -13,10 +13,12 @@ export function toBeFalsy( `Expected ${context.value} to NOT be falsy`, ); } - } - if (!isFalsy) { - throw new AssertionError( - `Expected ${context.value} to be falsy`, - ); + return; + } else { + if (!isFalsy) { + throw new AssertionError( + `Expected ${context.value} to be falsy`, + ); + } } } diff --git a/expect/_to_be_falsy_test.ts b/expect/_to_be_falsy_test.ts index 4e69d8e17a60..9528191fedf5 100644 --- a/expect/_to_be_falsy_test.ts +++ b/expect/_to_be_falsy_test.ts @@ -3,4 +3,22 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeFalsy()", () => {}); +Deno.test("expect().toBeFalsy()", () => { + expect(false).toBeFalsy(); + expect(0).toBeFalsy(); + expect("").toBeFalsy(); + + expect(true).not.toBeFalsy(); + expect(1).not.toBeFalsy(); + expect("hello").not.toBeFalsy(); + + assertThrows(() => { + expect(true).toBeFalsy(); + }, AssertionError); + assertThrows(() => { + expect(1).toBeFalsy(); + }, AssertionError); + assertThrows(() => { + expect("hello").toBeFalsy(); + }, AssertionError); +}); diff --git a/expect/_to_throw_test.ts b/expect/_to_throw_test.ts index a6f9551ec29d..1bfad3bb78e7 100644 --- a/expect/_to_throw_test.ts +++ b/expect/_to_throw_test.ts @@ -8,6 +8,8 @@ Deno.test("expect().toThrow()", () => { throw new Error("hello world"); }).toThrow(); + expect(() => {}).not.toThrow(); + assertThrows(() => { expect(() => {}).toThrow(); }, AssertionError); From 8305b7ad64436f2ed0db689a2cfa58d62a1f66ec Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 16 Nov 2023 19:24:45 +0900 Subject: [PATCH 06/17] add tests, wip --- expect/_to_be.ts | 5 +-- expect/_to_be_greater_than.ts | 11 ++++--- expect/_to_be_greater_than_or_equal.ts | 11 ++++--- expect/_to_be_greater_than_or_equal_test.ts | 11 ++++++- expect/_to_be_greater_than_test.ts | 14 +++++++- expect/_to_be_instance_of.ts | 5 +-- expect/_to_be_instance_of_test.ts | 11 ++++++- expect/_to_be_less_than.ts | 11 ++++--- expect/_to_be_less_than_or_equal.ts | 11 ++++--- expect/_to_be_less_than_or_equal_test.ts | 11 ++++++- expect/_to_be_less_than_test.ts | 14 +++++++- expect/_to_be_nan.ts | 13 ++++---- expect/_to_be_nan_test.ts | 10 +++++- expect/_to_be_null.ts | 13 ++++---- expect/_to_be_null_test.ts | 10 +++++- expect/_to_be_test.ts | 21 +++++++++++- expect/_to_be_truthy.ts | 11 ++++--- expect/_to_be_truthy_test.ts | 24 +++++++++++++- expect/_to_be_undefined.ts | 5 +-- expect/_to_be_undefined_test.ts | 10 +++++- expect/_to_equal.ts | 5 +-- expect/_to_match_object.ts | 13 ++++---- expect/_to_match_object_test.ts | 36 ++++++++++++++++++++- expect/_to_match_test.ts | 10 +++++- expect/_to_strict_equal_test.ts | 4 +++ expect/expect.ts | 14 ++++---- 26 files changed, 245 insertions(+), 69 deletions(-) diff --git a/expect/_to_be.ts b/expect/_to_be.ts index 3fdd4cf410ca..0f37f67a6a4b 100644 --- a/expect/_to_be.ts +++ b/expect/_to_be.ts @@ -6,7 +6,8 @@ import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; export function toBe(context: MatcherContext, expect: unknown): MatchResult { if (context.isNot) { - return assertNotStrictEquals(context.value, expect, context.customMessage); + assertNotStrictEquals(context.value, expect, context.customMessage); + } else { + assertStrictEquals(context.value, expect, context.customMessage); } - return assertStrictEquals(context.value, expect, context.customMessage); } diff --git a/expect/_to_be_greater_than.ts b/expect/_to_be_greater_than.ts index 6ca8187aead3..31b315e5537a 100644 --- a/expect/_to_be_greater_than.ts +++ b/expect/_to_be_greater_than.ts @@ -14,10 +14,11 @@ export function toBeGreaterThan( `Expected ${context.value} to NOT be greater than ${expected}`, ); } - } - if (!isGreater) { - throw new AssertionError( - `Expected ${context.value} to be greater than ${expected}`, - ); + } else { + if (!isGreater) { + throw new AssertionError( + `Expected ${context.value} to be greater than ${expected}`, + ); + } } } diff --git a/expect/_to_be_greater_than_or_equal.ts b/expect/_to_be_greater_than_or_equal.ts index 05289f01c7f6..1444f609f92c 100644 --- a/expect/_to_be_greater_than_or_equal.ts +++ b/expect/_to_be_greater_than_or_equal.ts @@ -14,10 +14,11 @@ export function toBeGreaterThanOrEqual( `Expected ${context.value} to NOT be greater than or equal ${expected}`, ); } - } - if (!isGreaterOrEqual) { - throw new AssertionError( - `Expected ${context.value} to be greater than or equal ${expected}`, - ); + } else { + if (!isGreaterOrEqual) { + throw new AssertionError( + `Expected ${context.value} to be greater than or equal ${expected}`, + ); + } } } diff --git a/expect/_to_be_greater_than_or_equal_test.ts b/expect/_to_be_greater_than_or_equal_test.ts index dfec6a34d8bb..a7332fe32930 100644 --- a/expect/_to_be_greater_than_or_equal_test.ts +++ b/expect/_to_be_greater_than_or_equal_test.ts @@ -3,4 +3,13 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeGreaterThanOrEqual()", () => {}); +Deno.test("expect().toBeGreaterThanOrEqual()", () => { + expect(10).toBeGreaterThanOrEqual(10); + expect(11).toBeGreaterThanOrEqual(10); + + expect(9).not.toBeGreaterThanOrEqual(10); + + assertThrows(() => { + expect(9).toBeGreaterThanOrEqual(10); + }, AssertionError); +}); diff --git a/expect/_to_be_greater_than_test.ts b/expect/_to_be_greater_than_test.ts index 748ef2e8bfbc..de38d6b75124 100644 --- a/expect/_to_be_greater_than_test.ts +++ b/expect/_to_be_greater_than_test.ts @@ -3,4 +3,16 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeGreaterThan()", () => {}); +Deno.test("expect().toBeGreaterThan()", () => { + expect(11).toBeGreaterThan(10); + + expect(10).not.toBeGreaterThan(10); + expect(9).not.toBeGreaterThan(10); + + assertThrows(() => { + expect(10).toBeGreaterThan(10); + }, AssertionError); + assertThrows(() => { + expect(9).toBeGreaterThan(10); + }); +}); diff --git a/expect/_to_be_instance_of.ts b/expect/_to_be_instance_of.ts index 67771efb25e5..d73882ab0442 100644 --- a/expect/_to_be_instance_of.ts +++ b/expect/_to_be_instance_of.ts @@ -9,7 +9,8 @@ export function toBeInstanceOf( expected: T, ): MatchResult { if (context.isNot) { - return assertNotInstanceOf(context.value, expected); + assertNotInstanceOf(context.value, expected); + } else { + assertInstanceOf(context.value, expected); } - return assertInstanceOf(context.value, expected); } diff --git a/expect/_to_be_instance_of_test.ts b/expect/_to_be_instance_of_test.ts index fe6dd8bbe165..48863e4fa460 100644 --- a/expect/_to_be_instance_of_test.ts +++ b/expect/_to_be_instance_of_test.ts @@ -3,4 +3,13 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeInstanceOf", () => {}); +Deno.test("expect().toBeInstanceOf", () => { + expect(new Error()).toBeInstanceOf(Error); + expect(new Error()).toBeInstanceOf(Object); + + expect(new Error()).not.toBeInstanceOf(String); + + assertThrows(() => { + expect(new Error()).toBeInstanceOf(String); + }, AssertionError); +}); diff --git a/expect/_to_be_less_than.ts b/expect/_to_be_less_than.ts index 3cdad4fa6d0c..2c3352b65fc2 100644 --- a/expect/_to_be_less_than.ts +++ b/expect/_to_be_less_than.ts @@ -14,10 +14,11 @@ export function toBeLessThan( `Expected ${context.value} to NOT be lower than ${expected}`, ); } - } - if (!isLower) { - throw new AssertionError( - `Expected ${context.value} to be lower than ${expected}`, - ); + } else { + if (!isLower) { + throw new AssertionError( + `Expected ${context.value} to be lower than ${expected}`, + ); + } } } diff --git a/expect/_to_be_less_than_or_equal.ts b/expect/_to_be_less_than_or_equal.ts index f8ce82166b83..7a2c61602bff 100644 --- a/expect/_to_be_less_than_or_equal.ts +++ b/expect/_to_be_less_than_or_equal.ts @@ -14,10 +14,11 @@ export function toBeLessThanOrEqual( `Expected ${context.value} to NOT be lower than or equal ${expected}`, ); } - } - if (!isLower) { - throw new AssertionError( - `Expected ${context.value} to be lower than or equal ${expected}`, - ); + } else { + if (!isLower) { + throw new AssertionError( + `Expected ${context.value} to be lower than or equal ${expected}`, + ); + } } } diff --git a/expect/_to_be_less_than_or_equal_test.ts b/expect/_to_be_less_than_or_equal_test.ts index e8eaa5b55f2c..134cde1bc1b2 100644 --- a/expect/_to_be_less_than_or_equal_test.ts +++ b/expect/_to_be_less_than_or_equal_test.ts @@ -3,4 +3,13 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeLessThanOrEqual", () => {}); +Deno.test("expect().toBeLessThanOrEqual", () => { + expect(10).toBeLessThanOrEqual(10); + expect(9).toBeLessThanOrEqual(10); + + expect(11).not.toBeLessThanOrEqual(10); + + assertThrows(() => { + expect(11).toBeLessThanOrEqual(10); + }, AssertionError); +}); diff --git a/expect/_to_be_less_than_test.ts b/expect/_to_be_less_than_test.ts index 228101421bcd..479a37126a21 100644 --- a/expect/_to_be_less_than_test.ts +++ b/expect/_to_be_less_than_test.ts @@ -3,4 +3,16 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeLessThan()", () => {}); +Deno.test("expect().toBeLessThan()", () => { + expect(9).toBeLessThan(10); + + expect(10).not.toBeLessThan(10); + expect(11).not.toBeLessThan(10); + + assertThrows(() => { + expect(10).toBeLessThan(10); + }, AssertionError); + assertThrows(() => { + expect(11).toBeLessThan(10); + }, AssertionError); +}); diff --git a/expect/_to_be_nan.ts b/expect/_to_be_nan.ts index 7fae0450c153..4b83c5617283 100644 --- a/expect/_to_be_nan.ts +++ b/expect/_to_be_nan.ts @@ -6,15 +6,16 @@ import { assertEquals } from "../assert/assert_equals.ts"; export function toBeNaN(context: MatcherContext): MatchResult { if (context.isNot) { - return assertNotEquals( + assertNotEquals( isNaN(Number(context.value)), true, context.customMessage || `Expected ${context.value} to not be NaN`, ); + } else { + assertEquals( + isNaN(Number(context.value)), + true, + context.customMessage || `Expected ${context.value} to be NaN`, + ); } - return assertEquals( - isNaN(Number(context.value)), - true, - context.customMessage || `Expected ${context.value} to be NaN`, - ); } diff --git a/expect/_to_be_nan_test.ts b/expect/_to_be_nan_test.ts index a5076e98c8dc..6e9f6a3ff081 100644 --- a/expect/_to_be_nan_test.ts +++ b/expect/_to_be_nan_test.ts @@ -3,4 +3,12 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeNaN()", () => {}); +Deno.test("expect().toBeNaN()", () => { + expect(NaN).toBeNaN(); + + expect(1).not.toBeNaN(); + + assertThrows(() => { + expect(1).toBeNaN(); + }, AssertionError); +}); diff --git a/expect/_to_be_null.ts b/expect/_to_be_null.ts index aab87ceb9299..d9cbe8918132 100644 --- a/expect/_to_be_null.ts +++ b/expect/_to_be_null.ts @@ -6,15 +6,16 @@ import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; export function toBeNull(context: MatcherContext): MatchResult { if (context.isNot) { - return assertNotStrictEquals( + assertNotStrictEquals( context.value as number, null, context.customMessage || `Expected ${context.value} to not be null`, ); + } else { + assertStrictEquals( + context.value as number, + null, + context.customMessage || `Expected ${context.value} to be null`, + ); } - return assertStrictEquals( - context.value as number, - null, - context.customMessage || `Expected ${context.value} to be null`, - ); } diff --git a/expect/_to_be_null_test.ts b/expect/_to_be_null_test.ts index d0d1c3481873..e66aec2a19dc 100644 --- a/expect/_to_be_null_test.ts +++ b/expect/_to_be_null_test.ts @@ -3,4 +3,12 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeNull()", () => {}); +Deno.test("expect().toBeNull()", () => { + expect(null).toBeNull(); + + expect(undefined).not.toBeNull(); + + assertThrows(() => { + expect(undefined).toBeNull(); + }, AssertionError); +}); diff --git a/expect/_to_be_test.ts b/expect/_to_be_test.ts index 80f4ce0ba7b0..a7b5b4d60e92 100644 --- a/expect/_to_be_test.ts +++ b/expect/_to_be_test.ts @@ -3,4 +3,23 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBe()", () => {}); +Deno.test("expect().toBe()", () => { + const obj = {}; + expect(1).toBe(1); + expect("hello").toBe("hello"); + expect(obj).toBe(obj); + + expect(1).not.toBe(2); + expect("hello").not.toBe("world"); + expect(obj).not.toBe({}); + + assertThrows(() => { + expect(1).toBe(2); + }, AssertionError); + assertThrows(() => { + expect("hello").toBe("world"); + }, AssertionError); + assertThrows(() => { + expect(obj).toBe({}); + }, AssertionError); +}); diff --git a/expect/_to_be_truthy.ts b/expect/_to_be_truthy.ts index 5c4f1ec04109..73c76ea15adf 100644 --- a/expect/_to_be_truthy.ts +++ b/expect/_to_be_truthy.ts @@ -13,10 +13,11 @@ export function toBeTruthy( `Expected ${context.value} to NOT be truthy`, ); } - } - if (!isTruthy) { - throw new AssertionError( - `Expected ${context.value} to be truthy`, - ); + } else { + if (!isTruthy) { + throw new AssertionError( + `Expected ${context.value} to be truthy`, + ); + } } } diff --git a/expect/_to_be_truthy_test.ts b/expect/_to_be_truthy_test.ts index 63110ba3efba..dc442613864b 100644 --- a/expect/_to_be_truthy_test.ts +++ b/expect/_to_be_truthy_test.ts @@ -3,4 +3,26 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeTruthy()", () => {}); +Deno.test("expect().toBeTruthy()", () => { + expect(1).toBeTruthy(); + expect("hello").toBeTruthy(); + expect({}).toBeTruthy(); + + expect(0).not.toBeTruthy(); + expect("").not.toBeTruthy(); + expect(null).not.toBeTruthy(); + expect(undefined).not.toBeTruthy(); + + assertThrows(() => { + expect(0).toBeTruthy(); + }, AssertionError); + assertThrows(() => { + expect("").toBeTruthy(); + }, AssertionError); + assertThrows(() => { + expect(null).toBeTruthy(); + }, AssertionError); + assertThrows(() => { + expect(undefined).toBeTruthy(); + }, AssertionError); +}); diff --git a/expect/_to_be_undefined.ts b/expect/_to_be_undefined.ts index 02e922d02279..2ffe406babd1 100644 --- a/expect/_to_be_undefined.ts +++ b/expect/_to_be_undefined.ts @@ -6,11 +6,12 @@ import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; export function toBeUndefined(context: MatcherContext): MatchResult { if (context.isNot) { - return assertNotStrictEquals( + assertNotStrictEquals( context.value, undefined, context.customMessage, ); + } else { + assertStrictEquals(context.value, undefined, context.customMessage); } - return assertStrictEquals(context.value, undefined, context.customMessage); } diff --git a/expect/_to_be_undefined_test.ts b/expect/_to_be_undefined_test.ts index d9b363a1484c..c4a23faebe0b 100644 --- a/expect/_to_be_undefined_test.ts +++ b/expect/_to_be_undefined_test.ts @@ -3,4 +3,12 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toBeUndefined()", () => {}); +Deno.test("expect().toBeUndefined()", () => { + expect(undefined).toBeUndefined(); + + expect(null).not.toBeUndefined(); + + assertThrows(() => { + expect(null).toBeUndefined(); + }, AssertionError); +}); diff --git a/expect/_to_equal.ts b/expect/_to_equal.ts index 11b957ab9ad7..0bc082a21390 100644 --- a/expect/_to_equal.ts +++ b/expect/_to_equal.ts @@ -9,7 +9,8 @@ export function toEqual( expected: unknown, ): MatchResult { if (context.isNot) { - return assertNotEquals(context.value, expected, context.customMessage); + assertNotEquals(context.value, expected, context.customMessage); + } else { + assertEquals(context.value, expected, context.customMessage); } - return assertEquals(context.value, expected, context.customMessage); } diff --git a/expect/_to_match_object.ts b/expect/_to_match_object.ts index eb35bb6582a4..48fe7c5854a1 100644 --- a/expect/_to_match_object.ts +++ b/expect/_to_match_object.ts @@ -31,11 +31,12 @@ export function toMatchObject( } return; } + } else { + assertObjectMatch( + // deno-lint-ignore no-explicit-any + context.value as Record, + expected, + context.customMessage, + ); } - return assertObjectMatch( - // deno-lint-ignore no-explicit-any - context.value as Record, - expected, - context.customMessage, - ); } diff --git a/expect/_to_match_object_test.ts b/expect/_to_match_object_test.ts index e819ed01502d..09c9dd638622 100644 --- a/expect/_to_match_object_test.ts +++ b/expect/_to_match_object_test.ts @@ -3,4 +3,38 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toMatchObject()", () => {}); +Deno.test("expect().toMatchObject()", () => { + const house0 = { + bath: true, + bedrooms: 4, + kitchen: { + amenities: ["oven", "stove", "washer"], + area: 20, + wallColor: "white", + }, + }; + const house1 = { + bath: true, + bedrooms: 4, + kitchen: { + amenities: ["oven", "stove"], + area: 20, + wallColor: "white", + }, + }; + const desiredHouse = { + bath: true, + kitchen: { + amenities: ["oven", "stove", "washer"], + wallColor: "white", + }, + }; + + expect(house0).toMatchObject(desiredHouse); + + expect(house1).not.toMatchObject(desiredHouse); + + assertThrows(() => { + expect(house1).toMatchObject(desiredHouse); + }, AssertionError); +}); diff --git a/expect/_to_match_test.ts b/expect/_to_match_test.ts index 9f24ac643116..6f2345fe3252 100644 --- a/expect/_to_match_test.ts +++ b/expect/_to_match_test.ts @@ -3,4 +3,12 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toMatch()", () => {}); +Deno.test("expect().toMatch()", () => { + expect("hello deno").toMatch(/deno/); + + expect("hello deno").not.toMatch(/DENO/); + + assertThrows(() => { + expect("hello deno").toMatch(/DENO/); + }, AssertionError); +}); diff --git a/expect/_to_strict_equal_test.ts b/expect/_to_strict_equal_test.ts index 70fda0cb5c85..03874b88fbbd 100644 --- a/expect/_to_strict_equal_test.ts +++ b/expect/_to_strict_equal_test.ts @@ -9,6 +9,10 @@ Deno.test("expect().toStrictEqual()", () => { expect("a").toStrictEqual("a"); expect(obj).toStrictEqual(obj); + expect(1).not.toStrictEqual(2); + expect("a").not.toStrictEqual("b"); + expect(obj).not.toStrictEqual({ a: 1 }); + assertThrows(() => { expect(1).toStrictEqual(2); }, AssertionError); diff --git a/expect/expect.ts b/expect/expect.ts index c194ba98dda1..e47ec633f72a 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -37,11 +37,11 @@ export interface Expected { toBeCloseTo(candidate: number, tolerance?: number): void; toBeDefined(): void; toBeFalsy(): void; - toBeGreater(expected: number): void; - toBeGreaterOrEqual(expected: number): void; + toBeGreaterThan(expected: number): void; + toBeGreaterThanOrEqual(expected: number): void; toBeInstanceOf(expected: T): void; - toBeLess(expected: number): void; - toBeLessOrEqual(expected: number): void; + toBeLessThan(expected: number): void; + toBeLessThanOrEqual(expected: number): void; toBeNaN(): void; toBeNull(): void; toBeTruthy(): void; @@ -69,7 +69,9 @@ export interface Expected { rejects: Async; } -const matchers: Record = { +type MatcherKey = keyof Omit; + +const matchers: Record = { toBeCloseTo, toBeDefined, toBeFalsy, @@ -139,7 +141,7 @@ export function expect(value: unknown, customMessage?: string): Expected { return self; } - const matcher: Matcher = matchers[name as string]; + const matcher: Matcher = matchers[name as MatcherKey]; if (!matcher) { throw new TypeError( typeof name === "string" From 46b27ac5c17ae53fb993a98461a5d9f95cf5c079 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Fri, 17 Nov 2023 11:02:28 +0900 Subject: [PATCH 07/17] wip --- expect/_to_be_defined_test.ts | 7 ++++ expect/_to_be_falsy.ts | 1 - expect/_to_be_falsy_test.ts | 10 ++++++ expect/_to_be_greater_than_or_equal_test.ts | 4 +++ expect/_to_be_greater_than_test.ts | 4 +++ expect/_to_be_instance_of_test.ts | 7 ++++ expect/_to_be_less_than_or_equal_test.ts | 7 ++++ expect/_to_be_less_than_test.ts | 4 +++ expect/_to_be_nan_test.ts | 4 +++ expect/_to_be_null_test.ts | 4 +++ expect/_to_be_test.ts | 10 ++++++ expect/_to_be_truthy_test.ts | 10 ++++++ expect/_to_be_undefined_test.ts | 4 +++ expect/_to_contain.ts | 21 +++++++++++ expect/_to_contain_equal.ts | 40 +++++++++++++++++++++ expect/_to_contain_test.ts | 28 +++++++++++++++ expect/expect.ts | 10 ++++-- 17 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 expect/_to_contain.ts create mode 100644 expect/_to_contain_equal.ts create mode 100644 expect/_to_contain_test.ts diff --git a/expect/_to_be_defined_test.ts b/expect/_to_be_defined_test.ts index b7ea14a32762..c32b7467be94 100644 --- a/expect/_to_be_defined_test.ts +++ b/expect/_to_be_defined_test.ts @@ -17,4 +17,11 @@ Deno.test("expect().toBeDefined()", () => { assertThrows(() => { expect(({} as any).foo).toBeDefined(); }, AssertionError); + + assertThrows(() => { + expect(1).not.toBeDefined(); + }, AssertionError); + assertThrows(() => { + expect("a").not.toBeDefined(); + }, AssertionError); }); diff --git a/expect/_to_be_falsy.ts b/expect/_to_be_falsy.ts index b040f377414f..bfc10c5667b5 100644 --- a/expect/_to_be_falsy.ts +++ b/expect/_to_be_falsy.ts @@ -13,7 +13,6 @@ export function toBeFalsy( `Expected ${context.value} to NOT be falsy`, ); } - return; } else { if (!isFalsy) { throw new AssertionError( diff --git a/expect/_to_be_falsy_test.ts b/expect/_to_be_falsy_test.ts index 9528191fedf5..b376ba40b98b 100644 --- a/expect/_to_be_falsy_test.ts +++ b/expect/_to_be_falsy_test.ts @@ -21,4 +21,14 @@ Deno.test("expect().toBeFalsy()", () => { assertThrows(() => { expect("hello").toBeFalsy(); }, AssertionError); + + assertThrows(() => { + expect(false).not.toBeFalsy(); + }, AssertionError); + assertThrows(() => { + expect(0).not.toBeFalsy(); + }, AssertionError); + assertThrows(() => { + expect("").not.toBeFalsy(); + }, AssertionError); }); diff --git a/expect/_to_be_greater_than_or_equal_test.ts b/expect/_to_be_greater_than_or_equal_test.ts index a7332fe32930..95757ba6d9f2 100644 --- a/expect/_to_be_greater_than_or_equal_test.ts +++ b/expect/_to_be_greater_than_or_equal_test.ts @@ -12,4 +12,8 @@ Deno.test("expect().toBeGreaterThanOrEqual()", () => { assertThrows(() => { expect(9).toBeGreaterThanOrEqual(10); }, AssertionError); + + assertThrows(() => { + expect(11).not.toBeGreaterThanOrEqual(10); + }, AssertionError); }); diff --git a/expect/_to_be_greater_than_test.ts b/expect/_to_be_greater_than_test.ts index de38d6b75124..b52a924852a3 100644 --- a/expect/_to_be_greater_than_test.ts +++ b/expect/_to_be_greater_than_test.ts @@ -15,4 +15,8 @@ Deno.test("expect().toBeGreaterThan()", () => { assertThrows(() => { expect(9).toBeGreaterThan(10); }); + + assertThrows(() => { + expect(11).not.toBeGreaterThan(10); + }, AssertionError); }); diff --git a/expect/_to_be_instance_of_test.ts b/expect/_to_be_instance_of_test.ts index 48863e4fa460..39f07bc2e568 100644 --- a/expect/_to_be_instance_of_test.ts +++ b/expect/_to_be_instance_of_test.ts @@ -12,4 +12,11 @@ Deno.test("expect().toBeInstanceOf", () => { assertThrows(() => { expect(new Error()).toBeInstanceOf(String); }, AssertionError); + + assertThrows(() => { + expect(new Error()).not.toBeInstanceOf(Error); + }, AssertionError); + assertThrows(() => { + expect(new Error()).not.toBeInstanceOf(Object); + }, AssertionError); }); diff --git a/expect/_to_be_less_than_or_equal_test.ts b/expect/_to_be_less_than_or_equal_test.ts index 134cde1bc1b2..aa7d7d058738 100644 --- a/expect/_to_be_less_than_or_equal_test.ts +++ b/expect/_to_be_less_than_or_equal_test.ts @@ -12,4 +12,11 @@ Deno.test("expect().toBeLessThanOrEqual", () => { assertThrows(() => { expect(11).toBeLessThanOrEqual(10); }, AssertionError); + + assertThrows(() => { + expect(10).not.toBeLessThanOrEqual(10); + }, AssertionError); + assertThrows(() => { + expect(9).not.toBeLessThanOrEqual(10); + }, AssertionError); }); diff --git a/expect/_to_be_less_than_test.ts b/expect/_to_be_less_than_test.ts index 479a37126a21..cf6749b837d2 100644 --- a/expect/_to_be_less_than_test.ts +++ b/expect/_to_be_less_than_test.ts @@ -15,4 +15,8 @@ Deno.test("expect().toBeLessThan()", () => { assertThrows(() => { expect(11).toBeLessThan(10); }, AssertionError); + + assertThrows(() => { + expect(9).not.toBeLessThan(10); + }, AssertionError); }); diff --git a/expect/_to_be_nan_test.ts b/expect/_to_be_nan_test.ts index 6e9f6a3ff081..603083daabf2 100644 --- a/expect/_to_be_nan_test.ts +++ b/expect/_to_be_nan_test.ts @@ -11,4 +11,8 @@ Deno.test("expect().toBeNaN()", () => { assertThrows(() => { expect(1).toBeNaN(); }, AssertionError); + + assertThrows(() => { + expect(NaN).not.toBeNaN(); + }, AssertionError); }); diff --git a/expect/_to_be_null_test.ts b/expect/_to_be_null_test.ts index e66aec2a19dc..3b79efab99dc 100644 --- a/expect/_to_be_null_test.ts +++ b/expect/_to_be_null_test.ts @@ -11,4 +11,8 @@ Deno.test("expect().toBeNull()", () => { assertThrows(() => { expect(undefined).toBeNull(); }, AssertionError); + + assertThrows(() => { + expect(null).not.toBeNull(); + }, AssertionError); }); diff --git a/expect/_to_be_test.ts b/expect/_to_be_test.ts index a7b5b4d60e92..c90fccaedc30 100644 --- a/expect/_to_be_test.ts +++ b/expect/_to_be_test.ts @@ -22,4 +22,14 @@ Deno.test("expect().toBe()", () => { assertThrows(() => { expect(obj).toBe({}); }, AssertionError); + + assertThrows(() => { + expect(1).not.toBe(1); + }, AssertionError); + assertThrows(() => { + expect("hello").not.toBe("hello"); + }, AssertionError); + assertThrows(() => { + expect(obj).not.toBe(obj); + }, AssertionError); }); diff --git a/expect/_to_be_truthy_test.ts b/expect/_to_be_truthy_test.ts index dc442613864b..8d7c28852ab4 100644 --- a/expect/_to_be_truthy_test.ts +++ b/expect/_to_be_truthy_test.ts @@ -25,4 +25,14 @@ Deno.test("expect().toBeTruthy()", () => { assertThrows(() => { expect(undefined).toBeTruthy(); }, AssertionError); + + assertThrows(() => { + expect(1).not.toBeTruthy(); + }, AssertionError); + assertThrows(() => { + expect("hello").not.toBeTruthy(); + }, AssertionError); + assertThrows(() => { + expect({}).not.toBeTruthy(); + }, AssertionError); }); diff --git a/expect/_to_be_undefined_test.ts b/expect/_to_be_undefined_test.ts index c4a23faebe0b..d44041411567 100644 --- a/expect/_to_be_undefined_test.ts +++ b/expect/_to_be_undefined_test.ts @@ -11,4 +11,8 @@ Deno.test("expect().toBeUndefined()", () => { assertThrows(() => { expect(null).toBeUndefined(); }, AssertionError); + + assertThrows(() => { + expect(undefined).not.toBeUndefined(); + }, AssertionError); }); diff --git a/expect/_to_contain.ts b/expect/_to_contain.ts new file mode 100644 index 000000000000..02ae4315b4ce --- /dev/null +++ b/expect/_to_contain.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { AssertionError } from "../assert/assertion_error.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toContain( + context: MatcherContext, + expected: unknown, +): MatchResult { + // deno-lint-ignore no-explicit-any + const doesContain = (context.value as any)?.includes?.(expected); + if (context.isNot) { + if (doesContain) { + throw new AssertionError("The value contains the expected item"); + } + } else { + if (!doesContain) { + throw new AssertionError("The value doesn't contain the expected item"); + } + } +} diff --git a/expect/_to_contain_equal.ts b/expect/_to_contain_equal.ts new file mode 100644 index 000000000000..1b4c582a08d1 --- /dev/null +++ b/expect/_to_contain_equal.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; +import { MatcherContext, MatchResult } from "./_types.ts"; + +export function toContainEqual( + context: MatcherContext, + expected: unknown, +): MatchResult { + const { value } = context; + assertIsIterable(value); + let doesContain = false; + for (const item of value) { + if (equal(item, expected)) { + doesContain = true; + break; + } + } + + if (context.isNot) { + if (doesContain) { + throw new AssertionError("The value contains the expected item"); + } + } else { + if (!doesContain) { + throw new AssertionError("The value doesn't contain the expected item"); + } + } +} + +// deno-lint-ignore no-explicit-any +function assertIsIterable(value: any): asserts value is Iterable { + if (value == null) { + throw new AssertionError("The value is null or undefined"); + } + if (typeof value[Symbol.iterator] !== "function") { + throw new AssertionError("The value is not iterable"); + } +} diff --git a/expect/_to_contain_test.ts b/expect/_to_contain_test.ts new file mode 100644 index 000000000000..b50e76f2d910 --- /dev/null +++ b/expect/_to_contain_test.ts @@ -0,0 +1,28 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toContain()", () => { + const arr = [1, 2, 3]; + + expect(arr).toContain(2); + expect("foobarbaz").toContain("bar"); + + expect(arr).not.toContain(4); + expect("foobarbaz").not.toContain("qux"); + + assertThrows(() => { + expect(arr).toContain(4); + }, AssertionError); + assertThrows(() => { + expect("foobarbaz").toContain("qux"); + }, AssertionError); + + assertThrows(() => { + expect(arr).not.toContain(2); + }, AssertionError); + assertThrows(() => { + expect("foobarbaz").not.toContain("bar"); + }, AssertionError); +}); diff --git a/expect/expect.ts b/expect/expect.ts index e47ec633f72a..b6f66238f04d 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -16,6 +16,8 @@ import { toBeNull } from "./_to_be_null.ts"; import { toBeTruthy } from "./_to_be_truthy.ts"; import { toBeUndefined } from "./_to_be_undefined.ts"; import { toBe } from "./_to_be.ts"; +import { toContain } from "./_to_contain.ts"; +import { toContainEqual } from "./_to_contain_equal.ts"; import { toEqual } from "./_to_equal.ts"; import { toHaveBeenCalledTimes } from "./_to_have_been_called_times.ts"; import { toHaveBeenCalledWith } from "./_to_have_been_called_with.ts"; @@ -46,8 +48,10 @@ export interface Expected { toBeNull(): void; toBeTruthy(): void; toBeUndefined(): void; - toBe(candidate: unknown): void; - toEqual(candidate: unknown): void; + toBe(expected: unknown): void; + toContainEqual(expected: unknown): void; + toContain(expected: unknown): void; + toEqual(expected: unknown): void; toHaveBeenCalledTimes(expected: number): void; toHaveBeenCalledWith(...expected: unknown[]): void; toHaveBeenCalled(): void; @@ -85,6 +89,8 @@ const matchers: Record = { toBeTruthy, toBeUndefined, toBe, + toContainEqual, + toContain, toEqual, toHaveBeenCalledTimes, toHaveBeenCalledWith, From 0b430602af502012a0b746fdd2ccf1c9e1d6b4b6 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 15:15:27 +0900 Subject: [PATCH 08/17] mock utils --- expect/_mock_util.ts | 22 ++++++++++++++++++++++ expect/_to_contain.ts | 1 + expect/_to_contain_equal_test.ts | 19 +++++++++++++++++++ expect/_to_have_been_called.ts | 18 ++++++++++++++++++ expect/_to_have_been_called_test.ts | 7 ++++++- expect/_types.ts | 3 +-- expect/fn.ts | 20 ++------------------ 7 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 expect/_mock_util.ts create mode 100644 expect/_to_contain_equal_test.ts diff --git a/expect/_mock_util.ts b/expect/_mock_util.ts new file mode 100644 index 000000000000..4a998b0e318b --- /dev/null +++ b/expect/_mock_util.ts @@ -0,0 +1,22 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +export const MOCK_SYMBOL = Symbol.for("@MOCK"); + +export type MockCall = { + args: any[]; + returned?: any; + thrown?: any; + timestamp: number; + returns: boolean; + throws: boolean; +}; + +export function getMockCalls(f: any): MockCall[] { + const mockInfo = f[MOCK_SYMBOL]; + if (!mockInfo) { + throw new Error("Received function must be a mock or spy function"); + } + + return [...mockInfo.calls]; +} diff --git a/expect/_to_contain.ts b/expect/_to_contain.ts index 02ae4315b4ce..6b34cddc481b 100644 --- a/expect/_to_contain.ts +++ b/expect/_to_contain.ts @@ -9,6 +9,7 @@ export function toContain( ): MatchResult { // deno-lint-ignore no-explicit-any const doesContain = (context.value as any)?.includes?.(expected); + if (context.isNot) { if (doesContain) { throw new AssertionError("The value contains the expected item"); diff --git a/expect/_to_contain_equal_test.ts b/expect/_to_contain_equal_test.ts new file mode 100644 index 000000000000..3db06fcb93c9 --- /dev/null +++ b/expect/_to_contain_equal_test.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { expect } from "./expect.ts"; +import { AssertionError, assertThrows } from "../assert/mod.ts"; + +Deno.test("expect().toContainEqual()", () => { + const arr = [{ foo: 42 }, { bar: 43 }, { baz: 44 }]; + expect(arr).toContainEqual({ bar: 43 }); + + expect(arr).not.toContainEqual({ bar: 42 }); + + assertThrows(() => { + expect(arr).toContainEqual({ bar: 42 }); + }, AssertionError); + + assertThrows(() => { + expect(arr).not.toContainEqual({ bar: 43 }); + }, AssertionError); +}); diff --git a/expect/_to_have_been_called.ts b/expect/_to_have_been_called.ts index 25081754c136..a7bd4f61bc8a 100644 --- a/expect/_to_have_been_called.ts +++ b/expect/_to_have_been_called.ts @@ -1,6 +1,24 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { getMockCalls } from "./_mock_util.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; export function toHaveBeenCalled(context: MatcherContext): MatchResult { + const calls = getMockCalls(context.value); + const hasBeenCalled = calls.length > 0; + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected mock function not to be called, but it was called ${calls.length} time(s)`, + ); + } + } else { + if (!hasBeenCalled) { + throw new AssertionError( + `Expected mock function to be called, but it was not called`, + ); + } + } } diff --git a/expect/_to_have_been_called_test.ts b/expect/_to_have_been_called_test.ts index fe147701a064..00d41a1cefd6 100644 --- a/expect/_to_have_been_called_test.ts +++ b/expect/_to_have_been_called_test.ts @@ -1,5 +1,10 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; -Deno.test("expect().toHaveBeenCalled()", () => {}); +Deno.test("expect().toHaveBeenCalled()", () => { + const mockFn = fn(); + mockFn(); + expect(mockFn).toHaveBeenCalled(); +}); diff --git a/expect/_types.ts b/expect/_types.ts index 5862c2913881..eba03d095a20 100644 --- a/expect/_types.ts +++ b/expect/_types.ts @@ -1,4 +1,5 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any export interface MatcherContext { value: unknown; @@ -8,7 +9,6 @@ export interface MatcherContext { export type Matcher = ( context: MatcherContext, - // deno-lint-ignore no-explicit-any ...args: any[] ) => MatchResult; @@ -16,5 +16,4 @@ export type Matchers = { [key: string]: Matcher; }; export type MatchResult = void | Promise | boolean; -// deno-lint-ignore no-explicit-any export type AnyConstructor = new (...args: any[]) => any; diff --git a/expect/fn.ts b/expect/fn.ts index fc763d942e06..6db57e06db33 100644 --- a/expect/fn.ts +++ b/expect/fn.ts @@ -2,19 +2,10 @@ // Copyright 2019 Allain Lalonde. All rights reserved. ISC License. // deno-lint-ignore-file no-explicit-any ban-types -const MOCK_SYMBOL = Symbol.for("@MOCK"); - -export type _MockCall = { - args: any[]; - returned?: any; - thrown?: any; - timestamp: number; - returns: boolean; - throws: boolean; -}; +import { MOCK_SYMBOL, MockCall } from "./_mock_util.ts"; export function fn(...stubs: Function[]) { - const calls: _MockCall[] = []; + const calls: MockCall[] = []; const f = (...args: any[]) => { const stub = stubs.length === 1 @@ -52,10 +43,3 @@ export function fn(...stubs: Function[]) { 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]; -} From 4a40f4280ef6c37cd3cc9ad098883ee1e94f2b5d Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 15:54:29 +0900 Subject: [PATCH 09/17] add toHaveBeenCalledWith --- expect/_inspect_args.ts | 11 +++++++++ expect/_to_have_been_called_times.ts | 17 +++++++++++++ expect/_to_have_been_called_times_test.ts | 17 ++++++++++++- expect/_to_have_been_called_with.ts | 30 +++++++++++++++++++++++ expect/_to_have_been_called_with_test.ts | 18 +++++++++++++- expect/_to_have_been_last_called_with.ts | 3 +++ expect/_to_have_been_nth_called_with.ts | 2 ++ expect/_to_have_last_returned_with.ts | 2 ++ expect/_to_have_nth_returned_with.ts | 2 ++ expect/_to_have_returned.ts | 2 ++ expect/_to_have_returned_times.ts | 2 ++ expect/_to_have_returned_with.ts | 2 ++ expect/_to_match_object_test.ts | 4 +++ expect/_to_match_test.ts | 4 +++ expect/_to_strict_equal_test.ts | 10 +++++--- expect/_to_throw_test.ts | 6 +++++ 16 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 expect/_inspect_args.ts diff --git a/expect/_inspect_args.ts b/expect/_inspect_args.ts new file mode 100644 index 000000000000..57236b5ba017 --- /dev/null +++ b/expect/_inspect_args.ts @@ -0,0 +1,11 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export function inspectArgs(args: unknown[]): string { + return args.map(inspectArg).join(", "); +} + +function inspectArg(arg: unknown): string { + return typeof Deno !== "undefined" && Deno.inspect + ? Deno.inspect(arg) + : String(arg); +} diff --git a/expect/_to_have_been_called_times.ts b/expect/_to_have_been_called_times.ts index c60818be382c..a1f38d021add 100644 --- a/expect/_to_have_been_called_times.ts +++ b/expect/_to_have_been_called_times.ts @@ -1,9 +1,26 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveBeenCalledTimes( context: MatcherContext, expected: number, ): MatchResult { + const calls = getMockCalls(context.value); + + if (context.isNot) { + if (calls.length === expected) { + throw new AssertionError( + `Expected mock function not to be called ${expected} time(s), but it was`, + ); + } + } else { + if (calls.length !== expected) { + throw new AssertionError( + `Expected mock function to be called ${expected} time(s), but it was called ${calls.length} time(s)`, + ); + } + } } diff --git a/expect/_to_have_been_called_times_test.ts b/expect/_to_have_been_called_times_test.ts index d468e20863b7..a6eec1cd0601 100644 --- a/expect/_to_have_been_called_times_test.ts +++ b/expect/_to_have_been_called_times_test.ts @@ -1,6 +1,21 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveBeenCalledTimes()", () => {}); +Deno.test("expect().toHaveBeenCalledTimes()", () => { + const mockFn = fn(); + mockFn(); + expect(mockFn).toHaveBeenCalledTimes(1); + + expect(mockFn).not.toHaveBeenCalledTimes(2); + + assertThrows(() => { + expect(mockFn).toHaveBeenCalledTimes(2); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveBeenCalledTimes(1); + }, AssertionError); +}); diff --git a/expect/_to_have_been_called_with.ts b/expect/_to_have_been_called_with.ts index fd1a3a43c534..a65254a694db 100644 --- a/expect/_to_have_been_called_with.ts +++ b/expect/_to_have_been_called_with.ts @@ -1,9 +1,39 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; +import { getMockCalls } from "./_mock_util.ts"; +import { inspectArgs } from "./_inspect_args.ts"; export function toHaveBeenCalledWith( context: MatcherContext, ...expected: unknown[] ): MatchResult { + const calls = getMockCalls(context.value); + const hasBeenCalled = calls.some((call) => equal(call.args, expected)); + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected mock function not to be called with ${ + inspectArgs(expected) + }, but it was`, + ); + } + } else { + if (!hasBeenCalled) { + let otherCalls = ""; + if (calls.length > 0) { + otherCalls = `\n Other calls:\n ${ + calls.map((call) => inspectArgs(call.args)).join("\n ") + }`; + } + throw new AssertionError( + `Expected mock function to be called with ${ + inspectArgs(expected) + }, but it was not.${otherCalls}`, + ); + } + } } diff --git a/expect/_to_have_been_called_with_test.ts b/expect/_to_have_been_called_with_test.ts index ae62e01acd6a..d5cfabaec0e7 100644 --- a/expect/_to_have_been_called_with_test.ts +++ b/expect/_to_have_been_called_with_test.ts @@ -1,6 +1,22 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveBeenCalledWith()", () => {}); +Deno.test("expect().toHaveBeenCalledWith()", () => { + const mockFn = fn(); + mockFn("hello", "deno"); + + expect(mockFn).toHaveBeenCalledWith("hello", "deno"); + + expect(mockFn).not.toHaveBeenCalledWith("hello", "DENO"); + + assertThrows(() => { + expect(mockFn).toHaveBeenCalledWith("hello", "DENO"); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveBeenCalledWith("hello", "deno"); + }); +}); diff --git a/expect/_to_have_been_last_called_with.ts b/expect/_to_have_been_last_called_with.ts index 5ed7a81904c6..5208d505031b 100644 --- a/expect/_to_have_been_last_called_with.ts +++ b/expect/_to_have_been_last_called_with.ts @@ -1,6 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; +import { inspectArgs } from "./_inspect_args.ts"; export function toHaveBeenLastCalledWith( context: MatcherContext, diff --git a/expect/_to_have_been_nth_called_with.ts b/expect/_to_have_been_nth_called_with.ts index aaa5366f38ca..145a12025ed9 100644 --- a/expect/_to_have_been_nth_called_with.ts +++ b/expect/_to_have_been_nth_called_with.ts @@ -1,6 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveBeenNthCalledWith( _context: MatcherContext, diff --git a/expect/_to_have_last_returned_with.ts b/expect/_to_have_last_returned_with.ts index 5bda66481d12..52fe42a86e16 100644 --- a/expect/_to_have_last_returned_with.ts +++ b/expect/_to_have_last_returned_with.ts @@ -1,6 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveLastReturnedWith( context: MatcherContext, diff --git a/expect/_to_have_nth_returned_with.ts b/expect/_to_have_nth_returned_with.ts index aa62b4a924e3..526948044aca 100644 --- a/expect/_to_have_nth_returned_with.ts +++ b/expect/_to_have_nth_returned_with.ts @@ -1,6 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveNthReturnedWith( context: MatcherContext, diff --git a/expect/_to_have_returned.ts b/expect/_to_have_returned.ts index 14281b0902e9..a40f33399479 100644 --- a/expect/_to_have_returned.ts +++ b/expect/_to_have_returned.ts @@ -1,6 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveReturned(context: MatcherContext): MatchResult { } diff --git a/expect/_to_have_returned_times.ts b/expect/_to_have_returned_times.ts index 878ade5809c4..8317c683bf8b 100644 --- a/expect/_to_have_returned_times.ts +++ b/expect/_to_have_returned_times.ts @@ -1,6 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveReturnedTimes( context: MatcherContext, diff --git a/expect/_to_have_returned_with.ts b/expect/_to_have_returned_with.ts index 878aca67cfa8..e424ca0b6652 100644 --- a/expect/_to_have_returned_with.ts +++ b/expect/_to_have_returned_with.ts @@ -1,6 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { getMockCalls } from "./_mock_util.ts"; export function toHaveReturnedWith( context: MatcherContext, diff --git a/expect/_to_match_object_test.ts b/expect/_to_match_object_test.ts index 09c9dd638622..96a9d8f3473e 100644 --- a/expect/_to_match_object_test.ts +++ b/expect/_to_match_object_test.ts @@ -37,4 +37,8 @@ Deno.test("expect().toMatchObject()", () => { assertThrows(() => { expect(house1).toMatchObject(desiredHouse); }, AssertionError); + + assertThrows(() => { + expect(house0).not.toMatchObject(desiredHouse); + }, AssertionError); }); diff --git a/expect/_to_match_test.ts b/expect/_to_match_test.ts index 6f2345fe3252..7b5ad0147d75 100644 --- a/expect/_to_match_test.ts +++ b/expect/_to_match_test.ts @@ -11,4 +11,8 @@ Deno.test("expect().toMatch()", () => { assertThrows(() => { expect("hello deno").toMatch(/DENO/); }, AssertionError); + + assertThrows(() => { + expect("hello deno").not.toMatch(/deno/); + }); }); diff --git a/expect/_to_strict_equal_test.ts b/expect/_to_strict_equal_test.ts index 03874b88fbbd..11d6da6787b1 100644 --- a/expect/_to_strict_equal_test.ts +++ b/expect/_to_strict_equal_test.ts @@ -6,20 +6,22 @@ import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toStrictEqual()", () => { const obj = { a: 1 }; expect(1).toStrictEqual(1); - expect("a").toStrictEqual("a"); expect(obj).toStrictEqual(obj); expect(1).not.toStrictEqual(2); - expect("a").not.toStrictEqual("b"); expect(obj).not.toStrictEqual({ a: 1 }); assertThrows(() => { expect(1).toStrictEqual(2); }, AssertionError); assertThrows(() => { - expect("a").toStrictEqual("b"); + expect(obj).toStrictEqual({ a: 1 }); }, AssertionError); + assertThrows(() => { - expect(obj).toStrictEqual({ a: 1 }); + expect(1).not.toStrictEqual(1); + }, AssertionError); + assertThrows(() => { + expect(obj).not.toStrictEqual(obj); }, AssertionError); }); diff --git a/expect/_to_throw_test.ts b/expect/_to_throw_test.ts index 1bfad3bb78e7..efd863d69936 100644 --- a/expect/_to_throw_test.ts +++ b/expect/_to_throw_test.ts @@ -13,4 +13,10 @@ Deno.test("expect().toThrow()", () => { assertThrows(() => { expect(() => {}).toThrow(); }, AssertionError); + + assertThrows(() => { + expect(() => { + throw new Error("hello world"); + }).not.toThrow(); + }, AssertionError); }); From 771af12a623141ce5a1fbd99f289f06b14c9821d Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 16:24:32 +0900 Subject: [PATCH 10/17] add alisases --- expect/expect.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/expect/expect.ts b/expect/expect.ts index b6f66238f04d..b365655b8b7c 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -36,6 +36,13 @@ import { toStrictEqual } from "./_to_strict_equal.ts"; import { toThrow } from "./_to_throw.ts"; export interface Expected { + lastCalledWith(...expected: unknown[]): void; + lastReturnedWith(expected: unknown): void; + nthCalledWith(nth: number, ...expected: unknown[]): void; + nthReturnedWith(nth: number, expected: unknown): void; + toBeCalled(): void; + toBeCalledTimes(expected: number): void; + toBeCalledWith(...expected: unknown[]): void; toBeCloseTo(candidate: number, tolerance?: number): void; toBeDefined(): void; toBeFalsy(): void; @@ -65,6 +72,9 @@ export interface Expected { toHaveReturned(): void; toMatch(expected: RegExp): void; toMatchObject(expected: Record): void; + toReturn(): void; + toReturnTimes(expected: number): void; + toReturnWith(expected: unknown): void; toStrictEqual(candidate: unknown): void; // deno-lint-ignore no-explicit-any toThrow(expected?: new (...args: any[]) => E): void; @@ -76,6 +86,13 @@ export interface Expected { type MatcherKey = keyof Omit; const matchers: Record = { + lastCalledWith: toHaveBeenLastCalledWith, + lastReturnedWith: toHaveReturnedWith, + nthCalledWith: toHaveBeenNthCalledWith, + nthReturnedWith: toHaveNthReturnedWith, + toBeCalled: toHaveBeenCalled, + toBeCalledTimes: toHaveBeenCalledTimes, + toBeCalledWith: toHaveBeenCalledWith, toBeCloseTo, toBeDefined, toBeFalsy, @@ -105,6 +122,9 @@ const matchers: Record = { toHaveReturned, toMatchObject, toMatch, + toReturn: toHaveReturned, + toReturnTimes: toHaveReturnedTimes, + toReturnWith: toHaveReturnedWith, toStrictEqual, toThrow, }; From 1cd90fe2880ac608dd85bc538ee5c6570f886bdc Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 16:50:07 +0900 Subject: [PATCH 11/17] implement toHaveBeenLastCalledWith, toHaveBeenNthCalledWith --- expect/_to_have_been_last_called_with.ts | 31 +++++++++++++++ expect/_to_have_been_last_called_with_test.ts | 20 +++++++++- expect/_to_have_been_nth_called_with.ts | 39 ++++++++++++++++++- expect/_to_have_been_nth_called_with_test.ts | 25 +++++++++++- expect/_to_have_last_returned_with_test.ts | 5 ++- expect/_to_have_nth_returned_with_test.ts | 5 ++- expect/_to_have_returned_test.ts | 5 ++- expect/_to_have_returned_times_test.ts | 5 ++- expect/_to_have_returned_with_test.ts | 5 ++- 9 files changed, 132 insertions(+), 8 deletions(-) diff --git a/expect/_to_have_been_last_called_with.ts b/expect/_to_have_been_last_called_with.ts index 5208d505031b..41712cc96743 100644 --- a/expect/_to_have_been_last_called_with.ts +++ b/expect/_to_have_been_last_called_with.ts @@ -2,6 +2,7 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; import { inspectArgs } from "./_inspect_args.ts"; @@ -9,4 +10,34 @@ export function toHaveBeenLastCalledWith( context: MatcherContext, ...expected: unknown[] ): MatchResult { + const calls = getMockCalls(context.value); + const hasBeenCalled = calls.length > 0 && + equal(calls[calls.length - 1].args, expected); + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected mock function not to be last called with ${ + inspectArgs(expected) + }, but it was`, + ); + } + } else { + if (!hasBeenCalled) { + const lastCall = calls.at(-1); + if (!lastCall) { + throw new AssertionError( + `Expected mock function to be last called with ${ + inspectArgs(expected) + }, but it was not.`, + ); + } else { + throw new AssertionError( + `Expected mock function to be last called with ${ + inspectArgs(expected) + }, but it was last called with ${inspectArgs(lastCall.args)}.`, + ); + } + } + } } diff --git a/expect/_to_have_been_last_called_with_test.ts b/expect/_to_have_been_last_called_with_test.ts index f3b08ea3e1d2..9312f6f89983 100644 --- a/expect/_to_have_been_last_called_with_test.ts +++ b/expect/_to_have_been_last_called_with_test.ts @@ -1,6 +1,24 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveBeenLastCalledWith()", () => {}); +Deno.test("expect().toHaveBeenLastCalledWith()", () => { + const mockFn = fn(); + + mockFn(1, 2, 3); + mockFn(4, 5, 6); + + expect(mockFn).toHaveBeenLastCalledWith(4, 5, 6); + + expect(mockFn).not.toHaveBeenLastCalledWith(1, 2, 3); + + assertThrows(() => { + expect(mockFn).toHaveBeenLastCalledWith(1, 2, 3); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveBeenLastCalledWith(4, 5, 6); + }, AssertionError); +}); diff --git a/expect/_to_have_been_nth_called_with.ts b/expect/_to_have_been_nth_called_with.ts index 145a12025ed9..bc62ea91b2d9 100644 --- a/expect/_to_have_been_nth_called_with.ts +++ b/expect/_to_have_been_nth_called_with.ts @@ -2,11 +2,48 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; +import { inspectArgs } from "./_inspect_args.ts"; export function toHaveBeenNthCalledWith( - _context: MatcherContext, + context: MatcherContext, nth: number, ...expected: unknown[] ): MatchResult { + if (nth < 1) { + new Error(`nth must be greater than 0. ${nth} was given.`); + } + + const calls = getMockCalls(context.value); + const callIndex = nth - 1; + const hasBeenCalled = calls.length > callIndex && + equal(calls[callIndex].args, expected); + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected the n-th call (n=${nth}) of mock function is not with ${ + inspectArgs(expected) + }, but it was`, + ); + } + } else { + if (!hasBeenCalled) { + const nthCall = calls[callIndex]; + if (!nth) { + throw new AssertionError( + `Expected the n-th call (n=${nth}) of mock function is with ${ + inspectArgs(expected) + }, but the n-th call does not exist.`, + ); + } else { + throw new AssertionError( + `Expected the n-th call (n=${nth}) of mock function is with ${ + inspectArgs(expected) + }, but it was with ${inspectArgs(nthCall.args)}.`, + ); + } + } + } } diff --git a/expect/_to_have_been_nth_called_with_test.ts b/expect/_to_have_been_nth_called_with_test.ts index 91a301151af1..6a268ccf18ab 100644 --- a/expect/_to_have_been_nth_called_with_test.ts +++ b/expect/_to_have_been_nth_called_with_test.ts @@ -1,6 +1,29 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().", () => {}); +Deno.test("expect().", () => { + const mockFn = fn(); + + mockFn(1, 2, 3); + mockFn(4, 5, 6); + mockFn(7, 8, 9); + + expect(mockFn).toHaveBeenNthCalledWith(2, 4, 5, 6); + + expect(mockFn).not.toHaveBeenNthCalledWith(2, 1, 2, 3); + expect(mockFn).not.toHaveBeenNthCalledWith(1, 4, 5, 6); + + assertThrows(() => { + expect(mockFn).toHaveBeenNthCalledWith(2, 1, 2, 3); + }, AssertionError); + assertThrows(() => { + expect(mockFn).toHaveBeenNthCalledWith(1, 4, 5, 6); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveBeenNthCalledWith(2, 4, 5, 6); + }); +}); diff --git a/expect/_to_have_last_returned_with_test.ts b/expect/_to_have_last_returned_with_test.ts index 09c8f7697828..f7551f6ff2c3 100644 --- a/expect/_to_have_last_returned_with_test.ts +++ b/expect/_to_have_last_returned_with_test.ts @@ -1,6 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveLastReturnedWith()", () => {}); +Deno.test("expect().toHaveLastReturnedWith()", () => { + const mockFn = fn(); +}); diff --git a/expect/_to_have_nth_returned_with_test.ts b/expect/_to_have_nth_returned_with_test.ts index 5c83cf15e4e8..96e5558dcde0 100644 --- a/expect/_to_have_nth_returned_with_test.ts +++ b/expect/_to_have_nth_returned_with_test.ts @@ -1,6 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveNthReturnedWith()", () => {}); +Deno.test("expect().toHaveNthReturnedWith()", () => { + const mockFn = fn(); +}); diff --git a/expect/_to_have_returned_test.ts b/expect/_to_have_returned_test.ts index 72e18ff7c250..986323f709a7 100644 --- a/expect/_to_have_returned_test.ts +++ b/expect/_to_have_returned_test.ts @@ -1,6 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveReturned()", () => {}); +Deno.test("expect().toHaveReturned()", () => { + const mockFn = fn(); +}); diff --git a/expect/_to_have_returned_times_test.ts b/expect/_to_have_returned_times_test.ts index e0f7ae2564f3..4c4d012299bd 100644 --- a/expect/_to_have_returned_times_test.ts +++ b/expect/_to_have_returned_times_test.ts @@ -1,6 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveReturnedTimes()", () => {}); +Deno.test("expect().toHaveReturnedTimes()", () => { + const mockFn = fn(); +}); diff --git a/expect/_to_have_returned_with_test.ts b/expect/_to_have_returned_with_test.ts index 91dbe3abce2f..199331b16255 100644 --- a/expect/_to_have_returned_with_test.ts +++ b/expect/_to_have_returned_with_test.ts @@ -1,6 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { expect } from "./expect.ts"; +import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveReturnedWith()", () => {}); +Deno.test("expect().toHaveReturnedWith()", () => { + const mockFn = fn(); +}); From 2663f62006968e0192abd716a144504c605ebb16 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 17:24:35 +0900 Subject: [PATCH 12/17] toHaveProperty --- expect/_inspect_args.ts | 2 +- expect/_to_have_last_returned_with.ts | 1 + expect/_to_have_length.ts | 19 +++++++++ expect/_to_have_lenth_test.ts | 22 +++++++++- expect/_to_have_nth_returned_with.ts | 1 + expect/_to_have_property.ts | 58 ++++++++++++++++++++++++++- expect/_to_have_property_test.ts | 19 ++++++++- expect/_to_have_returned_with.ts | 1 + expect/expect.ts | 2 +- 9 files changed, 119 insertions(+), 6 deletions(-) diff --git a/expect/_inspect_args.ts b/expect/_inspect_args.ts index 57236b5ba017..3aafc40c37d4 100644 --- a/expect/_inspect_args.ts +++ b/expect/_inspect_args.ts @@ -4,7 +4,7 @@ export function inspectArgs(args: unknown[]): string { return args.map(inspectArg).join(", "); } -function inspectArg(arg: unknown): string { +export function inspectArg(arg: unknown): string { return typeof Deno !== "undefined" && Deno.inspect ? Deno.inspect(arg) : String(arg); diff --git a/expect/_to_have_last_returned_with.ts b/expect/_to_have_last_returned_with.ts index 52fe42a86e16..c807d841cd54 100644 --- a/expect/_to_have_last_returned_with.ts +++ b/expect/_to_have_last_returned_with.ts @@ -2,6 +2,7 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; export function toHaveLastReturnedWith( diff --git a/expect/_to_have_length.ts b/expect/_to_have_length.ts index d1400c217322..eb44b90f8623 100644 --- a/expect/_to_have_length.ts +++ b/expect/_to_have_length.ts @@ -1,9 +1,28 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; export function toHaveLength( context: MatcherContext, expected: number, ): MatchResult { + const { value } = context; + // deno-lint-ignore no-explicit-any + const maybeLength = (value as any)?.length; + const hasLength = maybeLength === expected; + + if (context.isNot) { + if (hasLength) { + throw new AssertionError( + `Expected value not to have length ${expected}, but it does`, + ); + } + } else { + if (!hasLength) { + throw new AssertionError( + `Expected value to have length ${expected}, but it does not. (The value has length ${maybeLength})`, + ); + } + } } diff --git a/expect/_to_have_lenth_test.ts b/expect/_to_have_lenth_test.ts index 84e890ce2449..a7184010216c 100644 --- a/expect/_to_have_lenth_test.ts +++ b/expect/_to_have_lenth_test.ts @@ -3,4 +3,24 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveLength()", () => {}); +Deno.test("expect().toHaveLength()", () => { + expect([1, 2, 3]).toHaveLength(3); + expect("abc").toHaveLength(3); + + expect([1, 2, 3]).not.toHaveLength(4); + expect("abc").not.toHaveLength(4); + + assertThrows(() => { + expect([1, 2, 3]).toHaveLength(4); + }, AssertionError); + assertThrows(() => { + expect("abc").toHaveLength(4); + }, AssertionError); + + assertThrows(() => { + expect([1, 2, 3]).not.toHaveLength(3); + }, AssertionError); + assertThrows(() => { + expect("abc").not.toHaveLength(3); + }, AssertionError); +}); diff --git a/expect/_to_have_nth_returned_with.ts b/expect/_to_have_nth_returned_with.ts index 526948044aca..f27652b76354 100644 --- a/expect/_to_have_nth_returned_with.ts +++ b/expect/_to_have_nth_returned_with.ts @@ -2,6 +2,7 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; export function toHaveNthReturnedWith( diff --git a/expect/_to_have_property.ts b/expect/_to_have_property.ts index c0fc0d8b95c3..3838153c53c1 100644 --- a/expect/_to_have_property.ts +++ b/expect/_to_have_property.ts @@ -1,10 +1,64 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; +import { inspectArg } from "./_inspect_args.ts"; import { MatcherContext, MatchResult } from "./_types.ts"; export function toHaveProperty( context: MatcherContext, - propName: string | symbol, - value?: unknown, + propName: string | string[], + v?: unknown, ): MatchResult { + const { value } = context; + + let propPath = [] as string[]; + if (Array.isArray(propName)) { + propPath = propName; + } else { + propPath = propName.split("."); + } + + let current = value as any; + while (true) { + if (current === undefined || current === null) { + break; + } + if (propPath.length === 0) { + break; + } + const prop = propPath.shift()!; + current = current[prop]; + } + + let hasProperty; + if (v) { + hasProperty = current !== undefined && propPath.length === 0 && + equal(current, v); + } else { + hasProperty = current !== undefined && propPath.length === 0; + } + + let ofValue = ""; + if (v) { + ofValue = ` of the value ${inspectArg(v)}`; + } + + if (context.isNot) { + if (hasProperty) { + throw new AssertionError( + `Expected the value not to have the property ${ + propPath.join(".") + }${ofValue}, but it does.`, + ); + } + } else { + if (!hasProperty) { + throw new AssertionError( + `Expected the value to have the property ${ + propPath.join(".") + }${ofValue}, but it does not.`, + ); + } + } } diff --git a/expect/_to_have_property_test.ts b/expect/_to_have_property_test.ts index 27d591862cc4..d8b6015a2309 100644 --- a/expect/_to_have_property_test.ts +++ b/expect/_to_have_property_test.ts @@ -3,4 +3,21 @@ import { expect } from "./expect.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; -Deno.test("expect().toHaveProperty()", () => {}); +Deno.test("expect().toHaveProperty()", () => { + expect({ a: 1 }).toHaveProperty("a"); + expect({ a: 1 }).toHaveProperty("a", 1); + expect({ a: { b: 1 } }).toHaveProperty("a.b", 1); + expect({ a: { b: 1 } }).toHaveProperty(["a", "b"], 1); + expect({ a: { b: { c: { d: 5 } } } }).toHaveProperty("a.b.c", { d: 5 }); + expect({ a: { b: { c: { d: 5 } } } }).toHaveProperty("a.b.c.d", 5); + + expect({ a: { b: { c: { d: 5 } } } }).not.toHaveProperty("a.b.c", { d: 6 }); + + assertThrows(() => { + expect({ a: { b: { c: { d: 5 } } } }).toHaveProperty("a.b.c", { d: 6 }); + }, AssertionError); + + assertThrows(() => { + expect({ a: { b: { c: { d: 5 } } } }).not.toHaveProperty("a.b.c", { d: 5 }); + }, AssertionError); +}); diff --git a/expect/_to_have_returned_with.ts b/expect/_to_have_returned_with.ts index e424ca0b6652..9a1f25ebe927 100644 --- a/expect/_to_have_returned_with.ts +++ b/expect/_to_have_returned_with.ts @@ -2,6 +2,7 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; export function toHaveReturnedWith( diff --git a/expect/expect.ts b/expect/expect.ts index b365655b8b7c..ebeb519ebb7a 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -66,7 +66,7 @@ export interface Expected { toHaveBeenNthCalledWith(nth: number, ...expected: unknown[]): void; toHaveLength(expected: number): void; toHaveNthReturnedWith(nth: number, expected: unknown): void; - toHaveProperty(propName: string | symbol, value?: unknown): void; + toHaveProperty(propName: string | string[], value?: unknown): void; toHaveReturnedTimes(expected: number): void; toHaveReturnedWith(expected: unknown): void; toHaveReturned(): void; From 86111eaf2d503021bc8ad144c619711717ac5a4a Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 17:34:05 +0900 Subject: [PATCH 13/17] add toHaveReturned and toHaveReturnedTimes --- expect/_to_have_last_returned_with.ts | 1 + expect/_to_have_nth_returned_with.ts | 1 + expect/_to_have_returned.ts | 16 ++++++++++++++++ expect/_to_have_returned_test.ts | 24 +++++++++++++++++++++++- expect/_to_have_returned_times.ts | 16 ++++++++++++++++ expect/_to_have_returned_times_test.ts | 15 +++++++++++++++ expect/_to_have_returned_with.ts | 1 + 7 files changed, 73 insertions(+), 1 deletion(-) diff --git a/expect/_to_have_last_returned_with.ts b/expect/_to_have_last_returned_with.ts index c807d841cd54..aeca22abe33b 100644 --- a/expect/_to_have_last_returned_with.ts +++ b/expect/_to_have_last_returned_with.ts @@ -9,4 +9,5 @@ export function toHaveLastReturnedWith( context: MatcherContext, expected: unknown, ): MatchResult { + const calls = getMockCalls(context.value); } diff --git a/expect/_to_have_nth_returned_with.ts b/expect/_to_have_nth_returned_with.ts index f27652b76354..8459a9839610 100644 --- a/expect/_to_have_nth_returned_with.ts +++ b/expect/_to_have_nth_returned_with.ts @@ -10,4 +10,5 @@ export function toHaveNthReturnedWith( nth: number, expected: unknown, ): MatchResult { + const calls = getMockCalls(context.value); } diff --git a/expect/_to_have_returned.ts b/expect/_to_have_returned.ts index a40f33399479..5813100ab1ba 100644 --- a/expect/_to_have_returned.ts +++ b/expect/_to_have_returned.ts @@ -5,4 +5,20 @@ import { AssertionError } from "../assert/assertion_error.ts"; import { getMockCalls } from "./_mock_util.ts"; export function toHaveReturned(context: MatcherContext): MatchResult { + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + + if (context.isNot) { + if (returned.length > 0) { + throw new AssertionError( + `Expected the mock function to not have returned, but it returned ${returned.length} times`, + ); + } + } else { + if (returned.length === 0) { + throw new AssertionError( + `Expected the mock function to have returned, but it did not return`, + ); + } + } } diff --git a/expect/_to_have_returned_test.ts b/expect/_to_have_returned_test.ts index 986323f709a7..6c12c110ae1e 100644 --- a/expect/_to_have_returned_test.ts +++ b/expect/_to_have_returned_test.ts @@ -5,5 +5,27 @@ import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toHaveReturned()", () => { - const mockFn = fn(); + const mockFn0 = fn(); + const mockFn1 = fn(() => { + throw new Error("foo"); + }); + + mockFn0(); + try { + mockFn1(); + } catch { + // ignore + } + + expect(mockFn0).toHaveReturned(); + + expect(mockFn1).not.toHaveReturned(); + + assertThrows(() => { + expect(mockFn1).toHaveReturned(); + }, AssertionError); + + assertThrows(() => { + expect(mockFn0).not.toHaveReturned(); + }, AssertionError); }); diff --git a/expect/_to_have_returned_times.ts b/expect/_to_have_returned_times.ts index 8317c683bf8b..94e92af3b646 100644 --- a/expect/_to_have_returned_times.ts +++ b/expect/_to_have_returned_times.ts @@ -8,4 +8,20 @@ export function toHaveReturnedTimes( context: MatcherContext, expected: number, ): MatchResult { + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + + if (context.isNot) { + if (returned.length === expected) { + throw new AssertionError( + `Expected the mock function to not have returned ${expected} times, but it returned ${returned.length} times`, + ); + } + } else { + if (returned.length !== expected) { + throw new AssertionError( + `Expected the mock function to have returned ${expected} times, but it returned ${returned.length} times`, + ); + } + } } diff --git a/expect/_to_have_returned_times_test.ts b/expect/_to_have_returned_times_test.ts index 4c4d012299bd..2bd0abe3d7eb 100644 --- a/expect/_to_have_returned_times_test.ts +++ b/expect/_to_have_returned_times_test.ts @@ -6,4 +6,19 @@ import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toHaveReturnedTimes()", () => { const mockFn = fn(); + + mockFn(); + mockFn(); + + expect(mockFn).toHaveReturnedTimes(2); + + expect(mockFn).not.toHaveReturnedTimes(1); + + assertThrows(() => { + expect(mockFn).toHaveReturnedTimes(1); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveReturnedTimes(2); + }, AssertionError); }); diff --git a/expect/_to_have_returned_with.ts b/expect/_to_have_returned_with.ts index 9a1f25ebe927..710644cc7b0b 100644 --- a/expect/_to_have_returned_with.ts +++ b/expect/_to_have_returned_with.ts @@ -9,4 +9,5 @@ export function toHaveReturnedWith( context: MatcherContext, expected: unknown, ): MatchResult { + const calls = getMockCalls(context.value); } From 357ecb3f2cd0f2ab9a2748cc7793c6f9f8f3c1ef Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 18:36:49 +0900 Subject: [PATCH 14/17] add toHaveReturnedWith, fix toBeCloseTo --- expect/_to_be_close_to.ts | 41 +++++++++++----------- expect/_to_be_close_to_test.ts | 6 ++++ expect/_to_have_last_returned_with.ts | 22 ++++++++++++ expect/_to_have_last_returned_with_test.ts | 17 ++++++++- expect/_to_have_nth_returned_with.ts | 28 +++++++++++++++ expect/_to_have_nth_returned_with_test.ts | 29 ++++++++++++++- expect/_to_have_property.ts | 1 + expect/_to_have_returned_with.ts | 23 ++++++++++++ expect/_to_have_returned_with_test.ts | 17 ++++++++- expect/expect.ts | 5 ++- 10 files changed, 165 insertions(+), 24 deletions(-) diff --git a/expect/_to_be_close_to.ts b/expect/_to_be_close_to.ts index dd58e7629c9e..5b679462d25a 100644 --- a/expect/_to_be_close_to.ts +++ b/expect/_to_be_close_to.ts @@ -1,34 +1,35 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertAlmostEquals } from "../assert/assert_almost_equals.ts"; import { AssertionError } from "../assert/assertion_error.ts"; // TODO(kt3k): tolerance handling is wrong export function toBeCloseTo( context: MatcherContext, expected: number, - tolerance = 1e-7, // FIXME: This should be digits number, not tolerance + numDigits = 2, ): MatchResult { + if (numDigits < 0) { + throw new Error( + "toBeCloseTo second argument must be a non-negative integer. Got " + + numDigits, + ); + } + const tolerance = 0.5 * Math.pow(10, -numDigits); + const value = Number(context.value); + const pass = Math.abs(expected - value) < tolerance; + if (context.isNot) { - const actual = Number(context.value); - const delta = Math.abs(expected - actual); - if (delta > tolerance) { - return; + if (pass) { + throw new AssertionError( + `Expected the value not to be close to ${expected} (using ${numDigits} digits), but it is`, + ); + } + } else { + if (!pass) { + throw new AssertionError( + `Expected the value (${value} to be close to ${expected} (using ${numDigits} digits), but it is not`, + ); } - const msgSuffix = context.customMessage - ? `: ${context.customMessage}` - : "."; - const f = (n: number) => Number.isInteger(n) ? n : n.toExponential(); - throw new AssertionError( - `Expected actual: "${f(actual)}" to NOT be close to "${f(expected)}": \ - delta "${f(delta)}" is greater than "${f(tolerance)}"${msgSuffix}`, - ); } - return assertAlmostEquals( - Number(context.value), - expected, - tolerance, - context.customMessage, - ); } diff --git a/expect/_to_be_close_to_test.ts b/expect/_to_be_close_to_test.ts index 3df46f3e957a..538d5e986f25 100644 --- a/expect/_to_be_close_to_test.ts +++ b/expect/_to_be_close_to_test.ts @@ -6,10 +6,16 @@ import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toBeCloseTo()", () => { expect(0.2 + 0.1).toBeCloseTo(0.3); expect(0.2 + 0.1).toBeCloseTo(0.3, 5); + expect(0.2 + 0.1).toBeCloseTo(0.3, 15); expect(0.2 + 0.11).not.toBeCloseTo(0.3); + expect(0.2 + 0.1).not.toBeCloseTo(0.3, 16); assertThrows(() => { expect(0.2 + 0.11).toBeCloseTo(0.3); }, AssertionError); + + assertThrows(() => { + expect(0.2 + 0.1).not.toBeCloseTo(0.3); + }); }); diff --git a/expect/_to_have_last_returned_with.ts b/expect/_to_have_last_returned_with.ts index aeca22abe33b..a58978e427a0 100644 --- a/expect/_to_have_last_returned_with.ts +++ b/expect/_to_have_last_returned_with.ts @@ -4,10 +4,32 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; +import { inspectArg } from "./_inspect_args.ts"; export function toHaveLastReturnedWith( context: MatcherContext, expected: unknown, ): MatchResult { const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + const lastReturnedWithExpected = returned.length > 0 && + equal(returned[returned.length - 1].returned, expected); + + if (context.isNot) { + if (lastReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to not have last returned with ${ + inspectArg(expected) + }, but it did`, + ); + } + } else { + if (!lastReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to have last returned with ${ + inspectArg(expected) + }, but it did not`, + ); + } + } } diff --git a/expect/_to_have_last_returned_with_test.ts b/expect/_to_have_last_returned_with_test.ts index f7551f6ff2c3..9403ccef5f5c 100644 --- a/expect/_to_have_last_returned_with_test.ts +++ b/expect/_to_have_last_returned_with_test.ts @@ -5,5 +5,20 @@ import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toHaveLastReturnedWith()", () => { - const mockFn = fn(); + const mockFn = fn((x: number) => x + 3); + + mockFn(1); + mockFn(4); + + expect(mockFn).toHaveLastReturnedWith(7); + + expect(mockFn).not.toHaveLastReturnedWith(4); + + assertThrows(() => { + expect(mockFn).toHaveLastReturnedWith(4); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveLastReturnedWith(7); + }, AssertionError); }); diff --git a/expect/_to_have_nth_returned_with.ts b/expect/_to_have_nth_returned_with.ts index 8459a9839610..1cd1aa94e19c 100644 --- a/expect/_to_have_nth_returned_with.ts +++ b/expect/_to_have_nth_returned_with.ts @@ -4,11 +4,39 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; +import { inspectArg } from "./_inspect_args.ts"; export function toHaveNthReturnedWith( context: MatcherContext, nth: number, expected: unknown, ): MatchResult { + if (nth < 1) { + throw new Error(`nth(${nth}) must be greater than 0`); + } + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + const returnIndex = nth - 1; + const maybeNthReturned = returned[returnIndex]; + const nthReturnedWithExpected = maybeNthReturned && + equal(maybeNthReturned.returned, expected); + + if (context.isNot) { + if (nthReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to not have n-th (n=${nth}) returned with ${ + inspectArg(expected) + }, but it did`, + ); + } + } else { + if (!nthReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to have n-th (n=${nth}) returned with ${ + inspectArg(expected) + }, but it did not`, + ); + } + } } diff --git a/expect/_to_have_nth_returned_with_test.ts b/expect/_to_have_nth_returned_with_test.ts index 96e5558dcde0..e9f9ba5ac8e2 100644 --- a/expect/_to_have_nth_returned_with_test.ts +++ b/expect/_to_have_nth_returned_with_test.ts @@ -5,5 +5,32 @@ import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toHaveNthReturnedWith()", () => { - const mockFn = fn(); + const mockFn = fn((x: number) => x + 7); + + mockFn(1); + mockFn(10); + mockFn(100); + mockFn(1000); + + expect(mockFn).toHaveNthReturnedWith(1, 8); + expect(mockFn).toHaveNthReturnedWith(2, 17); + expect(mockFn).toHaveNthReturnedWith(3, 107); + expect(mockFn).toHaveNthReturnedWith(4, 1007); + + expect(mockFn).not.toHaveNthReturnedWith(1, 1); + expect(mockFn).not.toHaveNthReturnedWith(2, 10); + expect(mockFn).not.toHaveNthReturnedWith(3, 100); + expect(mockFn).not.toHaveNthReturnedWith(4, 1000); + + assertThrows(() => { + expect(mockFn).toHaveNthReturnedWith(1, 1); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveNthReturnedWith(1, 8); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).toHaveNthReturnedWith(0, 0); + }, Error); }); diff --git a/expect/_to_have_property.ts b/expect/_to_have_property.ts index 3838153c53c1..295d051890a2 100644 --- a/expect/_to_have_property.ts +++ b/expect/_to_have_property.ts @@ -19,6 +19,7 @@ export function toHaveProperty( propPath = propName.split("."); } + // deno-lint-ignore no-explicit-any let current = value as any; while (true) { if (current === undefined || current === null) { diff --git a/expect/_to_have_returned_with.ts b/expect/_to_have_returned_with.ts index 710644cc7b0b..d71fd210d4a9 100644 --- a/expect/_to_have_returned_with.ts +++ b/expect/_to_have_returned_with.ts @@ -4,10 +4,33 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; import { equal } from "../assert/equal.ts"; import { getMockCalls } from "./_mock_util.ts"; +import { inspectArg } from "./_inspect_args.ts"; export function toHaveReturnedWith( context: MatcherContext, expected: unknown, ): MatchResult { const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + const returnedWithExpected = returned.some((call) => + equal(call.returned, expected) + ); + + if (context.isNot) { + if (returnedWithExpected) { + throw new AssertionError( + `Expected the mock function to not have returned with ${ + inspectArg(expected) + }, but it did`, + ); + } + } else { + if (!returnedWithExpected) { + throw new AssertionError( + `Expected the mock function to have returned with ${ + inspectArg(expected) + }, but it did not`, + ); + } + } } diff --git a/expect/_to_have_returned_with_test.ts b/expect/_to_have_returned_with_test.ts index 199331b16255..29e72912f0d7 100644 --- a/expect/_to_have_returned_with_test.ts +++ b/expect/_to_have_returned_with_test.ts @@ -5,5 +5,20 @@ import { fn } from "./fn.ts"; import { AssertionError, assertThrows } from "../assert/mod.ts"; Deno.test("expect().toHaveReturnedWith()", () => { - const mockFn = fn(); + const mockFn = fn((x: number) => ({ foo: x + 1 })); + + mockFn(5); + mockFn(6); + + expect(mockFn).toHaveReturnedWith({ foo: 7 }); + + expect(mockFn).not.toHaveReturnedWith({ foo: 5 }); + + assertThrows(() => { + expect(mockFn).toHaveReturnedWith({ foo: 5 }); + }, AssertionError); + + assertThrows(() => { + expect(mockFn).not.toHaveReturnedWith({ foo: 7 }); + }, AssertionError); }); diff --git a/expect/expect.ts b/expect/expect.ts index ebeb519ebb7a..d9e9da1f1ee8 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -25,6 +25,7 @@ import { toHaveBeenCalled } from "./_to_have_been_called.ts"; import { toHaveBeenLastCalledWith } from "./_to_have_been_last_called_with.ts"; import { toHaveBeenNthCalledWith } from "./_to_have_been_nth_called_with.ts"; import { toHaveLength } from "./_to_have_length.ts"; +import { toHaveLastReturnedWith } from "./_to_have_last_returned_with.ts"; import { toHaveNthReturnedWith } from "./_to_have_nth_returned_with.ts"; import { toHaveProperty } from "./_to_have_property.ts"; import { toHaveReturnedTimes } from "./_to_have_returned_times.ts"; @@ -65,6 +66,7 @@ export interface Expected { toHaveBeenLastCalledWith(...expected: unknown[]): void; toHaveBeenNthCalledWith(nth: number, ...expected: unknown[]): void; toHaveLength(expected: number): void; + toHaveLastReturnedWith(expected: unknown): void; toHaveNthReturnedWith(nth: number, expected: unknown): void; toHaveProperty(propName: string | string[], value?: unknown): void; toHaveReturnedTimes(expected: number): void; @@ -87,7 +89,7 @@ type MatcherKey = keyof Omit; const matchers: Record = { lastCalledWith: toHaveBeenLastCalledWith, - lastReturnedWith: toHaveReturnedWith, + lastReturnedWith: toHaveLastReturnedWith, nthCalledWith: toHaveBeenNthCalledWith, nthReturnedWith: toHaveNthReturnedWith, toBeCalled: toHaveBeenCalled, @@ -115,6 +117,7 @@ const matchers: Record = { toHaveBeenLastCalledWith, toHaveBeenNthCalledWith, toHaveLength, + toHaveLastReturnedWith, toHaveNthReturnedWith, toHaveProperty, toHaveReturnedTimes, From b1d19a4a10a2807840d4ac3c26e8f9c09cf290c5 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 18:51:45 +0900 Subject: [PATCH 15/17] fix browser typing --- expect/_inspect_args.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/expect/_inspect_args.ts b/expect/_inspect_args.ts index 3aafc40c37d4..95af662fd7d2 100644 --- a/expect/_inspect_args.ts +++ b/expect/_inspect_args.ts @@ -1,10 +1,12 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file export function inspectArgs(args: unknown[]): string { return args.map(inspectArg).join(", "); } export function inspectArg(arg: unknown): string { + const { Deno } = globalThis as any; return typeof Deno !== "undefined" && Deno.inspect ? Deno.inspect(arg) : String(arg); From 1af6b63930da47e9491e4ea5be09d1f9eb8afcf1 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 21:42:09 +0900 Subject: [PATCH 16/17] remove resolved todo --- expect/_to_be_close_to.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/expect/_to_be_close_to.ts b/expect/_to_be_close_to.ts index 5b679462d25a..a5007a8348dd 100644 --- a/expect/_to_be_close_to.ts +++ b/expect/_to_be_close_to.ts @@ -3,7 +3,6 @@ import { MatcherContext, MatchResult } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -// TODO(kt3k): tolerance handling is wrong export function toBeCloseTo( context: MatcherContext, expected: number, From ef3fce673bbd92b8d7ca638d717163a43d126b1b Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 21 Nov 2023 22:03:59 +0900 Subject: [PATCH 17/17] move matchers to expect/_matchers.ts --- expect/_matchers.ts | 760 +++++++++++++++++++++++ expect/_to_be.ts | 13 - expect/_to_be_close_to.ts | 34 - expect/_to_be_defined.ts | 13 - expect/_to_be_falsy.ts | 23 - expect/_to_be_greater_than.ts | 24 - expect/_to_be_greater_than_or_equal.ts | 24 - expect/_to_be_instance_of.ts | 16 - expect/_to_be_less_than.ts | 24 - expect/_to_be_less_than_or_equal.ts | 24 - expect/_to_be_nan.ts | 21 - expect/_to_be_null.ts | 21 - expect/_to_be_truthy.ts | 23 - expect/_to_be_undefined.ts | 17 - expect/_to_contain.ts | 22 - expect/_to_contain_equal.ts | 40 -- expect/_to_equal.ts | 16 - expect/_to_have_been_called.ts | 24 - expect/_to_have_been_called_times.ts | 26 - expect/_to_have_been_called_with.ts | 39 -- expect/_to_have_been_last_called_with.ts | 43 -- expect/_to_have_been_nth_called_with.ts | 49 -- expect/_to_have_last_returned_with.ts | 35 -- expect/_to_have_length.ts | 28 - expect/_to_have_nth_returned_with.ts | 42 -- expect/_to_have_property.ts | 65 -- expect/_to_have_returned.ts | 24 - expect/_to_have_returned_times.ts | 27 - expect/_to_match.ts | 20 - expect/_to_match_object.ts | 42 -- expect/_to_strict_equal.ts | 20 - expect/_to_throw.ts | 39 -- expect/expect.ts | 67 +- 33 files changed, 794 insertions(+), 911 deletions(-) create mode 100644 expect/_matchers.ts delete mode 100644 expect/_to_be.ts delete mode 100644 expect/_to_be_close_to.ts delete mode 100644 expect/_to_be_defined.ts delete mode 100644 expect/_to_be_falsy.ts delete mode 100644 expect/_to_be_greater_than.ts delete mode 100644 expect/_to_be_greater_than_or_equal.ts delete mode 100644 expect/_to_be_instance_of.ts delete mode 100644 expect/_to_be_less_than.ts delete mode 100644 expect/_to_be_less_than_or_equal.ts delete mode 100644 expect/_to_be_nan.ts delete mode 100644 expect/_to_be_null.ts delete mode 100644 expect/_to_be_truthy.ts delete mode 100644 expect/_to_be_undefined.ts delete mode 100644 expect/_to_contain.ts delete mode 100644 expect/_to_contain_equal.ts delete mode 100644 expect/_to_equal.ts delete mode 100644 expect/_to_have_been_called.ts delete mode 100644 expect/_to_have_been_called_times.ts delete mode 100644 expect/_to_have_been_called_with.ts delete mode 100644 expect/_to_have_been_last_called_with.ts delete mode 100644 expect/_to_have_been_nth_called_with.ts delete mode 100644 expect/_to_have_last_returned_with.ts delete mode 100644 expect/_to_have_length.ts delete mode 100644 expect/_to_have_nth_returned_with.ts delete mode 100644 expect/_to_have_property.ts delete mode 100644 expect/_to_have_returned.ts delete mode 100644 expect/_to_have_returned_times.ts delete mode 100644 expect/_to_match.ts delete mode 100644 expect/_to_match_object.ts delete mode 100644 expect/_to_strict_equal.ts delete mode 100644 expect/_to_throw.ts diff --git a/expect/_matchers.ts b/expect/_matchers.ts new file mode 100644 index 000000000000..18d44b3f0ec8 --- /dev/null +++ b/expect/_matchers.ts @@ -0,0 +1,760 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; +import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; +import { assertInstanceOf } from "../assert/assert_instance_of.ts"; +import { assertIsError } from "../assert/assert_is_error.ts"; +import { assertNotInstanceOf } from "../assert/assert_not_instance_of.ts"; +import { assertNotEquals } from "../assert/assert_not_equals.ts"; +import { assertEquals } from "../assert/assert_equals.ts"; +import { assertMatch } from "../assert/assert_match.ts"; +import { assertObjectMatch } from "../assert/assert_object_match.ts"; +import { assertNotMatch } from "../assert/assert_not_match.ts"; +import { AssertionError } from "../assert/assertion_error.ts"; +import { equal } from "../assert/equal.ts"; +import { format } from "../assert/_format.ts"; + +import { AnyConstructor, MatcherContext, MatchResult } from "./_types.ts"; +import { getMockCalls } from "./_mock_util.ts"; +import { inspectArg, inspectArgs } from "./_inspect_args.ts"; + +export function toBe(context: MatcherContext, expect: unknown): MatchResult { + if (context.isNot) { + assertNotStrictEquals(context.value, expect, context.customMessage); + } else { + assertStrictEquals(context.value, expect, context.customMessage); + } +} + +export function toEqual( + context: MatcherContext, + expected: unknown, +): MatchResult { + if (context.isNot) { + assertNotEquals(context.value, expected, context.customMessage); + } else { + assertEquals(context.value, expected, context.customMessage); + } +} + +export function toStrictEqual( + context: MatcherContext, + expected: unknown, +): MatchResult { + if (context.isNot) { + assertNotStrictEquals( + context.value, + expected, + context.customMessage, + ); + } else { + assertStrictEquals(context.value, expected, context.customMessage); + } +} + +export function toBeCloseTo( + context: MatcherContext, + expected: number, + numDigits = 2, +): MatchResult { + if (numDigits < 0) { + throw new Error( + "toBeCloseTo second argument must be a non-negative integer. Got " + + numDigits, + ); + } + const tolerance = 0.5 * Math.pow(10, -numDigits); + const value = Number(context.value); + const pass = Math.abs(expected - value) < tolerance; + + if (context.isNot) { + if (pass) { + throw new AssertionError( + `Expected the value not to be close to ${expected} (using ${numDigits} digits), but it is`, + ); + } + } else { + if (!pass) { + throw new AssertionError( + `Expected the value (${value} to be close to ${expected} (using ${numDigits} digits), but it is not`, + ); + } + } +} + +export function toBeDefined(context: MatcherContext): MatchResult { + if (context.isNot) { + assertStrictEquals(context.value, undefined, context.customMessage); + } else { + assertNotStrictEquals(context.value, undefined, context.customMessage); + } +} + +export function toBeUndefined(context: MatcherContext): MatchResult { + if (context.isNot) { + assertNotStrictEquals( + context.value, + undefined, + context.customMessage, + ); + } else { + assertStrictEquals(context.value, undefined, context.customMessage); + } +} + +export function toBeFalsy( + context: MatcherContext, +): MatchResult { + const isFalsy = !(context.value); + if (context.isNot) { + if (isFalsy) { + throw new AssertionError( + `Expected ${context.value} to NOT be falsy`, + ); + } + } else { + if (!isFalsy) { + throw new AssertionError( + `Expected ${context.value} to be falsy`, + ); + } + } +} + +export function toBeTruthy( + context: MatcherContext, +): MatchResult { + const isTruthy = !!(context.value); + if (context.isNot) { + if (isTruthy) { + throw new AssertionError( + `Expected ${context.value} to NOT be truthy`, + ); + } + } else { + if (!isTruthy) { + throw new AssertionError( + `Expected ${context.value} to be truthy`, + ); + } + } +} + +export function toBeGreaterThanOrEqual( + context: MatcherContext, + expected: number, +): MatchResult { + const isGreaterOrEqual = Number(context.value) >= Number(expected); + if (context.isNot) { + if (isGreaterOrEqual) { + throw new AssertionError( + `Expected ${context.value} to NOT be greater than or equal ${expected}`, + ); + } + } else { + if (!isGreaterOrEqual) { + throw new AssertionError( + `Expected ${context.value} to be greater than or equal ${expected}`, + ); + } + } +} + +export function toBeGreaterThan( + context: MatcherContext, + expected: number, +): MatchResult { + const isGreater = Number(context.value) > Number(expected); + if (context.isNot) { + if (isGreater) { + throw new AssertionError( + `Expected ${context.value} to NOT be greater than ${expected}`, + ); + } + } else { + if (!isGreater) { + throw new AssertionError( + `Expected ${context.value} to be greater than ${expected}`, + ); + } + } +} + +export function toBeInstanceOf( + context: MatcherContext, + expected: T, +): MatchResult { + if (context.isNot) { + assertNotInstanceOf(context.value, expected); + } else { + assertInstanceOf(context.value, expected); + } +} +export function toBeLessThanOrEqual( + context: MatcherContext, + expected: number, +): MatchResult { + const isLower = Number(context.value) <= Number(expected); + if (context.isNot) { + if (isLower) { + throw new AssertionError( + `Expected ${context.value} to NOT be lower than or equal ${expected}`, + ); + } + } else { + if (!isLower) { + throw new AssertionError( + `Expected ${context.value} to be lower than or equal ${expected}`, + ); + } + } +} +export function toBeLessThan( + context: MatcherContext, + expected: number, +): MatchResult { + const isLower = Number(context.value) < Number(expected); + if (context.isNot) { + if (isLower) { + throw new AssertionError( + `Expected ${context.value} to NOT be lower than ${expected}`, + ); + } + } else { + if (!isLower) { + throw new AssertionError( + `Expected ${context.value} to be lower than ${expected}`, + ); + } + } +} +export function toBeNaN(context: MatcherContext): MatchResult { + if (context.isNot) { + assertNotEquals( + isNaN(Number(context.value)), + true, + context.customMessage || `Expected ${context.value} to not be NaN`, + ); + } else { + assertEquals( + isNaN(Number(context.value)), + true, + context.customMessage || `Expected ${context.value} to be NaN`, + ); + } +} + +export function toBeNull(context: MatcherContext): MatchResult { + if (context.isNot) { + assertNotStrictEquals( + context.value as number, + null, + context.customMessage || `Expected ${context.value} to not be null`, + ); + } else { + assertStrictEquals( + context.value as number, + null, + context.customMessage || `Expected ${context.value} to be null`, + ); + } +} + +export function toHaveLength( + context: MatcherContext, + expected: number, +): MatchResult { + const { value } = context; + // deno-lint-ignore no-explicit-any + const maybeLength = (value as any)?.length; + const hasLength = maybeLength === expected; + + if (context.isNot) { + if (hasLength) { + throw new AssertionError( + `Expected value not to have length ${expected}, but it does`, + ); + } + } else { + if (!hasLength) { + throw new AssertionError( + `Expected value to have length ${expected}, but it does not. (The value has length ${maybeLength})`, + ); + } + } +} + +export function toHaveProperty( + context: MatcherContext, + propName: string | string[], + v?: unknown, +): MatchResult { + const { value } = context; + + let propPath = [] as string[]; + if (Array.isArray(propName)) { + propPath = propName; + } else { + propPath = propName.split("."); + } + + // deno-lint-ignore no-explicit-any + let current = value as any; + while (true) { + if (current === undefined || current === null) { + break; + } + if (propPath.length === 0) { + break; + } + const prop = propPath.shift()!; + current = current[prop]; + } + + let hasProperty; + if (v) { + hasProperty = current !== undefined && propPath.length === 0 && + equal(current, v); + } else { + hasProperty = current !== undefined && propPath.length === 0; + } + + let ofValue = ""; + if (v) { + ofValue = ` of the value ${inspectArg(v)}`; + } + + if (context.isNot) { + if (hasProperty) { + throw new AssertionError( + `Expected the value not to have the property ${ + propPath.join(".") + }${ofValue}, but it does.`, + ); + } + } else { + if (!hasProperty) { + throw new AssertionError( + `Expected the value to have the property ${ + propPath.join(".") + }${ofValue}, but it does not.`, + ); + } + } +} + +export function toContain( + context: MatcherContext, + expected: unknown, +): MatchResult { + // deno-lint-ignore no-explicit-any + const doesContain = (context.value as any)?.includes?.(expected); + + if (context.isNot) { + if (doesContain) { + throw new AssertionError("The value contains the expected item"); + } + } else { + if (!doesContain) { + throw new AssertionError("The value doesn't contain the expected item"); + } + } +} + +export function toContainEqual( + context: MatcherContext, + expected: unknown, +): MatchResult { + const { value } = context; + assertIsIterable(value); + let doesContain = false; + for (const item of value) { + if (equal(item, expected)) { + doesContain = true; + break; + } + } + + if (context.isNot) { + if (doesContain) { + throw new AssertionError("The value contains the expected item"); + } + } else { + if (!doesContain) { + throw new AssertionError("The value doesn't contain the expected item"); + } + } +} + +// deno-lint-ignore no-explicit-any +function assertIsIterable(value: any): asserts value is Iterable { + if (value == null) { + throw new AssertionError("The value is null or undefined"); + } + if (typeof value[Symbol.iterator] !== "function") { + throw new AssertionError("The value is not iterable"); + } +} + +export function toMatch( + context: MatcherContext, + expected: RegExp, +): MatchResult { + if (context.isNot) { + assertNotMatch( + String(context.value), + expected, + context.customMessage, + ); + } else { + assertMatch(String(context.value), expected, context.customMessage); + } +} + +export function toMatchObject( + context: MatcherContext, + expected: Record, +): MatchResult { + if (context.isNot) { + let objectMatch = false; + try { + assertObjectMatch( + // deno-lint-ignore no-explicit-any + context.value as Record, + expected, + context.customMessage, + ); + objectMatch = true; + const actualString = format(context.value); + const expectedString = format(expected); + throw new AssertionError( + `Expected ${actualString} to NOT match ${expectedString}`, + ); + } catch (e) { + if (objectMatch) { + throw e; + } + return; + } + } else { + assertObjectMatch( + // deno-lint-ignore no-explicit-any + context.value as Record, + expected, + context.customMessage, + ); + } +} + +export function toHaveBeenCalled(context: MatcherContext): MatchResult { + const calls = getMockCalls(context.value); + const hasBeenCalled = calls.length > 0; + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected mock function not to be called, but it was called ${calls.length} time(s)`, + ); + } + } else { + if (!hasBeenCalled) { + throw new AssertionError( + `Expected mock function to be called, but it was not called`, + ); + } + } +} + +export function toHaveBeenCalledTimes( + context: MatcherContext, + expected: number, +): MatchResult { + const calls = getMockCalls(context.value); + + if (context.isNot) { + if (calls.length === expected) { + throw new AssertionError( + `Expected mock function not to be called ${expected} time(s), but it was`, + ); + } + } else { + if (calls.length !== expected) { + throw new AssertionError( + `Expected mock function to be called ${expected} time(s), but it was called ${calls.length} time(s)`, + ); + } + } +} + +export function toHaveBeenCalledWith( + context: MatcherContext, + ...expected: unknown[] +): MatchResult { + const calls = getMockCalls(context.value); + const hasBeenCalled = calls.some((call) => equal(call.args, expected)); + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected mock function not to be called with ${ + inspectArgs(expected) + }, but it was`, + ); + } + } else { + if (!hasBeenCalled) { + let otherCalls = ""; + if (calls.length > 0) { + otherCalls = `\n Other calls:\n ${ + calls.map((call) => inspectArgs(call.args)).join("\n ") + }`; + } + throw new AssertionError( + `Expected mock function to be called with ${ + inspectArgs(expected) + }, but it was not.${otherCalls}`, + ); + } + } +} +export function toHaveBeenLastCalledWith( + context: MatcherContext, + ...expected: unknown[] +): MatchResult { + const calls = getMockCalls(context.value); + const hasBeenCalled = calls.length > 0 && + equal(calls[calls.length - 1].args, expected); + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected mock function not to be last called with ${ + inspectArgs(expected) + }, but it was`, + ); + } + } else { + if (!hasBeenCalled) { + const lastCall = calls.at(-1); + if (!lastCall) { + throw new AssertionError( + `Expected mock function to be last called with ${ + inspectArgs(expected) + }, but it was not.`, + ); + } else { + throw new AssertionError( + `Expected mock function to be last called with ${ + inspectArgs(expected) + }, but it was last called with ${inspectArgs(lastCall.args)}.`, + ); + } + } + } +} +export function toHaveBeenNthCalledWith( + context: MatcherContext, + nth: number, + ...expected: unknown[] +): MatchResult { + if (nth < 1) { + new Error(`nth must be greater than 0. ${nth} was given.`); + } + + const calls = getMockCalls(context.value); + const callIndex = nth - 1; + const hasBeenCalled = calls.length > callIndex && + equal(calls[callIndex].args, expected); + + if (context.isNot) { + if (hasBeenCalled) { + throw new AssertionError( + `Expected the n-th call (n=${nth}) of mock function is not with ${ + inspectArgs(expected) + }, but it was`, + ); + } + } else { + if (!hasBeenCalled) { + const nthCall = calls[callIndex]; + if (!nth) { + throw new AssertionError( + `Expected the n-th call (n=${nth}) of mock function is with ${ + inspectArgs(expected) + }, but the n-th call does not exist.`, + ); + } else { + throw new AssertionError( + `Expected the n-th call (n=${nth}) of mock function is with ${ + inspectArgs(expected) + }, but it was with ${inspectArgs(nthCall.args)}.`, + ); + } + } + } +} + +export function toHaveReturned(context: MatcherContext): MatchResult { + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + + if (context.isNot) { + if (returned.length > 0) { + throw new AssertionError( + `Expected the mock function to not have returned, but it returned ${returned.length} times`, + ); + } + } else { + if (returned.length === 0) { + throw new AssertionError( + `Expected the mock function to have returned, but it did not return`, + ); + } + } +} + +export function toHaveReturnedTimes( + context: MatcherContext, + expected: number, +): MatchResult { + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + + if (context.isNot) { + if (returned.length === expected) { + throw new AssertionError( + `Expected the mock function to not have returned ${expected} times, but it returned ${returned.length} times`, + ); + } + } else { + if (returned.length !== expected) { + throw new AssertionError( + `Expected the mock function to have returned ${expected} times, but it returned ${returned.length} times`, + ); + } + } +} +export function toHaveReturnedWith( + context: MatcherContext, + expected: unknown, +): MatchResult { + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + const returnedWithExpected = returned.some((call) => + equal(call.returned, expected) + ); + + if (context.isNot) { + if (returnedWithExpected) { + throw new AssertionError( + `Expected the mock function to not have returned with ${ + inspectArg(expected) + }, but it did`, + ); + } + } else { + if (!returnedWithExpected) { + throw new AssertionError( + `Expected the mock function to have returned with ${ + inspectArg(expected) + }, but it did not`, + ); + } + } +} + +export function toHaveLastReturnedWith( + context: MatcherContext, + expected: unknown, +): MatchResult { + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + const lastReturnedWithExpected = returned.length > 0 && + equal(returned[returned.length - 1].returned, expected); + + if (context.isNot) { + if (lastReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to not have last returned with ${ + inspectArg(expected) + }, but it did`, + ); + } + } else { + if (!lastReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to have last returned with ${ + inspectArg(expected) + }, but it did not`, + ); + } + } +} + +export function toHaveNthReturnedWith( + context: MatcherContext, + nth: number, + expected: unknown, +): MatchResult { + if (nth < 1) { + throw new Error(`nth(${nth}) must be greater than 0`); + } + + const calls = getMockCalls(context.value); + const returned = calls.filter((call) => call.returns); + const returnIndex = nth - 1; + const maybeNthReturned = returned[returnIndex]; + const nthReturnedWithExpected = maybeNthReturned && + equal(maybeNthReturned.returned, expected); + + if (context.isNot) { + if (nthReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to not have n-th (n=${nth}) returned with ${ + inspectArg(expected) + }, but it did`, + ); + } + } else { + if (!nthReturnedWithExpected) { + throw new AssertionError( + `Expected the mock function to have n-th (n=${nth}) returned with ${ + inspectArg(expected) + }, but it did not`, + ); + } + } +} + +export function toThrow( + context: MatcherContext, + // deno-lint-ignore no-explicit-any + expected: new (...args: any[]) => E, +): MatchResult { + if (typeof context.value === "function") { + try { + context.value = context.value(); + } catch (err) { + context.value = err; + } + } + if (context.isNot) { + let isError = false; + try { + assertIsError(context.value, expected, undefined, context.customMessage); + isError = true; + throw new AssertionError(`Expected to NOT throw ${expected}`); + } catch (e) { + if (isError) { + throw e; + } + return; + } + } + return assertIsError( + context.value, + expected, + undefined, + context.customMessage, + ); +} diff --git a/expect/_to_be.ts b/expect/_to_be.ts deleted file mode 100644 index 0f37f67a6a4b..000000000000 --- a/expect/_to_be.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; -import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; - -export function toBe(context: MatcherContext, expect: unknown): MatchResult { - if (context.isNot) { - assertNotStrictEquals(context.value, expect, context.customMessage); - } else { - assertStrictEquals(context.value, expect, context.customMessage); - } -} diff --git a/expect/_to_be_close_to.ts b/expect/_to_be_close_to.ts deleted file mode 100644 index a5007a8348dd..000000000000 --- a/expect/_to_be_close_to.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeCloseTo( - context: MatcherContext, - expected: number, - numDigits = 2, -): MatchResult { - if (numDigits < 0) { - throw new Error( - "toBeCloseTo second argument must be a non-negative integer. Got " + - numDigits, - ); - } - const tolerance = 0.5 * Math.pow(10, -numDigits); - const value = Number(context.value); - const pass = Math.abs(expected - value) < tolerance; - - if (context.isNot) { - if (pass) { - throw new AssertionError( - `Expected the value not to be close to ${expected} (using ${numDigits} digits), but it is`, - ); - } - } else { - if (!pass) { - throw new AssertionError( - `Expected the value (${value} to be close to ${expected} (using ${numDigits} digits), but it is not`, - ); - } - } -} diff --git a/expect/_to_be_defined.ts b/expect/_to_be_defined.ts deleted file mode 100644 index 1778dd2878a1..000000000000 --- a/expect/_to_be_defined.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; -import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; - -export function toBeDefined(context: MatcherContext): MatchResult { - if (context.isNot) { - assertStrictEquals(context.value, undefined, context.customMessage); - } else { - assertNotStrictEquals(context.value, undefined, context.customMessage); - } -} diff --git a/expect/_to_be_falsy.ts b/expect/_to_be_falsy.ts deleted file mode 100644 index bfc10c5667b5..000000000000 --- a/expect/_to_be_falsy.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeFalsy( - context: MatcherContext, -): MatchResult { - const isFalsy = !(context.value); - if (context.isNot) { - if (isFalsy) { - throw new AssertionError( - `Expected ${context.value} to NOT be falsy`, - ); - } - } else { - if (!isFalsy) { - throw new AssertionError( - `Expected ${context.value} to be falsy`, - ); - } - } -} diff --git a/expect/_to_be_greater_than.ts b/expect/_to_be_greater_than.ts deleted file mode 100644 index 31b315e5537a..000000000000 --- a/expect/_to_be_greater_than.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeGreaterThan( - context: MatcherContext, - expected: number, -): MatchResult { - const isGreater = Number(context.value) > Number(expected); - if (context.isNot) { - if (isGreater) { - throw new AssertionError( - `Expected ${context.value} to NOT be greater than ${expected}`, - ); - } - } else { - if (!isGreater) { - throw new AssertionError( - `Expected ${context.value} to be greater than ${expected}`, - ); - } - } -} diff --git a/expect/_to_be_greater_than_or_equal.ts b/expect/_to_be_greater_than_or_equal.ts deleted file mode 100644 index 1444f609f92c..000000000000 --- a/expect/_to_be_greater_than_or_equal.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeGreaterThanOrEqual( - context: MatcherContext, - expected: number, -): MatchResult { - const isGreaterOrEqual = Number(context.value) >= Number(expected); - if (context.isNot) { - if (isGreaterOrEqual) { - throw new AssertionError( - `Expected ${context.value} to NOT be greater than or equal ${expected}`, - ); - } - } else { - if (!isGreaterOrEqual) { - throw new AssertionError( - `Expected ${context.value} to be greater than or equal ${expected}`, - ); - } - } -} diff --git a/expect/_to_be_instance_of.ts b/expect/_to_be_instance_of.ts deleted file mode 100644 index d73882ab0442..000000000000 --- a/expect/_to_be_instance_of.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { AnyConstructor, MatcherContext, MatchResult } from "./_types.ts"; -import { assertInstanceOf } from "../assert/assert_instance_of.ts"; -import { assertNotInstanceOf } from "../assert/assert_not_instance_of.ts"; - -export function toBeInstanceOf( - context: MatcherContext, - expected: T, -): MatchResult { - if (context.isNot) { - assertNotInstanceOf(context.value, expected); - } else { - assertInstanceOf(context.value, expected); - } -} diff --git a/expect/_to_be_less_than.ts b/expect/_to_be_less_than.ts deleted file mode 100644 index 2c3352b65fc2..000000000000 --- a/expect/_to_be_less_than.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeLessThan( - context: MatcherContext, - expected: number, -): MatchResult { - const isLower = Number(context.value) < Number(expected); - if (context.isNot) { - if (isLower) { - throw new AssertionError( - `Expected ${context.value} to NOT be lower than ${expected}`, - ); - } - } else { - if (!isLower) { - throw new AssertionError( - `Expected ${context.value} to be lower than ${expected}`, - ); - } - } -} diff --git a/expect/_to_be_less_than_or_equal.ts b/expect/_to_be_less_than_or_equal.ts deleted file mode 100644 index 7a2c61602bff..000000000000 --- a/expect/_to_be_less_than_or_equal.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeLessThanOrEqual( - context: MatcherContext, - expected: number, -): MatchResult { - const isLower = Number(context.value) <= Number(expected); - if (context.isNot) { - if (isLower) { - throw new AssertionError( - `Expected ${context.value} to NOT be lower than or equal ${expected}`, - ); - } - } else { - if (!isLower) { - throw new AssertionError( - `Expected ${context.value} to be lower than or equal ${expected}`, - ); - } - } -} diff --git a/expect/_to_be_nan.ts b/expect/_to_be_nan.ts deleted file mode 100644 index 4b83c5617283..000000000000 --- a/expect/_to_be_nan.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotEquals } from "../assert/assert_not_equals.ts"; -import { assertEquals } from "../assert/assert_equals.ts"; - -export function toBeNaN(context: MatcherContext): MatchResult { - if (context.isNot) { - assertNotEquals( - isNaN(Number(context.value)), - true, - context.customMessage || `Expected ${context.value} to not be NaN`, - ); - } else { - assertEquals( - isNaN(Number(context.value)), - true, - context.customMessage || `Expected ${context.value} to be NaN`, - ); - } -} diff --git a/expect/_to_be_null.ts b/expect/_to_be_null.ts deleted file mode 100644 index d9cbe8918132..000000000000 --- a/expect/_to_be_null.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; -import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; - -export function toBeNull(context: MatcherContext): MatchResult { - if (context.isNot) { - assertNotStrictEquals( - context.value as number, - null, - context.customMessage || `Expected ${context.value} to not be null`, - ); - } else { - assertStrictEquals( - context.value as number, - null, - context.customMessage || `Expected ${context.value} to be null`, - ); - } -} diff --git a/expect/_to_be_truthy.ts b/expect/_to_be_truthy.ts deleted file mode 100644 index 73c76ea15adf..000000000000 --- a/expect/_to_be_truthy.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toBeTruthy( - context: MatcherContext, -): MatchResult { - const isTruthy = !!(context.value); - if (context.isNot) { - if (isTruthy) { - throw new AssertionError( - `Expected ${context.value} to NOT be truthy`, - ); - } - } else { - if (!isTruthy) { - throw new AssertionError( - `Expected ${context.value} to be truthy`, - ); - } - } -} diff --git a/expect/_to_be_undefined.ts b/expect/_to_be_undefined.ts deleted file mode 100644 index 2ffe406babd1..000000000000 --- a/expect/_to_be_undefined.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; -import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; - -export function toBeUndefined(context: MatcherContext): MatchResult { - if (context.isNot) { - assertNotStrictEquals( - context.value, - undefined, - context.customMessage, - ); - } else { - assertStrictEquals(context.value, undefined, context.customMessage); - } -} diff --git a/expect/_to_contain.ts b/expect/_to_contain.ts deleted file mode 100644 index 6b34cddc481b..000000000000 --- a/expect/_to_contain.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { AssertionError } from "../assert/assertion_error.ts"; -import { MatcherContext, MatchResult } from "./_types.ts"; - -export function toContain( - context: MatcherContext, - expected: unknown, -): MatchResult { - // deno-lint-ignore no-explicit-any - const doesContain = (context.value as any)?.includes?.(expected); - - if (context.isNot) { - if (doesContain) { - throw new AssertionError("The value contains the expected item"); - } - } else { - if (!doesContain) { - throw new AssertionError("The value doesn't contain the expected item"); - } - } -} diff --git a/expect/_to_contain_equal.ts b/expect/_to_contain_equal.ts deleted file mode 100644 index 1b4c582a08d1..000000000000 --- a/expect/_to_contain_equal.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { MatcherContext, MatchResult } from "./_types.ts"; - -export function toContainEqual( - context: MatcherContext, - expected: unknown, -): MatchResult { - const { value } = context; - assertIsIterable(value); - let doesContain = false; - for (const item of value) { - if (equal(item, expected)) { - doesContain = true; - break; - } - } - - if (context.isNot) { - if (doesContain) { - throw new AssertionError("The value contains the expected item"); - } - } else { - if (!doesContain) { - throw new AssertionError("The value doesn't contain the expected item"); - } - } -} - -// deno-lint-ignore no-explicit-any -function assertIsIterable(value: any): asserts value is Iterable { - if (value == null) { - throw new AssertionError("The value is null or undefined"); - } - if (typeof value[Symbol.iterator] !== "function") { - throw new AssertionError("The value is not iterable"); - } -} diff --git a/expect/_to_equal.ts b/expect/_to_equal.ts deleted file mode 100644 index 0bc082a21390..000000000000 --- a/expect/_to_equal.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotEquals } from "../assert/assert_not_equals.ts"; -import { assertEquals } from "../assert/assert_equals.ts"; - -export function toEqual( - context: MatcherContext, - expected: unknown, -): MatchResult { - if (context.isNot) { - assertNotEquals(context.value, expected, context.customMessage); - } else { - assertEquals(context.value, expected, context.customMessage); - } -} diff --git a/expect/_to_have_been_called.ts b/expect/_to_have_been_called.ts deleted file mode 100644 index a7bd4f61bc8a..000000000000 --- a/expect/_to_have_been_called.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { getMockCalls } from "./_mock_util.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toHaveBeenCalled(context: MatcherContext): MatchResult { - const calls = getMockCalls(context.value); - const hasBeenCalled = calls.length > 0; - - if (context.isNot) { - if (hasBeenCalled) { - throw new AssertionError( - `Expected mock function not to be called, but it was called ${calls.length} time(s)`, - ); - } - } else { - if (!hasBeenCalled) { - throw new AssertionError( - `Expected mock function to be called, but it was not called`, - ); - } - } -} diff --git a/expect/_to_have_been_called_times.ts b/expect/_to_have_been_called_times.ts deleted file mode 100644 index a1f38d021add..000000000000 --- a/expect/_to_have_been_called_times.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { getMockCalls } from "./_mock_util.ts"; - -export function toHaveBeenCalledTimes( - context: MatcherContext, - expected: number, -): MatchResult { - const calls = getMockCalls(context.value); - - if (context.isNot) { - if (calls.length === expected) { - throw new AssertionError( - `Expected mock function not to be called ${expected} time(s), but it was`, - ); - } - } else { - if (calls.length !== expected) { - throw new AssertionError( - `Expected mock function to be called ${expected} time(s), but it was called ${calls.length} time(s)`, - ); - } - } -} diff --git a/expect/_to_have_been_called_with.ts b/expect/_to_have_been_called_with.ts deleted file mode 100644 index a65254a694db..000000000000 --- a/expect/_to_have_been_called_with.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { getMockCalls } from "./_mock_util.ts"; -import { inspectArgs } from "./_inspect_args.ts"; - -export function toHaveBeenCalledWith( - context: MatcherContext, - ...expected: unknown[] -): MatchResult { - const calls = getMockCalls(context.value); - const hasBeenCalled = calls.some((call) => equal(call.args, expected)); - - if (context.isNot) { - if (hasBeenCalled) { - throw new AssertionError( - `Expected mock function not to be called with ${ - inspectArgs(expected) - }, but it was`, - ); - } - } else { - if (!hasBeenCalled) { - let otherCalls = ""; - if (calls.length > 0) { - otherCalls = `\n Other calls:\n ${ - calls.map((call) => inspectArgs(call.args)).join("\n ") - }`; - } - throw new AssertionError( - `Expected mock function to be called with ${ - inspectArgs(expected) - }, but it was not.${otherCalls}`, - ); - } - } -} diff --git a/expect/_to_have_been_last_called_with.ts b/expect/_to_have_been_last_called_with.ts deleted file mode 100644 index 41712cc96743..000000000000 --- a/expect/_to_have_been_last_called_with.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { getMockCalls } from "./_mock_util.ts"; -import { inspectArgs } from "./_inspect_args.ts"; - -export function toHaveBeenLastCalledWith( - context: MatcherContext, - ...expected: unknown[] -): MatchResult { - const calls = getMockCalls(context.value); - const hasBeenCalled = calls.length > 0 && - equal(calls[calls.length - 1].args, expected); - - if (context.isNot) { - if (hasBeenCalled) { - throw new AssertionError( - `Expected mock function not to be last called with ${ - inspectArgs(expected) - }, but it was`, - ); - } - } else { - if (!hasBeenCalled) { - const lastCall = calls.at(-1); - if (!lastCall) { - throw new AssertionError( - `Expected mock function to be last called with ${ - inspectArgs(expected) - }, but it was not.`, - ); - } else { - throw new AssertionError( - `Expected mock function to be last called with ${ - inspectArgs(expected) - }, but it was last called with ${inspectArgs(lastCall.args)}.`, - ); - } - } - } -} diff --git a/expect/_to_have_been_nth_called_with.ts b/expect/_to_have_been_nth_called_with.ts deleted file mode 100644 index bc62ea91b2d9..000000000000 --- a/expect/_to_have_been_nth_called_with.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { getMockCalls } from "./_mock_util.ts"; -import { inspectArgs } from "./_inspect_args.ts"; - -export function toHaveBeenNthCalledWith( - context: MatcherContext, - nth: number, - ...expected: unknown[] -): MatchResult { - if (nth < 1) { - new Error(`nth must be greater than 0. ${nth} was given.`); - } - - const calls = getMockCalls(context.value); - const callIndex = nth - 1; - const hasBeenCalled = calls.length > callIndex && - equal(calls[callIndex].args, expected); - - if (context.isNot) { - if (hasBeenCalled) { - throw new AssertionError( - `Expected the n-th call (n=${nth}) of mock function is not with ${ - inspectArgs(expected) - }, but it was`, - ); - } - } else { - if (!hasBeenCalled) { - const nthCall = calls[callIndex]; - if (!nth) { - throw new AssertionError( - `Expected the n-th call (n=${nth}) of mock function is with ${ - inspectArgs(expected) - }, but the n-th call does not exist.`, - ); - } else { - throw new AssertionError( - `Expected the n-th call (n=${nth}) of mock function is with ${ - inspectArgs(expected) - }, but it was with ${inspectArgs(nthCall.args)}.`, - ); - } - } - } -} diff --git a/expect/_to_have_last_returned_with.ts b/expect/_to_have_last_returned_with.ts deleted file mode 100644 index a58978e427a0..000000000000 --- a/expect/_to_have_last_returned_with.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { getMockCalls } from "./_mock_util.ts"; -import { inspectArg } from "./_inspect_args.ts"; - -export function toHaveLastReturnedWith( - context: MatcherContext, - expected: unknown, -): MatchResult { - const calls = getMockCalls(context.value); - const returned = calls.filter((call) => call.returns); - const lastReturnedWithExpected = returned.length > 0 && - equal(returned[returned.length - 1].returned, expected); - - if (context.isNot) { - if (lastReturnedWithExpected) { - throw new AssertionError( - `Expected the mock function to not have last returned with ${ - inspectArg(expected) - }, but it did`, - ); - } - } else { - if (!lastReturnedWithExpected) { - throw new AssertionError( - `Expected the mock function to have last returned with ${ - inspectArg(expected) - }, but it did not`, - ); - } - } -} diff --git a/expect/_to_have_length.ts b/expect/_to_have_length.ts deleted file mode 100644 index eb44b90f8623..000000000000 --- a/expect/_to_have_length.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; - -export function toHaveLength( - context: MatcherContext, - expected: number, -): MatchResult { - const { value } = context; - // deno-lint-ignore no-explicit-any - const maybeLength = (value as any)?.length; - const hasLength = maybeLength === expected; - - if (context.isNot) { - if (hasLength) { - throw new AssertionError( - `Expected value not to have length ${expected}, but it does`, - ); - } - } else { - if (!hasLength) { - throw new AssertionError( - `Expected value to have length ${expected}, but it does not. (The value has length ${maybeLength})`, - ); - } - } -} diff --git a/expect/_to_have_nth_returned_with.ts b/expect/_to_have_nth_returned_with.ts deleted file mode 100644 index 1cd1aa94e19c..000000000000 --- a/expect/_to_have_nth_returned_with.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { getMockCalls } from "./_mock_util.ts"; -import { inspectArg } from "./_inspect_args.ts"; - -export function toHaveNthReturnedWith( - context: MatcherContext, - nth: number, - expected: unknown, -): MatchResult { - if (nth < 1) { - throw new Error(`nth(${nth}) must be greater than 0`); - } - - const calls = getMockCalls(context.value); - const returned = calls.filter((call) => call.returns); - const returnIndex = nth - 1; - const maybeNthReturned = returned[returnIndex]; - const nthReturnedWithExpected = maybeNthReturned && - equal(maybeNthReturned.returned, expected); - - if (context.isNot) { - if (nthReturnedWithExpected) { - throw new AssertionError( - `Expected the mock function to not have n-th (n=${nth}) returned with ${ - inspectArg(expected) - }, but it did`, - ); - } - } else { - if (!nthReturnedWithExpected) { - throw new AssertionError( - `Expected the mock function to have n-th (n=${nth}) returned with ${ - inspectArg(expected) - }, but it did not`, - ); - } - } -} diff --git a/expect/_to_have_property.ts b/expect/_to_have_property.ts deleted file mode 100644 index 295d051890a2..000000000000 --- a/expect/_to_have_property.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { AssertionError } from "../assert/assertion_error.ts"; -import { equal } from "../assert/equal.ts"; -import { inspectArg } from "./_inspect_args.ts"; -import { MatcherContext, MatchResult } from "./_types.ts"; - -export function toHaveProperty( - context: MatcherContext, - propName: string | string[], - v?: unknown, -): MatchResult { - const { value } = context; - - let propPath = [] as string[]; - if (Array.isArray(propName)) { - propPath = propName; - } else { - propPath = propName.split("."); - } - - // deno-lint-ignore no-explicit-any - let current = value as any; - while (true) { - if (current === undefined || current === null) { - break; - } - if (propPath.length === 0) { - break; - } - const prop = propPath.shift()!; - current = current[prop]; - } - - let hasProperty; - if (v) { - hasProperty = current !== undefined && propPath.length === 0 && - equal(current, v); - } else { - hasProperty = current !== undefined && propPath.length === 0; - } - - let ofValue = ""; - if (v) { - ofValue = ` of the value ${inspectArg(v)}`; - } - - if (context.isNot) { - if (hasProperty) { - throw new AssertionError( - `Expected the value not to have the property ${ - propPath.join(".") - }${ofValue}, but it does.`, - ); - } - } else { - if (!hasProperty) { - throw new AssertionError( - `Expected the value to have the property ${ - propPath.join(".") - }${ofValue}, but it does not.`, - ); - } - } -} diff --git a/expect/_to_have_returned.ts b/expect/_to_have_returned.ts deleted file mode 100644 index 5813100ab1ba..000000000000 --- a/expect/_to_have_returned.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { getMockCalls } from "./_mock_util.ts"; - -export function toHaveReturned(context: MatcherContext): MatchResult { - const calls = getMockCalls(context.value); - const returned = calls.filter((call) => call.returns); - - if (context.isNot) { - if (returned.length > 0) { - throw new AssertionError( - `Expected the mock function to not have returned, but it returned ${returned.length} times`, - ); - } - } else { - if (returned.length === 0) { - throw new AssertionError( - `Expected the mock function to have returned, but it did not return`, - ); - } - } -} diff --git a/expect/_to_have_returned_times.ts b/expect/_to_have_returned_times.ts deleted file mode 100644 index 94e92af3b646..000000000000 --- a/expect/_to_have_returned_times.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { getMockCalls } from "./_mock_util.ts"; - -export function toHaveReturnedTimes( - context: MatcherContext, - expected: number, -): MatchResult { - const calls = getMockCalls(context.value); - const returned = calls.filter((call) => call.returns); - - if (context.isNot) { - if (returned.length === expected) { - throw new AssertionError( - `Expected the mock function to not have returned ${expected} times, but it returned ${returned.length} times`, - ); - } - } else { - if (returned.length !== expected) { - throw new AssertionError( - `Expected the mock function to have returned ${expected} times, but it returned ${returned.length} times`, - ); - } - } -} diff --git a/expect/_to_match.ts b/expect/_to_match.ts deleted file mode 100644 index 29bd3a14a824..000000000000 --- a/expect/_to_match.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertMatch } from "../assert/assert_match.ts"; -import { assertNotMatch } from "../assert/assert_not_match.ts"; - -/* Similar to assertStrictEquals(value, undefined) and assertNotStrictEquals(value, undefined)*/ -export function toMatch( - context: MatcherContext, - expected: RegExp, -): MatchResult { - if (context.isNot) { - return assertNotMatch( - String(context.value), - expected, - context.customMessage, - ); - } - return assertMatch(String(context.value), expected, context.customMessage); -} diff --git a/expect/_to_match_object.ts b/expect/_to_match_object.ts deleted file mode 100644 index 48fe7c5854a1..000000000000 --- a/expect/_to_match_object.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { assertObjectMatch } from "../assert/assert_object_match.ts"; -import { format } from "../assert/_format.ts"; - -/* Similar to assertObjectMatch(value, expected)*/ -export function toMatchObject( - context: MatcherContext, - expected: Record, -): MatchResult { - if (context.isNot) { - let objectMatch = false; - try { - assertObjectMatch( - // deno-lint-ignore no-explicit-any - context.value as Record, - expected, - context.customMessage, - ); - objectMatch = true; - const actualString = format(context.value); - const expectedString = format(expected); - throw new AssertionError( - `Expected ${actualString} to NOT match ${expectedString}`, - ); - } catch (e) { - if (objectMatch) { - throw e; - } - return; - } - } else { - assertObjectMatch( - // deno-lint-ignore no-explicit-any - context.value as Record, - expected, - context.customMessage, - ); - } -} diff --git a/expect/_to_strict_equal.ts b/expect/_to_strict_equal.ts deleted file mode 100644 index d5f99bdefca3..000000000000 --- a/expect/_to_strict_equal.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { assertNotStrictEquals } from "../assert/assert_not_strict_equals.ts"; -import { assertStrictEquals } from "../assert/assert_strict_equals.ts"; - -/* Similar to assertStrictEquals */ -export function toStrictEqual( - context: MatcherContext, - expected: unknown, -): MatchResult { - if (context.isNot) { - return assertNotStrictEquals( - context.value, - expected, - context.customMessage, - ); - } - return assertStrictEquals(context.value, expected, context.customMessage); -} diff --git a/expect/_to_throw.ts b/expect/_to_throw.ts deleted file mode 100644 index 0d39ce8151ce..000000000000 --- a/expect/_to_throw.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { MatcherContext, MatchResult } from "./_types.ts"; -import { AssertionError } from "../assert/assertion_error.ts"; -import { assertIsError } from "../assert/assert_is_error.ts"; - -/* Similar to assertIsError with value thrown error*/ -export function toThrow( - context: MatcherContext, - // deno-lint-ignore no-explicit-any - expected: new (...args: any[]) => E, -): MatchResult { - if (typeof context.value === "function") { - try { - context.value = context.value(); - } catch (err) { - context.value = err; - } - } - if (context.isNot) { - let isError = false; - try { - assertIsError(context.value, expected, undefined, context.customMessage); - isError = true; - throw new AssertionError(`Expected to NOT throw ${expected}`); - } catch (e) { - if (isError) { - throw e; - } - return; - } - } - return assertIsError( - context.value, - expected, - undefined, - context.customMessage, - ); -} diff --git a/expect/expect.ts b/expect/expect.ts index d9e9da1f1ee8..425175dca152 100644 --- a/expect/expect.ts +++ b/expect/expect.ts @@ -3,39 +3,40 @@ import type { AnyConstructor, Matcher, MatcherContext } from "./_types.ts"; import { AssertionError } from "../assert/assertion_error.ts"; -import { toBeCloseTo } from "./_to_be_close_to.ts"; -import { toBeDefined } from "./_to_be_defined.ts"; -import { toBeFalsy } from "./_to_be_falsy.ts"; -import { toBeGreaterThanOrEqual } from "./_to_be_greater_than_or_equal.ts"; -import { toBeGreaterThan } from "./_to_be_greater_than.ts"; -import { toBeInstanceOf } from "./_to_be_instance_of.ts"; -import { toBeLessThanOrEqual } from "./_to_be_less_than_or_equal.ts"; -import { toBeLessThan } from "./_to_be_less_than.ts"; -import { toBeNaN } from "./_to_be_nan.ts"; -import { toBeNull } from "./_to_be_null.ts"; -import { toBeTruthy } from "./_to_be_truthy.ts"; -import { toBeUndefined } from "./_to_be_undefined.ts"; -import { toBe } from "./_to_be.ts"; -import { toContain } from "./_to_contain.ts"; -import { toContainEqual } from "./_to_contain_equal.ts"; -import { toEqual } from "./_to_equal.ts"; -import { toHaveBeenCalledTimes } from "./_to_have_been_called_times.ts"; -import { toHaveBeenCalledWith } from "./_to_have_been_called_with.ts"; -import { toHaveBeenCalled } from "./_to_have_been_called.ts"; -import { toHaveBeenLastCalledWith } from "./_to_have_been_last_called_with.ts"; -import { toHaveBeenNthCalledWith } from "./_to_have_been_nth_called_with.ts"; -import { toHaveLength } from "./_to_have_length.ts"; -import { toHaveLastReturnedWith } from "./_to_have_last_returned_with.ts"; -import { toHaveNthReturnedWith } from "./_to_have_nth_returned_with.ts"; -import { toHaveProperty } from "./_to_have_property.ts"; -import { toHaveReturnedTimes } from "./_to_have_returned_times.ts"; -import { toHaveReturnedWith } from "./_to_have_returned_with.ts"; -import { toHaveReturned } from "./_to_have_returned.ts"; -import { toMatchObject } from "./_to_match_object.ts"; -import { toMatch } from "./_to_match.ts"; -import { toStrictEqual } from "./_to_strict_equal.ts"; -import { toThrow } from "./_to_throw.ts"; - +import { + toBe, + toBeCloseTo, + toBeDefined, + toBeFalsy, + toBeGreaterThan, + toBeGreaterThanOrEqual, + toBeInstanceOf, + toBeLessThan, + toBeLessThanOrEqual, + toBeNaN, + toBeNull, + toBeTruthy, + toBeUndefined, + toContain, + toContainEqual, + toEqual, + toHaveBeenCalled, + toHaveBeenCalledTimes, + toHaveBeenCalledWith, + toHaveBeenLastCalledWith, + toHaveBeenNthCalledWith, + toHaveLastReturnedWith, + toHaveLength, + toHaveNthReturnedWith, + toHaveProperty, + toHaveReturned, + toHaveReturnedTimes, + toHaveReturnedWith, + toMatch, + toMatchObject, + toStrictEqual, + toThrow, +} from "./_matchers.ts"; export interface Expected { lastCalledWith(...expected: unknown[]): void; lastReturnedWith(expected: unknown): void;