diff options
Diffstat (limited to 'src/bun.js/test/jest.zig')
-rw-r--r-- | src/bun.js/test/jest.zig | 305 |
1 files changed, 198 insertions, 107 deletions
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index f2e832ff9..93edb8abd 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -36,6 +36,7 @@ const ZigString = JSC.ZigString; const JSInternalPromise = JSC.JSInternalPromise; const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; +const JSType = JSValue.JSType; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; const JSObject = JSC.JSObject; @@ -772,6 +773,108 @@ pub const Jest = struct { } }; +pub const ExpectAnything = struct { + pub usingnamespace JSC.Codegen.JSExpectAnything; + + pub fn finalize( + this: *ExpectAnything, + ) callconv(.C) void { + VirtualMachine.get().allocator.destroy(this); + } + + pub fn call(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + const anything = globalObject.bunVM().allocator.create(ExpectAnything) catch unreachable; + if (Jest.runner.?.pending_test == null) { + const err = globalObject.createErrorInstance("expect.anything() must be called in a test", .{}); + err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject)); + globalObject.throwValue(err); + return .zero; + } + + const anything_js_value = anything.toJS(globalObject); + anything_js_value.ensureStillAlive(); + + var vm = globalObject.bunVM(); + vm.autoGarbageCollect(); + + return anything_js_value; + } +}; + +pub const ExpectStringMatching = struct { + pub usingnamespace JSC.Codegen.JSExpectStringMatching; + + pub fn finalize( + this: *ExpectStringMatching, + ) callconv(.C) void { + VirtualMachine.get().allocator.destroy(this); + } + + pub fn call(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + const args = callFrame.arguments(1).slice(); + + if (args.len == 0 or (!args[0].isString() and !args[0].isRegExp())) { + const fmt = "<d>expect.<r>stringContaining<d>(<r>string<d>)<r>\n\nExpected a string or regular expression\n"; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const test_value = args[0]; + const string_matching = globalObject.bunVM().allocator.create(ExpectStringMatching) catch unreachable; + + if (Jest.runner.?.pending_test == null) { + const err = globalObject.createErrorInstance("expect.stringContaining() must be called in a test", .{}); + err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject)); + globalObject.throwValue(err); + return .zero; + } + + const string_matching_js_value = string_matching.toJS(globalObject); + ExpectStringMatching.testValueSetCached(string_matching_js_value, globalObject, test_value); + + var vm = globalObject.bunVM(); + vm.autoGarbageCollect(); + return string_matching_js_value; + } +}; + +pub const ExpectStringContaining = struct { + pub usingnamespace JSC.Codegen.JSExpectStringContaining; + + pub fn finalize( + this: *ExpectStringContaining, + ) callconv(.C) void { + VirtualMachine.get().allocator.destroy(this); + } + + pub fn call(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + const args = callFrame.arguments(1).slice(); + + if (args.len == 0 or !args[0].isString()) { + const fmt = "<d>expect.<r>stringContaining<d>(<r>string<d>)<r>\n\nExpected a string\n"; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const string_value = args[0]; + + const string_containing = globalObject.bunVM().allocator.create(ExpectStringContaining) catch unreachable; + + if (Jest.runner.?.pending_test == null) { + const err = globalObject.createErrorInstance("expect.stringContaining() must be called in a test", .{}); + err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject)); + globalObject.throwValue(err); + return .zero; + } + + const string_containing_js_value = string_containing.toJS(globalObject); + ExpectStringContaining.stringValueSetCached(string_containing_js_value, globalObject, string_value); + + var vm = globalObject.bunVM(); + vm.autoGarbageCollect(); + return string_containing_js_value; + } +}; pub const ExpectAny = struct { pub usingnamespace JSC.Codegen.JSExpectAny; @@ -810,7 +913,7 @@ pub const ExpectAny = struct { any.* = .{}; const any_js_value = any.toJS(globalObject); any_js_value.ensureStillAlive(); - JSC.Jest.ExpectAny.constructorValueSetCached(any_js_value, globalObject, constructor); + ExpectAny.constructorValueSetCached(any_js_value, globalObject, constructor); any_js_value.ensureStillAlive(); var vm = globalObject.bunVM(); @@ -888,7 +991,7 @@ pub const Expect = struct { pub fn call(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { const arguments_ = callframe.arguments(1); if (arguments_.len < 1) { - globalObject.throw("expect() requires one argument", .{}); + globalObject.throw("expect() requires one argument\n", .{}); return .zero; } const arguments = arguments_.ptr[0..arguments_.len]; @@ -1523,26 +1626,23 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - const diff_formatter = DiffFormatter{ .received = value, .expected = expected, .globalObject = globalObject, .not = not }; + const diff_formatter = DiffFormatter{ + .received = value, + .expected = expected, + .globalObject = globalObject, + .not = not, + }; if (not) { const signature = comptime getSignature("toEqual", "<green>expected<r>", true); const fmt = signature ++ "\n\n{any}\n"; - if (Output.enable_ansi_colors) { - globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); - return .zero; - } - globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); + globalObject.throwPretty(fmt, .{diff_formatter}); return .zero; } const signature = comptime getSignature("toEqual", "<green>expected<r>", false); const fmt = signature ++ "\n\n{any}\n"; - if (Output.enable_ansi_colors) { - globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); - return .zero; - } - globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); + globalObject.throwPretty(fmt, .{diff_formatter}); return .zero; } @@ -2728,14 +2828,7 @@ pub const Expect = struct { if (property_matchers) |_prop_matchers| { var prop_matchers = _prop_matchers; - var itr = PropertyMatcherIterator{ - .received_object = value, - .failed = false, - }; - - prop_matchers.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach); - - if (itr.failed) { + if (!value.deepMatch(prop_matchers, globalObject, true)) { // TODO: print diff with properties from propertyMatchers const signature = comptime getSignature("toMatchSnapshot", "<green>propertyMatchers<r>", false); const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++ @@ -3627,88 +3720,6 @@ pub const Expect = struct { return .zero; } - pub const PropertyMatcherIterator = struct { - received_object: JSValue, - failed: bool, - i: usize = 0, - - pub fn forEach( - globalObject: *JSGlobalObject, - ctx_ptr: ?*anyopaque, - key_: [*c]ZigString, - value: JSValue, - _: bool, - ) callconv(.C) void { - const key: ZigString = key_.?[0]; - if (key.eqlComptime("constructor")) return; - if (key.eqlComptime("call")) return; - - var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return); - defer ctx.i += 1; - var received_object: JSValue = ctx.received_object; - - if (received_object.get(globalObject, key.slice())) |received_value| { - if (JSC.Jest.ExpectAny.fromJS(value)) |_| { - var constructor_value = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse { - globalObject.throw("Internal consistency error: the expect.any(constructor value) was garbage collected but it should not have been!", .{}); - ctx.failed = true; - return; - }; - - if (received_value.isCell() and received_value.isInstanceOf(globalObject, constructor_value)) { - received_object.put(globalObject, &key, value); - return; - } - - // check primitives - // TODO: check the constructor for primitives by reading it from JSGlobalObject through a binding. - var constructor_name = ZigString.Empty; - constructor_value.getNameProperty(globalObject, &constructor_name); - if (received_value.isNumber() and constructor_name.eqlComptime("Number")) { - received_object.put(globalObject, &key, value); - return; - } - if (received_value.isBoolean() and constructor_name.eqlComptime("Boolean")) { - received_object.put(globalObject, &key, value); - return; - } - if (received_value.isString() and constructor_name.eqlComptime("String")) { - received_object.put(globalObject, &key, value); - return; - } - if (received_value.isBigInt() and constructor_name.eqlComptime("BigInt")) { - received_object.put(globalObject, &key, value); - return; - } - - ctx.failed = true; - return; - } - - if (value.isObject()) { - if (received_object.get(globalObject, key.slice())) |new_object| { - var itr = PropertyMatcherIterator{ - .received_object = new_object, - .failed = false, - }; - value.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach); - if (itr.failed) { - ctx.failed = true; - } - } else { - ctx.failed = true; - } - - return; - } - - if (value.isSameValue(received_value, globalObject)) return; - } - - ctx.failed = true; - } - }; - pub fn toBeInstanceOf(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { defer this.postMatch(globalObject); @@ -3945,6 +3956,78 @@ pub const Expect = struct { unreachable; } + pub fn toMatchObject(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalObject); + const thisValue = callFrame.this(); + const args = callFrame.arguments(1).slice(); + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toMatchObject() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + + const received_object = 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_object.isObject()) { + const matcher_error = "\n\n<b>Matcher error<r>: <red>received<r> value must be a non-null object\n"; + if (not) { + const fmt = comptime getSignature("toMatchObject", "<green>expected<r>", true) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const fmt = comptime getSignature("toMatchObject", "<green>expected<r>", false) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + if (args.len < 1 or !args[0].isObject()) { + const matcher_error = "\n\n<b>Matcher error<r>: <green>expected<r> value must be a non-null object\n"; + if (not) { + const fmt = comptime getSignature("toMatchObject", "", true) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + const fmt = comptime getSignature("toMatchObject", "", false) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const property_matchers = args[0]; + + var pass = received_object.deepMatch(property_matchers, globalObject, true); + + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + const diff_formatter = DiffFormatter{ + .received = received_object, + .expected = property_matchers, + .globalObject = globalObject, + .not = not, + }; + + if (not) { + const signature = comptime getSignature("toMatchObject", "<green>expected<r>", true); + const fmt = signature ++ "\n\n{any}\n"; + globalObject.throwPretty(fmt, .{diff_formatter}); + return .zero; + } + + const signature = comptime getSignature("toMatchObject", "<green>expected<r>", false); + const fmt = signature ++ "\n\n{any}\n"; + globalObject.throwPretty(fmt, .{diff_formatter}); + return .zero; + } + pub const toHaveBeenCalledWith = notImplementedJSCFn; pub const toHaveBeenLastCalledWith = notImplementedJSCFn; pub const toHaveBeenNthCalledWith = notImplementedJSCFn; @@ -3953,7 +4036,6 @@ pub const Expect = struct { pub const toHaveLastReturnedWith = notImplementedJSCFn; pub const toHaveNthReturnedWith = notImplementedJSCFn; pub const toContainEqual = notImplementedJSCFn; - pub const toMatchObject = notImplementedJSCFn; pub const toMatchInlineSnapshot = notImplementedJSCFn; pub const toThrowErrorMatchingSnapshot = notImplementedJSCFn; pub const toThrowErrorMatchingInlineSnapshot = notImplementedJSCFn; @@ -3980,14 +4062,23 @@ pub const Expect = struct { return ExpectAny.call(globalObject, callFrame); } + pub fn anything(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + return ExpectAnything.call(globalObject, callFrame); + } + + pub fn stringContaining(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + return ExpectStringContaining.call(globalObject, callFrame); + } + + pub fn stringMatching(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + return ExpectStringMatching.call(globalObject, callFrame); + } + pub const extend = notImplementedStaticFn; - pub const anything = notImplementedStaticFn; pub const arrayContaining = notImplementedStaticFn; pub const assertions = notImplementedStaticFn; pub const hasAssertions = notImplementedStaticFn; pub const objectContaining = notImplementedStaticFn; - pub const stringContaining = notImplementedStaticFn; - pub const stringMatching = notImplementedStaticFn; pub const addSnapshotSerializer = notImplementedStaticFn; pub fn notImplementedJSCFn(_: *Expect, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { |