feat(expect): add asymmetric matchers (any, anything, arrayContaining) (#4366)

This commit is contained in:
Javier Hernández 2024-02-28 05:07:50 +01:00 committed by GitHub
parent 1e0764dc23
commit 5d15ab7596
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 153 additions and 2 deletions

15
expect/_any_test.ts Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { expect } from "./expect.ts";
class Cat {}
Deno.test("expect.any()", () => {
expect(new Cat()).toEqual(expect.any(Cat));
expect(new Cat()).toEqual(expect.any(Object));
expect("Hello").toEqual(expect.any(String));
expect(1).toEqual(expect.any(Number));
expect(() => {}).toEqual(expect.any(Function));
expect(false).toEqual(expect.any(Boolean));
expect(BigInt(1)).toEqual(expect.any(BigInt));
expect(Symbol("sym")).toEqual(expect.any(Symbol));
});

27
expect/_anything_test.ts Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { expect } from "./expect.ts";
import { AssertionError, assertThrows } from "../assert/mod.ts";
import { fn } from "./fn.ts";
Deno.test("expect.anything() as a parameter of toEqual()", () => {
expect(null).not.toEqual(expect.anything());
expect(undefined).not.toEqual(expect.anything());
expect(1).toEqual(expect.anything());
assertThrows(() => {
expect(null).toEqual(expect.anything());
}, AssertionError);
assertThrows(() => {
expect(undefined).toEqual(expect.anything());
}, AssertionError);
assertThrows(() => {
expect(1).not.toEqual(expect.anything());
}, AssertionError);
});
Deno.test("expect.anything() as a parameter of toHaveBeenCalled()", () => {
const mockFn = fn();
mockFn(3);
expect(mockFn).toHaveBeenCalledWith(expect.anything());
});

View File

@ -0,0 +1,16 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { expect } from "./expect.ts";
Deno.test("expect.arrayContaining() with array of numbers", () => {
const arr = [1, 2, 3];
expect([1, 2, 3, 4]).toEqual(expect.arrayContaining(arr));
expect([4, 5, 6]).not.toEqual(expect.arrayContaining(arr));
expect([1, 2, 3]).toEqual(expect.arrayContaining(arr));
});
Deno.test("expect.arrayContaining() with array of mixed types", () => {
const arr = [1, 2, "hello"];
expect([1, 2, 3, "hello", "bye"]).toEqual(expect.arrayContaining(arr));
expect([4, "bye"]).not.toEqual(expect.arrayContaining(arr));
});

View File

@ -0,0 +1,79 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// deno-lint-ignore-file no-explicit-any
abstract class AsymmetricMatcher<T> {
constructor(
protected value: T,
) {}
abstract equals(other: unknown): boolean;
}
export class Anything extends AsymmetricMatcher<void> {
equals(other: unknown): boolean {
return other !== null && other !== undefined;
}
}
export function anything(): Anything {
return new Anything();
}
export class Any extends AsymmetricMatcher<any> {
constructor(value: unknown) {
if (value === undefined) {
throw TypeError("Expected a constructor function");
}
super(value);
}
equals(other: unknown): boolean {
if (typeof other === "object") {
return other instanceof this.value;
} else {
if (this.value == Number) {
return typeof other === "number";
}
if (this.value == String) {
return typeof other == "string";
}
if (this.value == Number) {
return typeof other == "number";
}
if (this.value == Function) {
return typeof other == "function";
}
if (this.value == Boolean) {
return typeof other == "boolean";
}
if (this.value == BigInt) {
return typeof other == "bigint";
}
if (this.value == Symbol) {
return typeof other == "symbol";
}
}
return false;
}
}
export function any(c: unknown): Any {
return new Any(c);
}
export class ArrayContaining extends AsymmetricMatcher<any[]> {
constructor(arr: any[]) {
super(arr);
}
equals(other: any[]): boolean {
return this.value.every((e) => other.includes(e));
}
}
export function arrayContaining(c: any[]): ArrayContaining {
return new ArrayContaining(c);
}

View File

@ -3,6 +3,7 @@
// This file is copied from `std/assert`. // This file is copied from `std/assert`.
import type { EqualOptions } from "./_types.ts"; import type { EqualOptions } from "./_types.ts";
import { Any, Anything, ArrayContaining } from "./_asymmetric_matchers.ts";
function isKeyedCollection(x: unknown): x is Set<unknown> { function isKeyedCollection(x: unknown): x is Set<unknown> {
return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>)); return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));
@ -52,6 +53,15 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
) { ) {
return String(a) === String(b); return String(a) === String(b);
} }
if (b instanceof Anything) {
return b.equals(a);
}
if (b instanceof Any) {
return b.equals(a);
}
if (b instanceof ArrayContaining && a instanceof Array) {
return b.equals(a);
}
if (a instanceof Date && b instanceof Date) { if (a instanceof Date && b instanceof Date) {
const aTime = a.getTime(); const aTime = a.getTime();
const bTime = b.getTime(); const bTime = b.getTime();
@ -95,7 +105,7 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
if (!strictCheck) { if (!strictCheck) {
if (aLen > 0) { if (aLen > 0) {
for (let i = 0; i < aKeys.length; i += 1) { for (let i = 0; i < aKeys.length; i += 1) {
const key = aKeys[i]; const key = aKeys[i]!;
if ( if (
(key in a) && (a[key as keyof typeof a] === undefined) && (key in a) && (a[key as keyof typeof a] === undefined) &&
!(key in b) !(key in b)
@ -107,7 +117,7 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
if (bLen > 0) { if (bLen > 0) {
for (let i = 0; i < bKeys.length; i += 1) { for (let i = 0; i < bKeys.length; i += 1) {
const key = bKeys[i]; const key = bKeys[i]!;
if ( if (
(key in b) && (b[key as keyof typeof b] === undefined) && (key in b) && (b[key as keyof typeof b] === undefined) &&
!(key in a) !(key in a)

View File

@ -47,6 +47,7 @@ import {
toThrow, toThrow,
} from "./_matchers.ts"; } from "./_matchers.ts";
import { isPromiseLike } from "./_utils.ts"; import { isPromiseLike } from "./_utils.ts";
import { any, anything, arrayContaining } from "./_asymmetric_matchers.ts";
const matchers: Record<MatcherKey, Matcher> = { const matchers: Record<MatcherKey, Matcher> = {
lastCalledWith: toHaveBeenLastCalledWith, lastCalledWith: toHaveBeenLastCalledWith,
@ -168,3 +169,6 @@ export function expect(value: unknown, customMessage?: string): Expected {
} }
expect.addEqualityTesters = addCustomEqualityTesters; expect.addEqualityTesters = addCustomEqualityTesters;
expect.anything = anything;
expect.any = any;
expect.arrayContaining = arrayContaining;