diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index c620050eca83d3..56d847b74bee6a 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -341,6 +341,15 @@ declare module "bun:test" { * expect(undefined).toBeDefined(); // fail */ toBeDefined(): void; + /** + * Asserts that the expected value is an instance of value + * + * @example + * expect([]).toBeInstanceOf(Array); + * expect(null).toBeInstanceOf(Array); // fail + */ + toBeInstanceOf(value: Function): void; + /** * Asserts that a value is `undefined`. * diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 5571294c7ce831..2cb86c4f72afcf 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -2080,6 +2080,70 @@ pub const Expect = struct { return .zero; } + pub fn toBeInstanceOf(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalObject); + + const thisValue = callFrame.this(); + const _arguments = callFrame.arguments(1); + const arguments: []const JSValue = _arguments.ptr[0.._arguments.len]; + + if (arguments.len < 1) { + globalObject.throwInvalidArguments("toBeInstanceOf() requires 1 argument", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toBeInstanceOf() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const expected_value = arguments[0]; + if (!expected_value.jsType().isFunction()) { + var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + globalObject.throw("Expected value must be a function: {any}", .{expected_value.toFmt(globalObject, &fmt)}); + return .zero; + } + expected_value.ensureStillAlive(); + + const value = Expect.capturedValueGetCached(thisValue) orelse { + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + value.ensureStillAlive(); + + const not = this.op.contains(.not); + var pass = value.isInstanceOf(globalObject, expected_value); + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + const value_fmt = value.toFmt(globalObject, &formatter); + if (not) { + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeInstanceOf", "", true) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeInstanceOf", "", false) ++ "\n\n" ++ received_line; + if (Output.enable_ansi_colors) { + globalObject.throw(Output.prettyFmt(fmt, true), .{value_fmt}); + return .zero; + } + + globalObject.throw(Output.prettyFmt(fmt, false), .{value_fmt}); + return .zero; + } + pub const toHaveBeenCalledTimes = notImplementedJSCFn; pub const toHaveBeenCalledWith = notImplementedJSCFn; pub const toHaveBeenLastCalledWith = notImplementedJSCFn; @@ -2089,7 +2153,6 @@ pub const Expect = struct { pub const toHaveLastReturnedWith = notImplementedJSCFn; pub const toHaveNthReturnedWith = notImplementedJSCFn; pub const toBeCloseTo = notImplementedJSCFn; - pub const toBeInstanceOf = notImplementedJSCFn; pub const toContainEqual = notImplementedJSCFn; pub const toMatch = notImplementedJSCFn; pub const toMatchObject = notImplementedJSCFn; diff --git a/test/bun.js/bun-test/matchers.test.ts b/test/bun.js/bun-test/matchers.test.ts new file mode 100644 index 00000000000000..8322c55a6a3a15 --- /dev/null +++ b/test/bun.js/bun-test/matchers.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, test } from "bun:test"; + +describe("different kinds of matchers", () => { + test("toBeInstanceOf", () => { + expect({}).toBeInstanceOf(Object); + expect(new String("test")).toBeInstanceOf(String); + expect(() => {}).toBeInstanceOf(Function); + class A {} + expect(new A()).toBeInstanceOf(A); + }); +}); \ No newline at end of file