aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/test/jest.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/test/jest.zig')
-rw-r--r--src/bun.js/test/jest.zig305
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 {