aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/bun-types/bun-test.d.ts20
-rw-r--r--src/bun.js/test/jest.zig99
-rw-r--r--test/js/bun/test/expect.test.ts49
-rw-r--r--test/js/bun/test/test-test.test.ts2
4 files changed, 167 insertions, 3 deletions
diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts
index d9bf36124..25997e2bb 100644
--- a/packages/bun-types/bun-test.d.ts
+++ b/packages/bun-types/bun-test.d.ts
@@ -33,7 +33,7 @@ declare module "bun:test" {
*/
export type Describe = {
(label: string, fn: () => void): void;
- skip: (label: string, fn: () => void) => void
+ skip: (label: string, fn: () => void) => void;
};
/**
* Describes a group of related tests.
@@ -243,6 +243,24 @@ declare module "bun:test" {
*/
toBe(expected: T): void;
/**
+ * Asserts that value is close to the expected by floating point precision.
+ *
+ * For example, the following fails because arithmetic on decimal (base 10)
+ * values often have rounding errors in limited precision binary (base 2) representation.
+ *
+ * @example
+ * expect(0.2 + 0.1).toBe(0.3); // fails
+ *
+ * Use `toBeCloseTo` to compare floating point numbers for approximate equality.
+ *
+ * @example
+ * expect(0.2 + 0.1).toBeCloseTo(0.3, 5); // passes
+ *
+ * @param expected the expected value
+ * @param numDigits the number of digits to check after the decimal point. Default is `2`
+ */
+ toBeCloseTo(expected: number, numDigits?: number): void;
+ /**
* Asserts that a value is deeply equal to what is expected.
*
* @example
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index 5b0315f78..e7f83110c 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -2169,6 +2169,104 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toBeCloseTo(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue {
+ defer this.postMatch(globalObject);
+
+ const thisValue = callFrame.this();
+ const thisArguments = callFrame.arguments(2);
+ const arguments = thisArguments.ptr[0..thisArguments.len];
+
+ if (arguments.len < 1) {
+ globalObject.throwInvalidArguments("toBeCloseTo() requires at least 1 argument. Expected value must be a number", .{});
+ return .zero;
+ }
+
+ const expected_ = arguments[0];
+ if (!expected_.isNumber()) {
+ globalObject.throwInvalidArgumentType("toBeCloseTo", "expected", "number");
+ return .zero;
+ }
+
+ var precision: f64 = 2.0;
+ if (arguments.len > 1) {
+ const precision_ = arguments[1];
+ if (!precision_.isNumber()) {
+ globalObject.throwInvalidArgumentType("toBeCloseTo", "precision", "number");
+ return .zero;
+ }
+
+ precision = precision_.asNumber();
+ }
+
+ const received_: JSC.JSValue = Expect.capturedValueGetCached(thisValue) orelse {
+ globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+
+ if (!received_.isNumber()) {
+ globalObject.throwInvalidArgumentType("expect", "received", "number");
+ return .zero;
+ }
+
+ var expected = expected_.asNumber();
+ var received = received_.asNumber();
+
+ if (std.math.isNegativeInf(expected)) {
+ expected = -expected;
+ }
+
+ if (std.math.isNegativeInf(received)) {
+ received = -received;
+ }
+
+ if (std.math.isPositiveInf(expected) and std.math.isPositiveInf(received)) {
+ return thisValue;
+ }
+
+ const expected_diff = std.math.pow(f64, 10, -precision) / 2;
+ const actual_diff = std.math.fabs(received - expected);
+ var pass = actual_diff < expected_diff;
+
+ const not = this.op.contains(.not);
+ if (not) pass = !pass;
+
+ if (pass) return thisValue;
+
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true };
+
+ const expected_fmt = expected_.toFmt(globalObject, &formatter);
+ const received_fmt = received_.toFmt(globalObject, &formatter);
+
+ const expected_line = "Expected: <green>{any}<r>\n";
+ const received_line = "Received: <red>{any}<r>\n";
+ const expected_precision = "Expected precision: {d}\n";
+ const expected_difference = "Expected difference: \\< <green>{d}<r>\n";
+ const received_difference = "Received difference: <red>{d}<r>\n";
+
+ const suffix_fmt = "\n\n" ++ expected_line ++ received_line ++ "\n" ++ expected_precision ++ expected_difference ++ received_difference;
+
+ if (not) {
+ const fmt = comptime getSignature("toBeCloseTo", "<green>expected<r>, precision", true) ++ suffix_fmt;
+ if (Output.enable_ansi_colors) {
+ globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt, precision, expected_diff, actual_diff });
+ return .zero;
+ }
+
+ globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt, precision, expected_diff, actual_diff });
+ return .zero;
+ }
+
+ const fmt = comptime getSignature("toBeCloseTo", "<green>expected<r>, precision", false) ++ suffix_fmt;
+
+ if (Output.enable_ansi_colors) {
+ globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt, precision, expected_diff, actual_diff });
+ return .zero;
+ }
+
+ globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt, precision, expected_diff, actual_diff });
+ return .zero;
+ }
+
pub fn toBeOdd(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue {
defer this.postMatch(globalObject);
@@ -2948,7 +3046,6 @@ pub const Expect = struct {
pub const toHaveReturnedWith = notImplementedJSCFn;
pub const toHaveLastReturnedWith = notImplementedJSCFn;
pub const toHaveNthReturnedWith = notImplementedJSCFn;
- pub const toBeCloseTo = notImplementedJSCFn;
pub const toContainEqual = notImplementedJSCFn;
pub const toMatchObject = notImplementedJSCFn;
pub const toMatchInlineSnapshot = notImplementedJSCFn;
diff --git a/test/js/bun/test/expect.test.ts b/test/js/bun/test/expect.test.ts
index ae535a6f7..2fabd6e66 100644
--- a/test/js/bun/test/expect.test.ts
+++ b/test/js/bun/test/expect.test.ts
@@ -78,4 +78,53 @@ describe("expect()", () => {
test(label, () => expect(value).toMatch(matched));
}
});
+
+ describe("toBeCloseTo()", () => {
+ const passTests = [
+ [0, 0],
+ [0, 0.001],
+ [1.23, 1.229],
+ [1.23, 1.226],
+ [1.23, 1.225],
+ [1.23, 1.234],
+ [Infinity, Infinity],
+ [-Infinity, -Infinity],
+ [0, 0.1, 0],
+ [0, 0.0001, 3],
+ [0, 0.000004, 5],
+ [2.0000002, 2, 5],
+ ];
+ for (const [actual, expected, precision] of passTests) {
+ if (precision === undefined) {
+ test(`actual = ${actual}, expected = ${expected}`, () => {
+ expect(actual).toBeCloseTo(expected);
+ });
+ } else {
+ test(`actual = ${actual}, expected = ${expected}, precision = ${precision}`, () => {
+ expect(actual).toBeCloseTo(expected, precision);
+ });
+ }
+ }
+ const failTests = [
+ [0, 0.01],
+ [1, 1.23],
+ [1.23, 1.2249999],
+ [Infinity, -Infinity],
+ [Infinity, 1.23],
+ [-Infinity, -1.23],
+ [3.141592e-7, 3e-7, 8],
+ [56789, 51234, -4],
+ ];
+ for (const [actual, expected, precision] of failTests) {
+ if (precision === undefined) {
+ test(`actual = ${actual}, expected != ${expected}`, () => {
+ expect(actual).not.toBeCloseTo(expected);
+ });
+ } else {
+ test(`actual = ${actual}, expected != ${expected}, precision = ${precision}`, () => {
+ expect(actual).not.toBeCloseTo(expected, precision);
+ });
+ }
+ }
+ });
});
diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts
index c739a8921..ed00c57ed 100644
--- a/test/js/bun/test/test-test.test.ts
+++ b/test/js/bun/test/test-test.test.ts
@@ -1,6 +1,6 @@
// @ts-nocheck
import { spawn, spawnSync } from "bun";
-import { describe, expect, it, test } from "bun:test";
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test } from "bun:test";
import { mkdirSync, realpathSync, rmSync, writeFileSync } from "fs";
import { mkdtemp, rm, writeFile } from "fs/promises";
import { bunEnv, bunExe } from "harness";