mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
feat(expect): add expect.{closeTo, stringContaining, stringMatching}
(#4508)
This commit is contained in:
parent
4c78e13be6
commit
1c38d2c886
@ -1,7 +1,7 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
|
||||
abstract class AsymmetricMatcher<T> {
|
||||
export abstract class AsymmetricMatcher<T> {
|
||||
constructor(
|
||||
protected value: T,
|
||||
) {}
|
||||
@ -21,10 +21,11 @@ export function anything(): Anything {
|
||||
export class Any extends AsymmetricMatcher<any> {
|
||||
constructor(value: unknown) {
|
||||
if (value === undefined) {
|
||||
throw TypeError("Expected a constructor function");
|
||||
throw new TypeError("Expected a constructor function");
|
||||
}
|
||||
super(value);
|
||||
}
|
||||
|
||||
equals(other: unknown): boolean {
|
||||
if (typeof other === "object") {
|
||||
return other instanceof this.value;
|
||||
@ -69,11 +70,78 @@ export class ArrayContaining extends AsymmetricMatcher<any[]> {
|
||||
constructor(arr: any[]) {
|
||||
super(arr);
|
||||
}
|
||||
|
||||
equals(other: any[]): boolean {
|
||||
return this.value.every((e) => other.includes(e));
|
||||
return Array.isArray(other) && this.value.every((e) => other.includes(e));
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayContaining(c: any[]): ArrayContaining {
|
||||
return new ArrayContaining(c);
|
||||
}
|
||||
|
||||
export class CloseTo extends AsymmetricMatcher<number> {
|
||||
readonly #precision: number;
|
||||
|
||||
constructor(num: number, precision: number = 2) {
|
||||
super(num);
|
||||
this.#precision = precision;
|
||||
}
|
||||
|
||||
equals(other: number): boolean {
|
||||
if (typeof other !== "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
(this.value === Number.POSITIVE_INFINITY &&
|
||||
other === Number.POSITIVE_INFINITY) ||
|
||||
(this.value === Number.NEGATIVE_INFINITY &&
|
||||
other === Number.NEGATIVE_INFINITY)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Math.abs(this.value - other) < Math.pow(10, -this.#precision) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
export function closeTo(num: number, numDigits?: number): CloseTo {
|
||||
return new CloseTo(num, numDigits);
|
||||
}
|
||||
|
||||
export class StringContaining extends AsymmetricMatcher<string> {
|
||||
constructor(str: string) {
|
||||
super(str);
|
||||
}
|
||||
|
||||
equals(other: string): boolean {
|
||||
if (typeof other !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return other.includes(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export function stringContaining(str: string): StringContaining {
|
||||
return new StringContaining(str);
|
||||
}
|
||||
|
||||
export class StringMatching extends AsymmetricMatcher<RegExp> {
|
||||
constructor(pattern: string | RegExp) {
|
||||
super(new RegExp(pattern));
|
||||
}
|
||||
|
||||
equals(other: string): boolean {
|
||||
if (typeof other !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.value.test(other);
|
||||
}
|
||||
}
|
||||
|
||||
export function stringMatching(pattern: string | RegExp): StringMatching {
|
||||
return new StringMatching(pattern);
|
||||
}
|
||||
|
21
expect/_close_to_test.ts
Normal file
21
expect/_close_to_test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { expect } from "./expect.ts";
|
||||
|
||||
Deno.test("expect.closeTo()", () => {
|
||||
expect(0.1 + 0.2).toEqual(expect.closeTo(0.3));
|
||||
expect(Math.PI).toEqual(expect.closeTo(3.14));
|
||||
expect(Number.POSITIVE_INFINITY).toEqual(
|
||||
expect.closeTo(Number.POSITIVE_INFINITY),
|
||||
);
|
||||
expect(Number.NEGATIVE_INFINITY).toEqual(
|
||||
expect.closeTo(Number.NEGATIVE_INFINITY),
|
||||
);
|
||||
|
||||
expect(0.1 + 0.2).not.toEqual(expect.closeTo(0.3, 17));
|
||||
expect(0.999_999).not.toEqual(expect.closeTo(1, 10));
|
||||
expect(NaN).not.toEqual(expect.closeTo(NaN));
|
||||
expect(Number.POSITIVE_INFINITY).not.toEqual(
|
||||
expect.closeTo(Number.NEGATIVE_INFINITY),
|
||||
);
|
||||
});
|
@ -3,7 +3,7 @@
|
||||
// This file is copied from `std/assert`.
|
||||
|
||||
import type { EqualOptions } from "./_types.ts";
|
||||
import { Any, Anything, ArrayContaining } from "./_asymmetric_matchers.ts";
|
||||
import { AsymmetricMatcher } from "./_asymmetric_matchers.ts";
|
||||
|
||||
function isKeyedCollection(x: unknown): x is Set<unknown> {
|
||||
return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));
|
||||
@ -15,6 +15,23 @@ function constructorsEqual(a: object, b: object) {
|
||||
!a.constructor && b.constructor === Object;
|
||||
}
|
||||
|
||||
function asymmetricEqual(a: unknown, b: unknown) {
|
||||
const asymmetricA = a instanceof AsymmetricMatcher;
|
||||
const asymmetricB = b instanceof AsymmetricMatcher;
|
||||
|
||||
if (asymmetricA && asymmetricB) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (asymmetricA) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
if (asymmetricB) {
|
||||
return b.equals(a);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep equality comparison used in assertions
|
||||
* @param c actual value
|
||||
@ -48,15 +65,12 @@ export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
|
||||
) {
|
||||
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);
|
||||
|
||||
const asymmetric = asymmetricEqual(a, b);
|
||||
if (asymmetric !== undefined) {
|
||||
return asymmetric;
|
||||
}
|
||||
|
||||
if (a instanceof Date && b instanceof Date) {
|
||||
const aTime = a.getTime();
|
||||
const bTime = b.getTime();
|
||||
|
18
expect/_string_containing_test.ts
Normal file
18
expect/_string_containing_test.ts
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { expect } from "./expect.ts";
|
||||
|
||||
Deno.test("expect.stringContaining() with strings", () => {
|
||||
expect("https://deno.com/").toEqual(expect.stringContaining("deno"));
|
||||
expect("function").toEqual(expect.stringContaining("func"));
|
||||
|
||||
expect("Hello, World").not.toEqual(expect.stringContaining("hello"));
|
||||
expect("foobar").not.toEqual(expect.stringContaining("bazz"));
|
||||
});
|
||||
|
||||
Deno.test("expect.stringContaining() with other types", () => {
|
||||
expect(123).not.toEqual(expect.stringContaining("1"));
|
||||
expect(true).not.toEqual(expect.stringContaining("true"));
|
||||
expect(["foo", "bar"]).not.toEqual(expect.stringContaining("foo"));
|
||||
expect({ foo: "bar" }).not.toEqual(expect.stringContaining(`{ foo: "bar" }`));
|
||||
});
|
19
expect/_string_matching_test.ts
Normal file
19
expect/_string_matching_test.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { expect } from "./expect.ts";
|
||||
|
||||
Deno.test("expect.stringMatching() with strings", () => {
|
||||
expect("deno_std").toEqual(expect.stringMatching("std"));
|
||||
expect("function").toEqual(expect.stringMatching("func"));
|
||||
|
||||
expect("Hello, World").not.toEqual(expect.stringMatching("hello"));
|
||||
expect("foobar").not.toEqual(expect.stringMatching("bazz"));
|
||||
});
|
||||
|
||||
Deno.test("expect.stringMatching() with RegExp", () => {
|
||||
expect("deno_std").toEqual(expect.stringMatching(/std/));
|
||||
expect("0123456789").toEqual(expect.stringMatching(/\d+/));
|
||||
|
||||
expect("\e").not.toEqual(expect.stringMatching(/\s/));
|
||||
expect("queue").not.toEqual(expect.stringMatching(/en/));
|
||||
});
|
@ -51,7 +51,14 @@ import {
|
||||
toThrow,
|
||||
} from "./_matchers.ts";
|
||||
import { isPromiseLike } from "./_utils.ts";
|
||||
import { any, anything, arrayContaining } from "./_asymmetric_matchers.ts";
|
||||
import {
|
||||
any,
|
||||
anything,
|
||||
arrayContaining,
|
||||
closeTo,
|
||||
stringContaining,
|
||||
stringMatching,
|
||||
} from "./_asymmetric_matchers.ts";
|
||||
|
||||
const matchers: Record<MatcherKey, Matcher> = {
|
||||
lastCalledWith: toHaveBeenLastCalledWith,
|
||||
@ -191,6 +198,10 @@ export function expect(value: unknown, customMessage?: string): Expected {
|
||||
|
||||
expect.addEqualityTesters = addCustomEqualityTesters;
|
||||
expect.extend = setExtendMatchers;
|
||||
|
||||
expect.anything = anything;
|
||||
expect.any = any;
|
||||
expect.arrayContaining = arrayContaining;
|
||||
expect.closeTo = closeTo;
|
||||
expect.stringContaining = stringContaining;
|
||||
expect.stringMatching = stringMatching;
|
||||
|
125
expect/mod.ts
125
expect/mod.ts
@ -3,70 +3,71 @@
|
||||
// This module is browser compatible.
|
||||
|
||||
/**
|
||||
* This module provides jest compatible expect assertion functionality.
|
||||
* This module provides Jest compatible expect assertion functionality.
|
||||
*
|
||||
* 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`
|
||||
* Currently this module supports the following functions:
|
||||
* - Common matchers:
|
||||
* - `toBe`
|
||||
* - `toEqual`
|
||||
* - `toStrictEqual`
|
||||
* - `toMatch`
|
||||
* - `toMatchObject`
|
||||
* - `toBeDefined`
|
||||
* - `toBeUndefined`
|
||||
* - `toBeNull`
|
||||
* - `toBeNaN`
|
||||
* - `toBeTruthy`
|
||||
* - `toBeFalsy`
|
||||
* - `toContain`
|
||||
* - `toContainEqual`
|
||||
* - `toHaveLength`
|
||||
* - `toBeGreaterThan`
|
||||
* - `toBeGreaterThanOrEqual`
|
||||
* - `toBeLessThan`
|
||||
* - `toBeLessThanOrEqual`
|
||||
* - `toBeCloseTo`
|
||||
* - `toBeInstanceOf`
|
||||
* - `toThrow`
|
||||
* - `toHaveProperty`
|
||||
* - `toHaveLength`
|
||||
* - Mock related matchers:
|
||||
* - `toHaveBeenCalled`
|
||||
* - `toHaveBeenCalledTimes`
|
||||
* - `toHaveBeenCalledWith`
|
||||
* - `toHaveBeenLastCalledWith`
|
||||
* - `toHaveBeenNthCalledWith`
|
||||
* - `toHaveReturned`
|
||||
* - `toHaveReturnedTimes`
|
||||
* - `toHaveReturnedWith`
|
||||
* - `toHaveLastReturnedWith`
|
||||
* - `toHaveNthReturnedWith`
|
||||
* - Asymmetric matchers:
|
||||
* - `expect.anything`
|
||||
* - `expect.any`
|
||||
* - `expect.arrayContaining`
|
||||
* - `expect.not.arrayContaining`
|
||||
* - `expect.closeTo`
|
||||
* - `expect.stringContaining`
|
||||
* - `expect.not.stringContaining`
|
||||
* - `expect.stringMatching`
|
||||
* - `expect.not.stringMatching`
|
||||
* - Utilities:
|
||||
* - `expect.addEqualityTester`
|
||||
* - `expect.extend`
|
||||
*
|
||||
* 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`
|
||||
* Only these functions are still not available:
|
||||
* - Matchers:
|
||||
* - `toMatchSnapShot`
|
||||
* - `toMatchInlineSnapShot`
|
||||
* - `toThrowErrorMatchingSnapShot`
|
||||
* - `toThrowErrorMatchingInlineSnapShot`
|
||||
* - Asymmetric matchers:
|
||||
* - `expect.objectContaining`
|
||||
* - `expect.not.objectContaining`
|
||||
* - Utilities:
|
||||
* - `expect.assertions`
|
||||
* - `expect.hasAssertions`
|
||||
* - `expect.addSnapshotSerializer`
|
||||
*
|
||||
* This module is largely inspired by {@link https://github.com/allain/expect | x/expect} module by Allain Lalonde.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user