aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/test')
-rw-r--r--src/bun.js/test/jest.classes.ts4
-rw-r--r--src/bun.js/test/jest.zig143
2 files changed, 141 insertions, 6 deletions
diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts
index 8ed291ef5..de9f260d2 100644
--- a/src/bun.js/test/jest.classes.ts
+++ b/src/bun.js/test/jest.classes.ts
@@ -77,6 +77,10 @@ export default [
fn: "toBe",
length: 1,
},
+ toHaveBeenCalled: {
+ fn: "toHaveBeenCalled",
+ length: 0,
+ },
toHaveBeenCalledTimes: {
fn: "toHaveBeenCalledTimes",
length: 1,
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index a1260ecb1..f2e832ff9 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -599,7 +599,7 @@ pub const Jest = struct {
pub fn Bun__Jest__createTestModuleObject(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
- const module = JSC.JSValue.createEmptyObject(globalObject, 7);
+ const module = JSC.JSValue.createEmptyObject(globalObject, 11);
const test_fn = JSC.NewFunction(globalObject, ZigString.static("test"), 2, TestScope.call, false);
module.put(
@@ -697,11 +697,31 @@ pub const Jest = struct {
Expect.getConstructor(globalObject),
);
+ const mock_object = JSMockFunction__createObject(globalObject);
+ const spyOn = JSC.NewFunction(globalObject, ZigString.static("spyOn"), 2, JSMock__spyOn, false);
+ const restoreAllMocks = JSC.NewFunction(globalObject, ZigString.static("restoreAllMocks"), 2, jsFunctionResetSpies, false);
+ module.put(
+ globalObject,
+ ZigString.static("mock"),
+ mock_object,
+ );
+
+ const jest = JSValue.createEmptyObject(globalObject, 3);
+ jest.put(globalObject, ZigString.static("fn"), mock_object);
+ jest.put(globalObject, ZigString.static("spyOn"), spyOn);
+ jest.put(globalObject, ZigString.static("restoreAllMocks"), restoreAllMocks);
+ module.put(globalObject, ZigString.static("jest"), jest);
+ module.put(globalObject, ZigString.static("spyOn"), spyOn);
+
return module;
}
+ extern fn JSMockFunction__createObject(*JSC.JSGlobalObject) JSC.JSValue;
+
extern fn Bun__Jest__testPreloadObject(*JSC.JSGlobalObject) JSC.JSValue;
extern fn Bun__Jest__testModuleObject(*JSC.JSGlobalObject) JSC.JSValue;
+ extern fn jsFunctionResetSpies(*JSC.JSGlobalObject, *JSC.CallFrame) JSC.JSValue;
+ extern fn JSMock__spyOn(*JSC.JSGlobalObject, *JSC.CallFrame) JSC.JSValue;
pub fn call(
_: void,
@@ -3757,13 +3777,13 @@ pub const Expect = struct {
const _arguments = callFrame.arguments(1);
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
- if (arguments.len < 1) {
- globalObject.throwInvalidArguments("toMatch() requires 1 argument", .{});
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalObject.throw("toMatch() must be called in a test", .{});
return .zero;
}
- if (this.scope.tests.items.len <= this.test_id) {
- globalObject.throw("toMatch() must be called in a test", .{});
+ if (arguments.len < 1) {
+ globalObject.throwInvalidArguments("toMatch() requires 1 argument", .{});
return .zero;
}
@@ -3821,7 +3841,110 @@ pub const Expect = struct {
return .zero;
}
- pub const toHaveBeenCalledTimes = notImplementedJSCFn;
+ pub fn toHaveBeenCalled(this: *Expect, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ const thisValue = callframe.this();
+ defer this.postMatch(globalObject);
+
+ const value: JSValue = JSC.Jest.Expect.capturedValueGetCached(thisValue) orelse {
+ globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+
+ const calls = JSMockFunction__getCalls(value);
+ active_test_expectation_counter.actual += 1;
+
+ if (calls == .zero or !calls.jsType().isArray()) {
+ globalObject.throw("Expected value must be a mock function: {}", .{value});
+ return .zero;
+ }
+
+ var pass = calls.getLength(globalObject) > 0;
+
+ const not = this.op.contains(.not);
+ if (not) pass = !pass;
+ if (pass) return thisValue;
+
+ // handle failure
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true };
+ if (not) {
+ const signature = comptime getSignature("toHaveBeenCalled", "<green>expected<r>", true);
+ const fmt = signature ++ "\n\nExpected: not <green>{any}<r>\n";
+ if (Output.enable_ansi_colors) {
+ globalObject.throw(Output.prettyFmt(fmt, true), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+ globalObject.throw(Output.prettyFmt(fmt, false), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ } else {
+ const signature = comptime getSignature("toHaveBeenCalled", "<green>expected<r>", true);
+ const fmt = signature ++ "\n\nExpected <green>{any}<r>\n";
+ if (Output.enable_ansi_colors) {
+ globalObject.throw(Output.prettyFmt(fmt, true), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+ globalObject.throw(Output.prettyFmt(fmt, false), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+
+ unreachable;
+ }
+ pub fn toHaveBeenCalledTimes(this: *Expect, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
+ const thisValue = callframe.this();
+ const arguments_ = callframe.arguments(1);
+ const arguments: []const JSValue = arguments_.ptr[0..arguments_.len];
+ defer this.postMatch(globalObject);
+ const value: JSValue = JSC.Jest.Expect.capturedValueGetCached(thisValue) orelse {
+ globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
+ return .zero;
+ };
+
+ active_test_expectation_counter.actual += 1;
+
+ const calls = JSMockFunction__getCalls(value);
+
+ if (calls == .zero or !calls.jsType().isArray()) {
+ globalObject.throw("Expected value must be a mock function: {}", .{value});
+ return .zero;
+ }
+
+ if (arguments.len < 1 or !arguments[0].isAnyInt()) {
+ globalObject.throwInvalidArguments("toHaveBeenCalledTimes() requires 1 integer argument", .{});
+ return .zero;
+ }
+
+ const times = arguments[0].coerce(i32, globalObject);
+
+ var pass = @intCast(i32, calls.getLength(globalObject)) == times;
+
+ const not = this.op.contains(.not);
+ if (not) pass = !pass;
+ if (pass) return thisValue;
+
+ // handle failure
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true };
+ if (not) {
+ const signature = comptime getSignature("toHaveBeenCalled", "<green>expected<r>", true);
+ const fmt = signature ++ "\n\nExpected: not <green>{any}<r>\n";
+ if (Output.enable_ansi_colors) {
+ globalObject.throw(Output.prettyFmt(fmt, true), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+ globalObject.throw(Output.prettyFmt(fmt, false), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ } else {
+ const signature = comptime getSignature("toHaveBeenCalled", "<green>expected<r>", true);
+ const fmt = signature ++ "\n\nExpected <green>{any}<r>\n";
+ if (Output.enable_ansi_colors) {
+ globalObject.throw(Output.prettyFmt(fmt, true), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+ globalObject.throw(Output.prettyFmt(fmt, false), .{calls.toFmt(globalObject, &formatter)});
+ return .zero;
+ }
+
+ unreachable;
+ }
+
pub const toHaveBeenCalledWith = notImplementedJSCFn;
pub const toHaveBeenLastCalledWith = notImplementedJSCFn;
pub const toHaveBeenNthCalledWith = notImplementedJSCFn;
@@ -5007,3 +5130,11 @@ pub fn printGithubAnnotation(exception: *JSC.ZigException) void {
Output.printError("\n", .{});
Output.flush();
}
+
+/// JSValue.zero is used to indicate it was not a JSMockFunction
+/// If there were no calls, it returns an empty JSArray*
+extern fn JSMockFunction__getCalls(JSValue) JSValue;
+
+/// JSValue.zero is used to indicate it was not a JSMockFunction
+/// If there were no calls, it returns an empty JSArray*
+extern fn JSMockFunction__getReturns(JSValue) JSValue;