diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 62 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 6 | ||||
-rw-r--r-- | src/bun.js/test/expect.zig | 200 | ||||
-rw-r--r-- | src/bun.js/test/jest.classes.ts | 8 |
4 files changed, 276 insertions, 0 deletions
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 25aca16c4..c2780bd7f 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -2855,6 +2855,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toHaveReturnedWithCallback); extern "C" EncodedJSValue ExpectPrototype__toInclude(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toIncludeCallback); +extern "C" EncodedJSValue ExpectPrototype__toIncludeRepeated(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toIncludeRepeatedCallback); + extern "C" EncodedJSValue ExpectPrototype__toMatch(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchCallback); @@ -2867,6 +2870,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchObjectCallback); extern "C" EncodedJSValue ExpectPrototype__toMatchSnapshot(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchSnapshotCallback); +extern "C" EncodedJSValue ExpectPrototype__toSatisfy(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toSatisfyCallback); + extern "C" EncodedJSValue ExpectPrototype__toStartWith(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toStartWithCallback); @@ -2939,10 +2945,12 @@ static const HashTableValue JSExpectPrototypeTableValues[] = { { "toHaveReturnedTimes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedTimesCallback, 1 } }, { "toHaveReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedWithCallback, 1 } }, { "toInclude"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toIncludeCallback, 1 } }, + { "toIncludeRepeated"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toIncludeRepeatedCallback, 2 } }, { "toMatch"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchCallback, 1 } }, { "toMatchInlineSnapshot"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchInlineSnapshotCallback, 1 } }, { "toMatchObject"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchObjectCallback, 1 } }, { "toMatchSnapshot"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchSnapshotCallback, 1 } }, + { "toSatisfy"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toSatisfyCallback, 1 } }, { "toStartWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toStartWithCallback, 1 } }, { "toStrictEqual"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toStrictEqualCallback, 1 } }, { "toThrow"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toThrowCallback, 1 } }, @@ -4377,6 +4385,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toIncludeCallback, (JSGlobalObject * l return ExpectPrototype__toInclude(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toIncludeRepeatedCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSExpect* thisObject = jsDynamicCast<JSExpect*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return ExpectPrototype__toIncludeRepeated(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toMatchCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -4485,6 +4520,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toMatchSnapshotCallback, (JSGlobalObje return ExpectPrototype__toMatchSnapshot(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toSatisfyCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSExpect* thisObject = jsDynamicCast<JSExpect*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + +#ifdef BUN_DEBUG + /** View the file name of the JS file that called this function + * from a debugger */ + SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); + const char* fileName = sourceOrigin.string().utf8().data(); + static const char* lastFileName = nullptr; + if (lastFileName != fileName) { + lastFileName = fileName; + } +#endif + + return ExpectPrototype__toSatisfy(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toStartWithCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index e8cc701a3..9cf63a2e1 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -991,6 +991,8 @@ pub const JSExpect = struct { @compileLog("Expected Expect.toHaveReturnedWith to be a callback but received " ++ @typeName(@TypeOf(Expect.toHaveReturnedWith))); if (@TypeOf(Expect.toInclude) != CallbackType) @compileLog("Expected Expect.toInclude to be a callback but received " ++ @typeName(@TypeOf(Expect.toInclude))); + if (@TypeOf(Expect.toIncludeRepeated) != CallbackType) + @compileLog("Expected Expect.toIncludeRepeated to be a callback but received " ++ @typeName(@TypeOf(Expect.toIncludeRepeated))); if (@TypeOf(Expect.toMatch) != CallbackType) @compileLog("Expected Expect.toMatch to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatch))); if (@TypeOf(Expect.toMatchInlineSnapshot) != CallbackType) @@ -999,6 +1001,8 @@ pub const JSExpect = struct { @compileLog("Expected Expect.toMatchObject to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatchObject))); if (@TypeOf(Expect.toMatchSnapshot) != CallbackType) @compileLog("Expected Expect.toMatchSnapshot to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatchSnapshot))); + if (@TypeOf(Expect.toSatisfy) != CallbackType) + @compileLog("Expected Expect.toSatisfy to be a callback but received " ++ @typeName(@TypeOf(Expect.toSatisfy))); if (@TypeOf(Expect.toStartWith) != CallbackType) @compileLog("Expected Expect.toStartWith to be a callback but received " ++ @typeName(@TypeOf(Expect.toStartWith))); if (@TypeOf(Expect.toStrictEqual) != CallbackType) @@ -1111,10 +1115,12 @@ pub const JSExpect = struct { @export(Expect.toHaveReturnedTimes, .{ .name = "ExpectPrototype__toHaveReturnedTimes" }); @export(Expect.toHaveReturnedWith, .{ .name = "ExpectPrototype__toHaveReturnedWith" }); @export(Expect.toInclude, .{ .name = "ExpectPrototype__toInclude" }); + @export(Expect.toIncludeRepeated, .{ .name = "ExpectPrototype__toIncludeRepeated" }); @export(Expect.toMatch, .{ .name = "ExpectPrototype__toMatch" }); @export(Expect.toMatchInlineSnapshot, .{ .name = "ExpectPrototype__toMatchInlineSnapshot" }); @export(Expect.toMatchObject, .{ .name = "ExpectPrototype__toMatchObject" }); @export(Expect.toMatchSnapshot, .{ .name = "ExpectPrototype__toMatchSnapshot" }); + @export(Expect.toSatisfy, .{ .name = "ExpectPrototype__toSatisfy" }); @export(Expect.toStartWith, .{ .name = "ExpectPrototype__toStartWith" }); @export(Expect.toStrictEqual, .{ .name = "ExpectPrototype__toStrictEqual" }); @export(Expect.toThrow, .{ .name = "ExpectPrototype__toThrow" }); diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index c56818efc..de3f185d5 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -2812,6 +2812,206 @@ pub const Expect = struct { return .zero; } + pub fn toIncludeRepeated(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + const arguments_ = callFrame.arguments(2); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 2) { + globalThis.throwInvalidArguments("toIncludeRepeated() requires 2 arguments", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toIncludeRepeated() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const substring = arguments[0]; + substring.ensureStillAlive(); + + if (!substring.isString()) { + globalThis.throw("toIncludeRepeated() requires the first argument to be a string", .{}); + return .zero; + } + + const count = arguments[1]; + count.ensureStillAlive(); + + if (!count.isAnyInt()) { + globalThis.throw("toIncludeRepeated() requires the second argument to be a number", .{}); + return .zero; + } + + const countAsNum = count.toU32(); + + const expect_string = Expect.capturedValueGetCached(thisValue) orelse { + globalThis.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + + if (!expect_string.isString()) { + globalThis.throw("toIncludeRepeated() requires the expect(value) to be a string", .{}); + return .zero; + } + + const not = this.flags.not; + var pass = false; + + const _expectStringAsStr = expect_string.toSliceOrNull(globalThis) orelse return .zero; + const _subStringAsStr = substring.toSliceOrNull(globalThis) orelse return .zero; + + defer { + _expectStringAsStr.deinit(); + _subStringAsStr.deinit(); + } + + var expectStringAsStr = _expectStringAsStr.slice(); + var subStringAsStr = _subStringAsStr.slice(); + + if (subStringAsStr.len == 0) { + globalThis.throw("toIncludeRepeated() requires the first argument to be a non-empty string", .{}); + return .zero; + } + + if (countAsNum == 0) + pass = !strings.contains(expectStringAsStr, subStringAsStr) + else + pass = std.mem.containsAtLeast(u8, expectStringAsStr, countAsNum, subStringAsStr); + + if (not) pass = !pass; + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const expect_string_fmt = expect_string.toFmt(globalThis, &formatter); + const substring_fmt = substring.toFmt(globalThis, &formatter); + const times_fmt = count.toFmt(globalThis, &formatter); + + const received_line = "Received: <red>{any}<r>\n"; + + if (not) { + if (countAsNum == 0) { + const expected_line = "Expected to include: <green>{any}<r> \n"; + const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt }); + } else if (countAsNum == 1) { + const expected_line = "Expected not to include: <green>{any}<r> \n"; + const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt }); + } else { + const expected_line = "Expected not to include: <green>{any}<r> <green>{any}<r> times \n"; + const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ substring_fmt, times_fmt, expect_string_fmt }); + } + + return .zero; + } + + if (countAsNum == 0) { + const expected_line = "Expected to not include: <green>{any}<r>\n"; + const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt }); + } else if (countAsNum == 1) { + const expected_line = "Expected to include: <green>{any}<r>\n"; + const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ substring_fmt, expect_string_fmt }); + } else { + const expected_line = "Expected to include: <green>{any}<r> <green>{any}<r> times \n"; + const fmt = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ substring_fmt, times_fmt, expect_string_fmt }); + } + + return .zero; + } + + pub fn toSatisfy(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + const arguments_ = callFrame.arguments(1); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 1) { + globalThis.throwInvalidArguments("toSatisfy() requires 1 argument", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toSatisfy() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const predicate = arguments[0]; + predicate.ensureStillAlive(); + + if (!predicate.isCallable(globalThis.vm())) { + globalThis.throw("toSatisfy() argument must be a function", .{}); + return .zero; + } + + const value = Expect.capturedValueGetCached(thisValue) orelse { + globalThis.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + value.ensureStillAlive(); + + const result = predicate.call(globalThis, &.{value}); + + if (result.toError()) |err| { + var errors: [1]*anyopaque = undefined; + var _err = errors[0..errors.len]; + + _err[0] = err.asVoid(); + + const fmt = ZigString.init("toSatisfy() predicate threw an exception"); + globalThis.vm().throwError(globalThis, globalThis.createAggregateError(_err.ptr, _err.len, &fmt)); + return .zero; + } + + const not = this.flags.not; + const pass = (result.isBoolean() and result.toBoolean()) != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + + if (not) { + const signature = comptime getSignature("toSatisfy", "<green>expected<r>", true); + const fmt = signature ++ "\n\nExpected: not <green>{any}<r>\n"; + if (Output.enable_ansi_colors) { + globalThis.throw(Output.prettyFmt(fmt, true), .{predicate.toFmt(globalThis, &formatter)}); + return .zero; + } + globalThis.throw(Output.prettyFmt(fmt, false), .{predicate.toFmt(globalThis, &formatter)}); + return .zero; + } + + const signature = comptime getSignature("toSatisfy", "<green>expected<r>", false); + + const fmt = signature ++ "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>\n"; + + if (Output.enable_ansi_colors) { + globalThis.throw(Output.prettyFmt(fmt, true), .{ + predicate.toFmt(globalThis, &formatter), + value.toFmt(globalThis, &formatter), + }); + return .zero; + } + + globalThis.throw(Output.prettyFmt(fmt, false), .{ + predicate.toFmt(globalThis, &formatter), + value.toFmt(globalThis, &formatter), + }); + + return .zero; + } + pub fn toStartWith(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { defer this.postMatch(globalThis); diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index c337ab4ec..d40acbf07 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -353,6 +353,14 @@ export default [ fn: "toInclude", length: 1, }, + toIncludeRepeated: { + fn: "toIncludeRepeated", + length: 2, + }, + toSatisfy: { + fn: "toSatisfy", + length: 1, + }, toStartWith: { fn: "toStartWith", length: 1, |