diff options
author | 2023-06-01 00:19:33 -0700 | |
---|---|---|
committer | 2023-06-01 00:19:33 -0700 | |
commit | a4ccd4e0b4cc19f534bf639f30b7e4218400e1e8 (patch) | |
tree | 250f89bddd6e6c920645db2ad39bb7edf576edf0 /src | |
parent | cb0f76aa73f6b85667b57015a77ac39d9c78aa0b (diff) | |
parent | 689434e012a47b9be897f6d90d6aa211b13dfc19 (diff) | |
download | bun-jarred/port.tar.gz bun-jarred/port.tar.zst bun-jarred/port.zip |
Merge branch 'main' into jarred/portjarred/port
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/api/bun/socket.zig | 47 | ||||
-rw-r--r-- | src/bun.js/api/bun/subprocess.zig | 21 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 527 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 54 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 51 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 20 | ||||
-rw-r--r-- | src/bun.js/test/diff_format.zig | 293 | ||||
-rw-r--r-- | src/bun.js/test/jest.classes.ts | 68 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 1601 | ||||
-rw-r--r-- | src/bun.js/test/pretty_format.zig | 31 | ||||
-rw-r--r-- | src/cli.zig | 6 | ||||
-rw-r--r-- | src/cli/test_command.zig | 78 | ||||
-rw-r--r-- | src/js_parser.zig | 25 | ||||
-rw-r--r-- | src/output.zig | 8 | ||||
-rw-r--r-- | src/string_immutable.zig | 97 |
16 files changed, 2393 insertions, 536 deletions
diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 667df4eb7..be18cc672 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -93,6 +93,23 @@ const Handlers = struct { this.active_connections += 1; } + pub const Scope = struct { + handlers: *Handlers, + socket_context: *uws.SocketContext, + + pub fn exit(this: *Scope, ssl: bool) void { + this.handlers.markInactive(ssl, this.socket_context); + } + }; + + pub fn enter(this: *Handlers, context: *uws.SocketContext) Scope { + this.markActive(); + return .{ + .handlers = this, + .socket_context = context, + }; + } + // corker: Corker = .{}, pub fn resolvePromise(this: *Handlers, value: JSValue) void { @@ -1143,18 +1160,24 @@ fn NewSocket(comptime ssl: bool) type { return this.this_value; } - pub fn onEnd(this: *This, _: Socket) void { + pub fn onEnd(this: *This, socket: Socket) void { JSC.markBinding(@src()); log("onEnd", .{}); this.detached = true; defer this.markInactive(); const handlers = this.handlers; + this.poll_ref.unref(handlers.vm); const callback = handlers.onEnd; if (callback == .zero) return; + // the handlers must be kept alive for the duration of the function call + // that way if we need to call the error handler, we can + var scope = handlers.enter(socket.context()); + defer scope.exit(ssl); + const globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ @@ -1166,7 +1189,7 @@ fn NewSocket(comptime ssl: bool) type { } } - pub fn onHandshake(this: *This, _: Socket, success: i32, ssl_error: uws.us_bun_verify_error_t) void { + pub fn onHandshake(this: *This, socket: Socket, success: i32, ssl_error: uws.us_bun_verify_error_t) void { log("onHandshake({d})", .{success}); JSC.markBinding(@src()); @@ -1187,6 +1210,11 @@ fn NewSocket(comptime ssl: bool) type { is_open = true; } + // the handlers must be kept alive for the duration of the function call + // that way if we need to call the error handler, we can + var scope = handlers.enter(socket.context()); + defer scope.exit(ssl); + const globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); @@ -1224,7 +1252,7 @@ fn NewSocket(comptime ssl: bool) type { } } - pub fn onClose(this: *This, _: Socket, err: c_int, _: ?*anyopaque) void { + pub fn onClose(this: *This, socket: Socket, err: c_int, _: ?*anyopaque) void { JSC.markBinding(@src()); log("onClose", .{}); this.detached = true; @@ -1236,6 +1264,11 @@ fn NewSocket(comptime ssl: bool) type { const callback = handlers.onClose; if (callback == .zero) return; + // the handlers must be kept alive for the duration of the function call + // that way if we need to call the error handler, we can + var scope = handlers.enter(socket.context()); + defer scope.exit(ssl); + var globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ @@ -1248,7 +1281,7 @@ fn NewSocket(comptime ssl: bool) type { } } - pub fn onData(this: *This, _: Socket, data: []const u8) void { + pub fn onData(this: *This, socket: Socket, data: []const u8) void { JSC.markBinding(@src()); log("onData({d})", .{data.len}); if (this.detached) return; @@ -1260,6 +1293,12 @@ fn NewSocket(comptime ssl: bool) type { const globalObject = handlers.globalObject; const this_value = this.getThisValue(globalObject); const output_value = handlers.binary_type.toJS(data, globalObject); + + // the handlers must be kept alive for the duration of the function call + // that way if we need to call the error handler, we can + var scope = handlers.enter(socket.context()); + defer scope.exit(ssl); + // const encoding = handlers.encoding; const result = callback.callWithThis(globalObject, this_value, &[_]JSValue{ this_value, diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index 0fb5a98be..a996f863b 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -1114,7 +1114,14 @@ pub const Subprocess = struct { } while (cmds_array.next()) |value| { - argv.appendAssumeCapacity(value.getZigString(globalThis).toOwnedSliceZ(allocator) catch { + const arg = value.getZigString(globalThis); + + // if the string is empty, ignore it, don't add it to the argv + if (arg.len == 0) { + continue; + } + + argv.appendAssumeCapacity(arg.toOwnedSliceZ(allocator) catch { globalThis.throw("out of memory", .{}); return .zero; }); @@ -1128,11 +1135,15 @@ pub const Subprocess = struct { if (args != .zero and args.isObject()) { if (args.get(globalThis, "cwd")) |cwd_| { + // ignore definitely invalid cwd if (!cwd_.isEmptyOrUndefinedOrNull()) { - cwd = cwd_.getZigString(globalThis).toOwnedSliceZ(allocator) catch { - globalThis.throw("out of memory", .{}); - return .zero; - }; + const cwd_str = cwd_.getZigString(globalThis); + if (cwd_str.len > 0) { + cwd = cwd_str.toOwnedSliceZ(allocator) catch { + globalThis.throw("out of memory", .{}); + return .zero; + }; + } } } diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 5482c461f..86f0ab29d 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -2665,9 +2665,15 @@ JSC_DECLARE_CUSTOM_GETTER(ExpectPrototype__resolvesGetterWrap); extern "C" EncodedJSValue ExpectPrototype__toBe(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeBoolean(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeBooleanCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeCloseTo(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeCloseToCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeDate(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeDateCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeDefined(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeDefinedCallback); @@ -2677,9 +2683,18 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeEmptyCallback); extern "C" EncodedJSValue ExpectPrototype__toBeEven(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeEvenCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeFalse(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeFalseCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeFalsy(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeFalsyCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeFinite(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeFiniteCallback); + +extern "C" EncodedJSValue ExpectPrototype__toBeFunction(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeFunctionCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeGreaterThan(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeGreaterThanCallback); @@ -2689,6 +2704,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeGreaterThanOrEqualCallback); extern "C" EncodedJSValue ExpectPrototype__toBeInstanceOf(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeInstanceOfCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeInteger(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeIntegerCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeLessThan(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeLessThanCallback); @@ -2698,24 +2716,51 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeLessThanOrEqualCallback); extern "C" EncodedJSValue ExpectPrototype__toBeNaN(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeNaNCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeNegative(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeNegativeCallback); + +extern "C" EncodedJSValue ExpectPrototype__toBeNil(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeNilCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeNull(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeNullCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeNumber(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeNumberCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeOdd(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeOddCallback); +extern "C" EncodedJSValue ExpectPrototype__toBePositive(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBePositiveCallback); + +extern "C" EncodedJSValue ExpectPrototype__toBeString(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeStringCallback); + +extern "C" EncodedJSValue ExpectPrototype__toBeSymbol(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeSymbolCallback); + +extern "C" EncodedJSValue ExpectPrototype__toBeTrue(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeTrueCallback); + extern "C" EncodedJSValue ExpectPrototype__toBeTruthy(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeTruthyCallback); extern "C" EncodedJSValue ExpectPrototype__toBeUndefined(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeUndefinedCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeWithin(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeWithinCallback); + extern "C" EncodedJSValue ExpectPrototype__toContain(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toContainCallback); extern "C" EncodedJSValue ExpectPrototype__toContainEqual(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toContainEqualCallback); +extern "C" EncodedJSValue ExpectPrototype__toEndWith(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toEndWithCallback); + extern "C" EncodedJSValue ExpectPrototype__toEqual(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toEqualCallback); @@ -2749,6 +2794,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toHaveReturnedTimesCallback); extern "C" EncodedJSValue ExpectPrototype__toHaveReturnedWith(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); 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__toMatch(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toMatchCallback); @@ -2761,6 +2809,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__toStartWith(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toStartWithCallback); + extern "C" EncodedJSValue ExpectPrototype__toStrictEqual(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toStrictEqualCallback); @@ -2780,23 +2831,38 @@ static const HashTableValue JSExpectPrototypeTableValues[] = { { "rejects"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ExpectPrototype__rejectsGetterWrap, 0 } }, { "resolves"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ExpectPrototype__resolvesGetterWrap, 0 } }, { "toBe"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeCallback, 1 } }, + { "toBeBoolean"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeBooleanCallback, 0 } }, { "toBeCloseTo"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeCloseToCallback, 1 } }, + { "toBeDate"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeDateCallback, 0 } }, { "toBeDefined"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeDefinedCallback, 0 } }, { "toBeEmpty"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeEmptyCallback, 0 } }, { "toBeEven"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeEvenCallback, 0 } }, + { "toBeFalse"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeFalseCallback, 0 } }, { "toBeFalsy"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeFalsyCallback, 0 } }, + { "toBeFinite"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeFiniteCallback, 0 } }, + { "toBeFunction"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeFunctionCallback, 0 } }, { "toBeGreaterThan"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeGreaterThanCallback, 1 } }, { "toBeGreaterThanOrEqual"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeGreaterThanOrEqualCallback, 1 } }, { "toBeInstanceOf"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeInstanceOfCallback, 1 } }, + { "toBeInteger"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeIntegerCallback, 0 } }, { "toBeLessThan"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeLessThanCallback, 1 } }, { "toBeLessThanOrEqual"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeLessThanOrEqualCallback, 1 } }, { "toBeNaN"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeNaNCallback, 0 } }, + { "toBeNegative"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeNegativeCallback, 0 } }, + { "toBeNil"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeNilCallback, 0 } }, { "toBeNull"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeNullCallback, 0 } }, + { "toBeNumber"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeNumberCallback, 0 } }, { "toBeOdd"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeOddCallback, 0 } }, + { "toBePositive"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBePositiveCallback, 0 } }, + { "toBeString"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeStringCallback, 0 } }, + { "toBeSymbol"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeSymbolCallback, 0 } }, + { "toBeTrue"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeTrueCallback, 0 } }, { "toBeTruthy"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeTruthyCallback, 0 } }, { "toBeUndefined"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeUndefinedCallback, 0 } }, + { "toBeWithin"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeWithinCallback, 2 } }, { "toContain"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toContainCallback, 1 } }, { "toContainEqual"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toContainEqualCallback, 1 } }, + { "toEndWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toEndWithCallback, 1 } }, { "toEqual"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toEqualCallback, 1 } }, { "toHaveBeenCalledTimes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveBeenCalledTimesCallback, 1 } }, { "toHaveBeenCalledWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveBeenCalledWithCallback, 1 } }, @@ -2808,10 +2874,12 @@ static const HashTableValue JSExpectPrototypeTableValues[] = { { "toHaveProperty"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHavePropertyCallback, 2 } }, { "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 } }, { "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 } }, + { "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 } }, { "toThrowErrorMatchingInlineSnapshot"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toThrowErrorMatchingInlineSnapshotCallback, 1 } }, @@ -2895,6 +2963,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeCallback, (JSGlobalObject * lexica return ExpectPrototype__toBe(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeBooleanCallback, (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__toBeBoolean(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeCloseToCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -2922,6 +3017,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeCloseToCallback, (JSGlobalObject * return ExpectPrototype__toBeCloseTo(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeDateCallback, (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__toBeDate(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeDefinedCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3003,6 +3125,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeEvenCallback, (JSGlobalObject * le return ExpectPrototype__toBeEven(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeFalseCallback, (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__toBeFalse(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeFalsyCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3030,6 +3179,60 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeFalsyCallback, (JSGlobalObject * l return ExpectPrototype__toBeFalsy(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeFiniteCallback, (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__toBeFinite(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeFunctionCallback, (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__toBeFunction(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeGreaterThanCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3111,6 +3314,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeInstanceOfCallback, (JSGlobalObjec return ExpectPrototype__toBeInstanceOf(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeIntegerCallback, (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__toBeInteger(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeLessThanCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3192,6 +3422,60 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeNaNCallback, (JSGlobalObject * lex return ExpectPrototype__toBeNaN(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeNegativeCallback, (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__toBeNegative(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeNilCallback, (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__toBeNil(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeNullCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3219,6 +3503,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeNullCallback, (JSGlobalObject * le return ExpectPrototype__toBeNull(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeNumberCallback, (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__toBeNumber(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeOddCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3246,6 +3557,114 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeOddCallback, (JSGlobalObject * lex return ExpectPrototype__toBeOdd(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBePositiveCallback, (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__toBePositive(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeStringCallback, (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__toBeString(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeSymbolCallback, (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__toBeSymbol(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeTrueCallback, (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__toBeTrue(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeTruthyCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3300,6 +3719,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeUndefinedCallback, (JSGlobalObject return ExpectPrototype__toBeUndefined(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeWithinCallback, (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__toBeWithin(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toContainCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3354,6 +3800,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toContainEqualCallback, (JSGlobalObjec return ExpectPrototype__toContainEqual(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toEndWithCallback, (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__toEndWith(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toEqualCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3651,6 +4124,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toHaveReturnedWithCallback, (JSGlobalO return ExpectPrototype__toHaveReturnedWith(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toIncludeCallback, (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__toInclude(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toMatchCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -3759,6 +4259,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toMatchSnapshotCallback, (JSGlobalObje return ExpectPrototype__toMatchSnapshot(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toStartWithCallback, (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__toStartWith(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toStrictEqualCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 2b569282e..3c100d66c 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -160,6 +160,22 @@ pub const ZigString = extern struct { }; } + pub fn indexOfAny(this: ZigString, comptime chars: []const u8) ?strings.OptionalUsize { + if (this.is16Bit()) { + return strings.indexOfAny16(this.utf16SliceAligned(), chars); + } else { + return strings.indexOfAny(this.slice(), chars); + } + } + + pub fn charAt(this: ZigString, offset: usize) u8 { + if (this.is16Bit()) { + return @truncate(u8, this.utf16SliceAligned()[offset]); + } else { + return @truncate(u8, this.slice()[offset]); + } + } + pub fn eql(this: ZigString, other: ZigString) bool { if (this.len == 0 or other.len == 0) return this.len == other.len; @@ -225,14 +241,7 @@ pub const ZigString = extern struct { return this.slice()[0] == char; } - pub fn substring(this: ZigString, offset: usize, maxlen: usize) ZigString { - var len: usize = undefined; - if (maxlen == 0) { - len = this.len; - } else { - len = @max(this.len, maxlen); - } - + pub fn substringWithLen(this: ZigString, offset: usize, len: usize) ZigString { if (this.is16Bit()) { return ZigString.from16Slice(this.utf16SliceAligned()[@min(this.len, offset)..len]); } @@ -249,6 +258,17 @@ pub const ZigString = extern struct { return out; } + pub fn substring(this: ZigString, offset: usize, maxlen: usize) ZigString { + var len: usize = undefined; + if (maxlen == 0) { + len = this.len; + } else { + len = @max(this.len, maxlen); + } + + return this.substringWithLen(offset, len); + } + pub fn maxUTF8ByteLength(this: ZigString) usize { if (this.isUTF8()) return this.len; @@ -504,6 +524,20 @@ pub const ZigString = extern struct { return &Holder.value; } + pub const GithubActionFormatter = struct { + text: ZigString, + + pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + var bytes = this.text.toSlice(bun.default_allocator); + defer bytes.deinit(); + try strings.githubActionWriter(writer, bytes.slice()); + } + }; + + pub fn githubAction(this: ZigString) GithubActionFormatter { + return GithubActionFormatter{ .text = this }; + } + pub fn toAtomicValue(this: *const ZigString, globalThis: *JSC.JSGlobalObject) JSValue { return shim.cppFn("toAtomicValue", .{ this, globalThis }); } @@ -3406,6 +3440,10 @@ pub const JSValue = enum(JSValueReprInt) { return this.jsType() == .RegExpObject; } + pub fn isDate(this: JSValue) bool { + return this.jsType() == .JSDate; + } + pub fn asCheckLoaded(value: JSValue, comptime ZigType: type) ?*ZigType { if (!ZigType.Class.isLoaded() or value.isUndefinedOrNull()) return null; diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index b993701fc..cd63e4fe9 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -1932,7 +1932,7 @@ pub const ZigConsoleClient = struct { if (str.is16Bit()) { // streaming print - writer.print("{s}", .{str}); + writer.print("{}", .{str}); } else if (strings.isAllASCII(str.slice())) { // fast path writer.writeAll(str.slice()); diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index a30774aa1..d24c8c81f 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -878,40 +878,70 @@ pub const JSExpect = struct { @compileLog("Expected Expect.getResolves to be a getter with thisValue"); if (@TypeOf(Expect.toBe) != CallbackType) @compileLog("Expected Expect.toBe to be a callback but received " ++ @typeName(@TypeOf(Expect.toBe))); + if (@TypeOf(Expect.toBeBoolean) != CallbackType) + @compileLog("Expected Expect.toBeBoolean to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeBoolean))); if (@TypeOf(Expect.toBeCloseTo) != CallbackType) @compileLog("Expected Expect.toBeCloseTo to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeCloseTo))); + if (@TypeOf(Expect.toBeDate) != CallbackType) + @compileLog("Expected Expect.toBeDate to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeDate))); if (@TypeOf(Expect.toBeDefined) != CallbackType) @compileLog("Expected Expect.toBeDefined to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeDefined))); if (@TypeOf(Expect.toBeEmpty) != CallbackType) @compileLog("Expected Expect.toBeEmpty to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeEmpty))); if (@TypeOf(Expect.toBeEven) != CallbackType) @compileLog("Expected Expect.toBeEven to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeEven))); + if (@TypeOf(Expect.toBeFalse) != CallbackType) + @compileLog("Expected Expect.toBeFalse to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeFalse))); if (@TypeOf(Expect.toBeFalsy) != CallbackType) @compileLog("Expected Expect.toBeFalsy to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeFalsy))); + if (@TypeOf(Expect.toBeFinite) != CallbackType) + @compileLog("Expected Expect.toBeFinite to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeFinite))); + if (@TypeOf(Expect.toBeFunction) != CallbackType) + @compileLog("Expected Expect.toBeFunction to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeFunction))); if (@TypeOf(Expect.toBeGreaterThan) != CallbackType) @compileLog("Expected Expect.toBeGreaterThan to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeGreaterThan))); if (@TypeOf(Expect.toBeGreaterThanOrEqual) != CallbackType) @compileLog("Expected Expect.toBeGreaterThanOrEqual to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeGreaterThanOrEqual))); if (@TypeOf(Expect.toBeInstanceOf) != CallbackType) @compileLog("Expected Expect.toBeInstanceOf to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeInstanceOf))); + if (@TypeOf(Expect.toBeInteger) != CallbackType) + @compileLog("Expected Expect.toBeInteger to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeInteger))); if (@TypeOf(Expect.toBeLessThan) != CallbackType) @compileLog("Expected Expect.toBeLessThan to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeLessThan))); if (@TypeOf(Expect.toBeLessThanOrEqual) != CallbackType) @compileLog("Expected Expect.toBeLessThanOrEqual to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeLessThanOrEqual))); if (@TypeOf(Expect.toBeNaN) != CallbackType) @compileLog("Expected Expect.toBeNaN to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeNaN))); + if (@TypeOf(Expect.toBeNegative) != CallbackType) + @compileLog("Expected Expect.toBeNegative to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeNegative))); + if (@TypeOf(Expect.toBeNil) != CallbackType) + @compileLog("Expected Expect.toBeNil to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeNil))); if (@TypeOf(Expect.toBeNull) != CallbackType) @compileLog("Expected Expect.toBeNull to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeNull))); + if (@TypeOf(Expect.toBeNumber) != CallbackType) + @compileLog("Expected Expect.toBeNumber to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeNumber))); if (@TypeOf(Expect.toBeOdd) != CallbackType) @compileLog("Expected Expect.toBeOdd to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeOdd))); + if (@TypeOf(Expect.toBePositive) != CallbackType) + @compileLog("Expected Expect.toBePositive to be a callback but received " ++ @typeName(@TypeOf(Expect.toBePositive))); + if (@TypeOf(Expect.toBeString) != CallbackType) + @compileLog("Expected Expect.toBeString to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeString))); + if (@TypeOf(Expect.toBeSymbol) != CallbackType) + @compileLog("Expected Expect.toBeSymbol to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeSymbol))); + if (@TypeOf(Expect.toBeTrue) != CallbackType) + @compileLog("Expected Expect.toBeTrue to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeTrue))); if (@TypeOf(Expect.toBeTruthy) != CallbackType) @compileLog("Expected Expect.toBeTruthy to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeTruthy))); if (@TypeOf(Expect.toBeUndefined) != CallbackType) @compileLog("Expected Expect.toBeUndefined to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeUndefined))); + if (@TypeOf(Expect.toBeWithin) != CallbackType) + @compileLog("Expected Expect.toBeWithin to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeWithin))); if (@TypeOf(Expect.toContain) != CallbackType) @compileLog("Expected Expect.toContain to be a callback but received " ++ @typeName(@TypeOf(Expect.toContain))); if (@TypeOf(Expect.toContainEqual) != CallbackType) @compileLog("Expected Expect.toContainEqual to be a callback but received " ++ @typeName(@TypeOf(Expect.toContainEqual))); + if (@TypeOf(Expect.toEndWith) != CallbackType) + @compileLog("Expected Expect.toEndWith to be a callback but received " ++ @typeName(@TypeOf(Expect.toEndWith))); if (@TypeOf(Expect.toEqual) != CallbackType) @compileLog("Expected Expect.toEqual to be a callback but received " ++ @typeName(@TypeOf(Expect.toEqual))); if (@TypeOf(Expect.toHaveBeenCalledTimes) != CallbackType) @@ -934,6 +964,8 @@ pub const JSExpect = struct { @compileLog("Expected Expect.toHaveReturnedTimes to be a callback but received " ++ @typeName(@TypeOf(Expect.toHaveReturnedTimes))); if (@TypeOf(Expect.toHaveReturnedWith) != CallbackType) @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.toMatch) != CallbackType) @compileLog("Expected Expect.toMatch to be a callback but received " ++ @typeName(@TypeOf(Expect.toMatch))); if (@TypeOf(Expect.toMatchInlineSnapshot) != CallbackType) @@ -942,6 +974,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.toStartWith) != CallbackType) + @compileLog("Expected Expect.toStartWith to be a callback but received " ++ @typeName(@TypeOf(Expect.toStartWith))); if (@TypeOf(Expect.toStrictEqual) != CallbackType) @compileLog("Expected Expect.toStrictEqual to be a callback but received " ++ @typeName(@TypeOf(Expect.toStrictEqual))); if (@TypeOf(Expect.toThrow) != CallbackType) @@ -1002,23 +1036,38 @@ pub const JSExpect = struct { @export(Expect.stringContaining, .{ .name = "ExpectClass__stringContaining" }); @export(Expect.stringMatching, .{ .name = "ExpectClass__stringMatching" }); @export(Expect.toBe, .{ .name = "ExpectPrototype__toBe" }); + @export(Expect.toBeBoolean, .{ .name = "ExpectPrototype__toBeBoolean" }); @export(Expect.toBeCloseTo, .{ .name = "ExpectPrototype__toBeCloseTo" }); + @export(Expect.toBeDate, .{ .name = "ExpectPrototype__toBeDate" }); @export(Expect.toBeDefined, .{ .name = "ExpectPrototype__toBeDefined" }); @export(Expect.toBeEmpty, .{ .name = "ExpectPrototype__toBeEmpty" }); @export(Expect.toBeEven, .{ .name = "ExpectPrototype__toBeEven" }); + @export(Expect.toBeFalse, .{ .name = "ExpectPrototype__toBeFalse" }); @export(Expect.toBeFalsy, .{ .name = "ExpectPrototype__toBeFalsy" }); + @export(Expect.toBeFinite, .{ .name = "ExpectPrototype__toBeFinite" }); + @export(Expect.toBeFunction, .{ .name = "ExpectPrototype__toBeFunction" }); @export(Expect.toBeGreaterThan, .{ .name = "ExpectPrototype__toBeGreaterThan" }); @export(Expect.toBeGreaterThanOrEqual, .{ .name = "ExpectPrototype__toBeGreaterThanOrEqual" }); @export(Expect.toBeInstanceOf, .{ .name = "ExpectPrototype__toBeInstanceOf" }); + @export(Expect.toBeInteger, .{ .name = "ExpectPrototype__toBeInteger" }); @export(Expect.toBeLessThan, .{ .name = "ExpectPrototype__toBeLessThan" }); @export(Expect.toBeLessThanOrEqual, .{ .name = "ExpectPrototype__toBeLessThanOrEqual" }); @export(Expect.toBeNaN, .{ .name = "ExpectPrototype__toBeNaN" }); + @export(Expect.toBeNegative, .{ .name = "ExpectPrototype__toBeNegative" }); + @export(Expect.toBeNil, .{ .name = "ExpectPrototype__toBeNil" }); @export(Expect.toBeNull, .{ .name = "ExpectPrototype__toBeNull" }); + @export(Expect.toBeNumber, .{ .name = "ExpectPrototype__toBeNumber" }); @export(Expect.toBeOdd, .{ .name = "ExpectPrototype__toBeOdd" }); + @export(Expect.toBePositive, .{ .name = "ExpectPrototype__toBePositive" }); + @export(Expect.toBeString, .{ .name = "ExpectPrototype__toBeString" }); + @export(Expect.toBeSymbol, .{ .name = "ExpectPrototype__toBeSymbol" }); + @export(Expect.toBeTrue, .{ .name = "ExpectPrototype__toBeTrue" }); @export(Expect.toBeTruthy, .{ .name = "ExpectPrototype__toBeTruthy" }); @export(Expect.toBeUndefined, .{ .name = "ExpectPrototype__toBeUndefined" }); + @export(Expect.toBeWithin, .{ .name = "ExpectPrototype__toBeWithin" }); @export(Expect.toContain, .{ .name = "ExpectPrototype__toContain" }); @export(Expect.toContainEqual, .{ .name = "ExpectPrototype__toContainEqual" }); + @export(Expect.toEndWith, .{ .name = "ExpectPrototype__toEndWith" }); @export(Expect.toEqual, .{ .name = "ExpectPrototype__toEqual" }); @export(Expect.toHaveBeenCalledTimes, .{ .name = "ExpectPrototype__toHaveBeenCalledTimes" }); @export(Expect.toHaveBeenCalledWith, .{ .name = "ExpectPrototype__toHaveBeenCalledWith" }); @@ -1030,10 +1079,12 @@ pub const JSExpect = struct { @export(Expect.toHaveProperty, .{ .name = "ExpectPrototype__toHaveProperty" }); @export(Expect.toHaveReturnedTimes, .{ .name = "ExpectPrototype__toHaveReturnedTimes" }); @export(Expect.toHaveReturnedWith, .{ .name = "ExpectPrototype__toHaveReturnedWith" }); + @export(Expect.toInclude, .{ .name = "ExpectPrototype__toInclude" }); @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.toStartWith, .{ .name = "ExpectPrototype__toStartWith" }); @export(Expect.toStrictEqual, .{ .name = "ExpectPrototype__toStrictEqual" }); @export(Expect.toThrow, .{ .name = "ExpectPrototype__toThrow" }); @export(Expect.toThrowErrorMatchingInlineSnapshot, .{ .name = "ExpectPrototype__toThrowErrorMatchingInlineSnapshot" }); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 5c158a4fb..e09b609cb 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -442,6 +442,8 @@ pub const VirtualMachine = struct { onUnhandledRejectionCtx: ?*anyopaque = null, unhandled_error_counter: usize = 0, + on_exception: ?*const OnException = null, + modules: ModuleLoader.AsyncModule.Queue = .{}, aggressive_garbage_collection: GCLevel = GCLevel.none, @@ -449,6 +451,16 @@ pub const VirtualMachine = struct { pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void; + pub const OnException = fn (*ZigException) void; + + pub fn setOnException(this: *VirtualMachine, callback: *const OnException) void { + this.on_exception = callback; + } + + pub fn clearOnException(this: *VirtualMachine) void { + this.on_exception = null; + } + const VMHolder = struct { pub threadlocal var vm: ?*VirtualMachine = null; }; @@ -2068,6 +2080,9 @@ pub const VirtualMachine = struct { var exception = exception_holder.zigException(); this.remapZigException(exception, error_instance, exception_list); this.had_errors = true; + defer if (this.on_exception) |cb| { + cb(exception); + }; var line_numbers = exception.stack.source_lines_numbers[0..exception.stack.source_lines_len]; var max_line: i32 = -1; @@ -2093,6 +2108,7 @@ pub const VirtualMachine = struct { var name = exception.name; const message = exception.message; + var did_print_name = false; if (source_lines.next()) |source| brk: { if (source.text.len == 0) break :brk; @@ -2228,7 +2244,7 @@ pub const VirtualMachine = struct { } if (show.syscall) { - try writer.print(comptime Output.prettyFmt("syscall<d>: <r><cyan>\"{s}\"<r>\n", allow_ansi_color), .{exception.syscall}); + try writer.print(comptime Output.prettyFmt(" syscall<d>: <r><cyan>\"{s}\"<r>\n", allow_ansi_color), .{exception.syscall}); add_extra_line = true; } @@ -2236,7 +2252,7 @@ pub const VirtualMachine = struct { if (show.syscall) { try writer.writeAll(" "); } - try writer.print(comptime Output.prettyFmt("errno<d>: <r><yellow>{d}<r>\n", allow_ansi_color), .{exception.errno}); + try writer.print(comptime Output.prettyFmt(" errno<d>: <r><yellow>{d}<r>\n", allow_ansi_color), .{exception.errno}); add_extra_line = true; } diff --git a/src/bun.js/test/diff_format.zig b/src/bun.js/test/diff_format.zig new file mode 100644 index 000000000..4558a5f39 --- /dev/null +++ b/src/bun.js/test/diff_format.zig @@ -0,0 +1,293 @@ +const std = @import("std"); +const bun = @import("root").bun; +const MutableString = bun.MutableString; +const Output = bun.Output; +const default_allocator = bun.default_allocator; +const string = bun.string; +const JSC = bun.JSC; +const JSValue = JSC.JSValue; +const JSGlobalObject = JSC.JSGlobalObject; +const ZigConsoleClient = JSC.ZigConsoleClient; +const DiffMatchPatch = @import("../../deps/diffz/DiffMatchPatch.zig"); + +pub const DiffFormatter = struct { + received_string: ?string = null, + expected_string: ?string = null, + received: ?JSValue = null, + expected: ?JSValue = null, + globalObject: *JSGlobalObject, + not: bool = false, + + pub fn format(this: DiffFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + if (this.expected_string != null and this.received_string != null) { + const received = this.received_string.?; + const expected = this.expected_string.?; + + var dmp = DiffMatchPatch.default; + dmp.diff_timeout = 200; + var diffs = try dmp.diff(default_allocator, received, expected, false); + defer diffs.deinit(default_allocator); + + const equal_fmt = "<d>{s}<r>"; + const delete_fmt = "<red>{s}<r>"; + const insert_fmt = "<green>{s}<r>"; + + try writer.writeAll("Expected: "); + for (diffs.items) |df| { + switch (df.operation) { + .delete => continue, + .insert => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text}); + } + }, + .equal => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); + } + }, + } + } + + try writer.writeAll("\nReceived: "); + for (diffs.items) |df| { + switch (df.operation) { + .insert => continue, + .delete => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text}); + } + }, + .equal => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); + } + }, + } + } + return; + } + + if (this.received == null or this.expected == null) return; + + const received = this.received.?; + const expected = this.expected.?; + var received_buf = MutableString.init(default_allocator, 0) catch unreachable; + var expected_buf = MutableString.init(default_allocator, 0) catch unreachable; + defer { + received_buf.deinit(); + expected_buf.deinit(); + } + + { + var buffered_writer_ = MutableString.BufferedWriter{ .context = &received_buf }; + var buffered_writer = &buffered_writer_; + + var buf_writer = buffered_writer.writer(); + const Writer = @TypeOf(buf_writer); + + const fmt_options = ZigConsoleClient.FormatOptions{ + .enable_colors = false, + .add_newline = false, + .flush = false, + .ordered_properties = true, + .quote_strings = true, + }; + ZigConsoleClient.format( + .Debug, + this.globalObject, + @ptrCast([*]const JSValue, &received), + 1, + Writer, + Writer, + buf_writer, + fmt_options, + ); + buffered_writer.flush() catch unreachable; + + buffered_writer_.context = &expected_buf; + + ZigConsoleClient.format( + .Debug, + this.globalObject, + @ptrCast([*]const JSValue, &this.expected), + 1, + Writer, + Writer, + buf_writer, + fmt_options, + ); + buffered_writer.flush() catch unreachable; + } + + const received_slice = received_buf.toOwnedSliceLeaky(); + const expected_slice = expected_buf.toOwnedSliceLeaky(); + + if (this.not) { + const not_fmt = "Expected: not <green>{s}<r>"; + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(not_fmt, true), .{expected_slice}); + } else { + try writer.print(Output.prettyFmt(not_fmt, false), .{expected_slice}); + } + return; + } + + switch (received.determineDiffMethod(expected, this.globalObject)) { + .none => { + const fmt = "Expected: <green>{any}<r>\nReceived: <red>{any}<r>"; + var formatter = ZigConsoleClient.Formatter{ .globalThis = this.globalObject, .quote_strings = true }; + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(fmt, true), .{ + expected.toFmt(this.globalObject, &formatter), + received.toFmt(this.globalObject, &formatter), + }); + return; + } + + try writer.print(Output.prettyFmt(fmt, true), .{ + expected.toFmt(this.globalObject, &formatter), + received.toFmt(this.globalObject, &formatter), + }); + return; + }, + .character => { + var dmp = DiffMatchPatch.default; + dmp.diff_timeout = 200; + var diffs = try dmp.diff(default_allocator, received_slice, expected_slice, false); + defer diffs.deinit(default_allocator); + + const equal_fmt = "<d>{s}<r>"; + const delete_fmt = "<red>{s}<r>"; + const insert_fmt = "<green>{s}<r>"; + + try writer.writeAll("Expected: "); + for (diffs.items) |df| { + switch (df.operation) { + .delete => continue, + .insert => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text}); + } + }, + .equal => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); + } + }, + } + } + + try writer.writeAll("\nReceived: "); + for (diffs.items) |df| { + switch (df.operation) { + .insert => continue, + .delete => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text}); + } + }, + .equal => { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); + } else { + try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); + } + }, + } + } + return; + }, + .line => { + var dmp = DiffMatchPatch.default; + dmp.diff_timeout = 200; + var diffs = try dmp.diffLines(default_allocator, received_slice, expected_slice); + defer diffs.deinit(default_allocator); + + const equal_fmt = "<d> {s}<r>"; + const delete_fmt = "<red>+ {s}<r>"; + const insert_fmt = "<green>- {s}<r>"; + + var insert_count: usize = 0; + var delete_count: usize = 0; + + for (diffs.items) |df| { + var prev: usize = 0; + var curr: usize = 0; + switch (df.operation) { + .equal => { + while (curr < df.text.len) { + if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) { + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text[prev .. curr + 1]}); + } else { + try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text[prev .. curr + 1]}); + } + prev = curr + 1; + } + curr += 1; + } + }, + .insert => { + while (curr < df.text.len) { + if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) { + insert_count += 1; + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text[prev .. curr + 1]}); + } else { + try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text[prev .. curr + 1]}); + } + prev = curr + 1; + } + curr += 1; + } + }, + .delete => { + while (curr < df.text.len) { + if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) { + delete_count += 1; + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text[prev .. curr + 1]}); + } else { + try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text[prev .. curr + 1]}); + } + prev = curr + 1; + } + curr += 1; + } + }, + } + if (df.text[df.text.len - 1] != '\n') try writer.writeAll("\n"); + } + + if (Output.enable_ansi_colors) { + try writer.print(Output.prettyFmt("\n<green>- Expected - {d}<r>\n", true), .{insert_count}); + try writer.print(Output.prettyFmt("<red>+ Received + {d}<r>", true), .{delete_count}); + return; + } + try writer.print("\n- Expected - {d}\n", .{insert_count}); + try writer.print("+ Received + {d}", .{delete_count}); + return; + }, + .word => { + // not implemented + // https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs#word-mode + }, + } + return; + } +}; diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index bc2dbb1a1..8ed291ef5 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -234,6 +234,74 @@ export default [ fn: "toBeOdd", length: 0, }, + toBeNil: { + fn: "toBeNil", + length: 0, + }, + toBeBoolean: { + fn: "toBeBoolean", + length: 0, + }, + toBeTrue: { + fn: "toBeTrue", + length: 0, + }, + toBeFalse: { + fn: "toBeFalse", + length: 0, + }, + toBeNumber: { + fn: "toBeNumber", + length: 0, + }, + toBeInteger: { + fn: "toBeInteger", + length: 0, + }, + toBeFinite: { + fn: "toBeFinite", + length: 0, + }, + toBePositive: { + fn: "toBePositive", + length: 0, + }, + toBeNegative: { + fn: "toBeNegative", + length: 0, + }, + toBeWithin: { + fn: "toBeWithin", + length: 2, + }, + toBeSymbol: { + fn: "toBeSymbol", + length: 0, + }, + toBeFunction: { + fn: "toBeFunction", + length: 0, + }, + toBeDate: { + fn: "toBeDate", + length: 0, + }, + toBeString: { + fn: "toBeString", + length: 0, + }, + toInclude: { + fn: "toInclude", + length: 1, + }, + toStartWith: { + fn: "toStartWith", + length: 1, + }, + toEndWith: { + fn: "toEndWith", + length: 1, + }, }, }), ]; diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 58a6a3efe..a1260ecb1 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -10,7 +10,7 @@ const HTTPClient = @import("root").bun.HTTP; const NetworkThread = HTTPClient.NetworkThread; const Environment = @import("../../env.zig"); -const DiffMatchPatch = @import("../../deps/diffz/DiffMatchPatch.zig"); +const DiffFormatter = @import("./diff_format.zig").DiffFormatter; const JSC = @import("root").bun.JSC; const js = JSC.C; @@ -39,6 +39,7 @@ const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; const JSObject = JSC.JSObject; +const CallFrame = JSC.CallFrame; const VirtualMachine = JSC.VirtualMachine; const Task = @import("../javascript.zig").Task; @@ -46,297 +47,24 @@ const Task = @import("../javascript.zig").Task; const Fs = @import("../../fs.zig"); const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false; -pub const DiffFormatter = struct { - received_string: ?string = null, - expected_string: ?string = null, - received: ?JSValue = null, - expected: ?JSValue = null, - globalObject: *JSC.JSGlobalObject, - not: bool = false, - - pub fn format(this: DiffFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - if (this.expected_string != null and this.received_string != null) { - const received = this.received_string.?; - const expected = this.expected_string.?; - - var dmp = DiffMatchPatch.default; - dmp.diff_timeout = 200; - var diffs = try dmp.diff(default_allocator, received, expected, false); - defer diffs.deinit(default_allocator); - - const equal_fmt = "<d>{s}<r>"; - const delete_fmt = "<red>{s}<r>"; - const insert_fmt = "<green>{s}<r>"; - - try writer.writeAll("Expected: "); - for (diffs.items) |df| { - switch (df.operation) { - .delete => continue, - .insert => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text}); - } - }, - .equal => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); - } - }, - } - } - - try writer.writeAll("\nReceived: "); - for (diffs.items) |df| { - switch (df.operation) { - .insert => continue, - .delete => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text}); - } - }, - .equal => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); - } - }, - } - } - return; - } - - if (this.received == null or this.expected == null) return; - - const received = this.received.?; - const expected = this.expected.?; - var received_buf = MutableString.init(default_allocator, 0) catch unreachable; - var expected_buf = MutableString.init(default_allocator, 0) catch unreachable; - defer { - received_buf.deinit(); - expected_buf.deinit(); - } - - { - var buffered_writer_ = bun.MutableString.BufferedWriter{ .context = &received_buf }; - var buffered_writer = &buffered_writer_; - - var buf_writer = buffered_writer.writer(); - const Writer = @TypeOf(buf_writer); - - const fmt_options = JSC.ZigConsoleClient.FormatOptions{ - .enable_colors = false, - .add_newline = false, - .flush = false, - .ordered_properties = true, - .quote_strings = true, - }; - JSC.ZigConsoleClient.format( - .Debug, - this.globalObject, - @ptrCast([*]const JSValue, &received), - 1, - Writer, - Writer, - buf_writer, - fmt_options, - ); - buffered_writer.flush() catch unreachable; - - buffered_writer_.context = &expected_buf; - - JSC.ZigConsoleClient.format( - .Debug, - this.globalObject, - @ptrCast([*]const JSValue, &this.expected), - 1, - Writer, - Writer, - buf_writer, - fmt_options, - ); - buffered_writer.flush() catch unreachable; - } - - const received_slice = received_buf.toOwnedSliceLeaky(); - const expected_slice = expected_buf.toOwnedSliceLeaky(); - - if (this.not) { - const not_fmt = "Expected: not <green>{s}<r>"; - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(not_fmt, true), .{expected_slice}); - } else { - try writer.print(Output.prettyFmt(not_fmt, false), .{expected_slice}); - } - return; - } - - switch (received.determineDiffMethod(expected, this.globalObject)) { - .none => { - const fmt = "Expected: <green>{any}<r>\nReceived: <red>{any}<r>"; - var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = this.globalObject, .quote_strings = true }; - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(fmt, true), .{ - expected.toFmt(this.globalObject, &formatter), - received.toFmt(this.globalObject, &formatter), - }); - return; - } - - try writer.print(Output.prettyFmt(fmt, true), .{ - expected.toFmt(this.globalObject, &formatter), - received.toFmt(this.globalObject, &formatter), - }); - return; - }, - .character => { - var dmp = DiffMatchPatch.default; - dmp.diff_timeout = 200; - var diffs = try dmp.diff(default_allocator, received_slice, expected_slice, false); - defer diffs.deinit(default_allocator); - - const equal_fmt = "<d>{s}<r>"; - const delete_fmt = "<red>{s}<r>"; - const insert_fmt = "<green>{s}<r>"; - - try writer.writeAll("Expected: "); - for (diffs.items) |df| { - switch (df.operation) { - .delete => continue, - .insert => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text}); - } - }, - .equal => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); - } - }, - } - } - - try writer.writeAll("\nReceived: "); - for (diffs.items) |df| { - switch (df.operation) { - .insert => continue, - .delete => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text}); - } - }, - .equal => { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text}); - } else { - try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text}); - } - }, - } - } - return; - }, - .line => { - var dmp = DiffMatchPatch.default; - dmp.diff_timeout = 200; - var diffs = try dmp.diffLines(default_allocator, received_slice, expected_slice); - defer diffs.deinit(default_allocator); - - const equal_fmt = "<d> {s}<r>"; - const delete_fmt = "<red>+ {s}<r>"; - const insert_fmt = "<green>- {s}<r>"; - - var insert_count: usize = 0; - var delete_count: usize = 0; - - for (diffs.items) |df| { - var prev: usize = 0; - var curr: usize = 0; - switch (df.operation) { - .equal => { - while (curr < df.text.len) { - if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) { - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(equal_fmt, true), .{df.text[prev .. curr + 1]}); - } else { - try writer.print(Output.prettyFmt(equal_fmt, false), .{df.text[prev .. curr + 1]}); - } - prev = curr + 1; - } - curr += 1; - } - }, - .insert => { - while (curr < df.text.len) { - if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) { - insert_count += 1; - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(insert_fmt, true), .{df.text[prev .. curr + 1]}); - } else { - try writer.print(Output.prettyFmt(insert_fmt, false), .{df.text[prev .. curr + 1]}); - } - prev = curr + 1; - } - curr += 1; - } - }, - .delete => { - while (curr < df.text.len) { - if (curr == df.text.len - 1 or df.text[curr] == '\n' and curr != 0) { - delete_count += 1; - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt(delete_fmt, true), .{df.text[prev .. curr + 1]}); - } else { - try writer.print(Output.prettyFmt(delete_fmt, false), .{df.text[prev .. curr + 1]}); - } - prev = curr + 1; - } - curr += 1; - } - }, - } - if (df.text[df.text.len - 1] != '\n') try writer.writeAll("\n"); - } - - if (Output.enable_ansi_colors) { - try writer.print(Output.prettyFmt("\n<green>- Expected - {d}<r>\n", true), .{insert_count}); - try writer.print(Output.prettyFmt("<red>+ Received + {d}<r>", true), .{delete_count}); - return; - } - try writer.print("\n- Expected - {d}\n", .{insert_count}); - try writer.print("+ Received + {d}", .{delete_count}); - return; - }, - .word => { - // not implemented - // https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs#word-mode - }, - } - return; - } -}; - const ArrayIdentityContext = @import("../../identity_context.zig").ArrayIdentityContext; pub var test_elapsed_timer: ?*std.time.Timer = null; +pub const Tag = enum(u3) { + pass, + fail, + only, + skip, + todo, +}; + pub const TestRunner = struct { tests: TestRunner.Test.List = .{}, log: *logger.Log, files: File.List = .{}, index: File.Map = File.Map{}, only: bool = false, + run_todo: bool = false, last_file: u64 = 0, allocator: std.mem.Allocator, @@ -431,7 +159,6 @@ pub const TestRunner = struct { if (this.only) { return; } - this.only = true; var list = this.queue.readableSlice(0); @@ -461,6 +188,7 @@ pub const TestRunner = struct { this.tests.items(.status)[test_id] = .pass; this.callback.onTestPass(this.callback, test_id, file, label, expectations, elapsed_ns, parent); } + pub fn reportFailure(this: *TestRunner, test_id: Test.ID, file: string, label: string, expectations: u32, elapsed_ns: u64, parent: ?*DescribeScope) void { this.tests.items(.status)[test_id] = .fail; this.callback.onTestFail(this.callback, test_id, file, label, expectations, elapsed_ns, parent); @@ -881,8 +609,8 @@ pub const Jest = struct { ); test_fn.put( globalObject, - ZigString.static("todo"), - JSC.NewFunction(globalObject, ZigString.static("todo"), 2, TestScope.todo, false), + ZigString.static("only"), + JSC.NewFunction(globalObject, ZigString.static("only"), 2, TestScope.only, false), ); test_fn.put( globalObject, @@ -891,8 +619,18 @@ pub const Jest = struct { ); test_fn.put( globalObject, - ZigString.static("only"), - JSC.NewFunction(globalObject, ZigString.static("only"), 2, TestScope.only, false), + ZigString.static("todo"), + JSC.NewFunction(globalObject, ZigString.static("todo"), 2, TestScope.todo, false), + ); + test_fn.put( + globalObject, + ZigString.static("if"), + JSC.NewFunction(globalObject, ZigString.static("if"), 2, TestScope.callIf, false), + ); + test_fn.put( + globalObject, + ZigString.static("skipIf"), + JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, TestScope.skipIf, false), ); module.put( @@ -900,12 +638,32 @@ pub const Jest = struct { ZigString.static("it"), test_fn, ); - const describe = JSC.NewFunction(globalObject, ZigString.static("describe"), 2, DescribeScope.describe, false); + const describe = JSC.NewFunction(globalObject, ZigString.static("describe"), 2, DescribeScope.call, false); + describe.put( + globalObject, + ZigString.static("only"), + JSC.NewFunction(globalObject, ZigString.static("only"), 2, DescribeScope.only, false), + ); describe.put( globalObject, ZigString.static("skip"), JSC.NewFunction(globalObject, ZigString.static("skip"), 2, DescribeScope.skip, false), ); + describe.put( + globalObject, + ZigString.static("todo"), + JSC.NewFunction(globalObject, ZigString.static("todo"), 2, DescribeScope.todo, false), + ); + describe.put( + globalObject, + ZigString.static("if"), + JSC.NewFunction(globalObject, ZigString.static("if"), 2, DescribeScope.callIf, false), + ); + describe.put( + globalObject, + ZigString.static("skipIf"), + JSC.NewFunction(globalObject, ZigString.static("skipIf"), 2, DescribeScope.skipIf, false), + ); module.put( globalObject, @@ -1863,9 +1621,13 @@ pub const Expect = struct { var path_string = ZigString.Empty; expected_property_path.toZigString(&path_string, globalObject); - const received_property = value.getIfPropertyExistsFromPath(globalObject, expected_property_path); + var pass = !value.isUndefinedOrNull(); + var received_property: JSValue = .zero; - var pass = !received_property.isEmpty(); + if (pass) { + received_property = value.getIfPropertyExistsFromPath(globalObject, expected_property_path); + pass = !received_property.isEmpty(); + } if (pass and expected_property != null) { pass = received_property.deepEquals(expected_property.?, globalObject); @@ -3095,6 +2857,756 @@ pub const Expect = struct { return .zero; } + pub fn toBeNil(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeNil() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isUndefinedOrNull() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeNil", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeNil", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeBoolean(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeBoolean() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isBoolean() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeBoolean", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeBoolean", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeTrue(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeTrue() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = (value.isBoolean() and value.toBoolean()) != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeTrue", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeTrue", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeFalse(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeFalse() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = (value.isBoolean() and !value.toBoolean()) != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeFalse", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeFalse", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeNumber(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeNumber() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isNumber() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeNumber", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeNumber", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeInteger(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeInteger() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isAnyInt() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeInteger", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeInteger", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeFinite(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeFinite() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isNumber(); + if (pass) { + const num: f64 = value.asNumber(); + pass = std.math.isFinite(num) and !std.math.isNan(num); + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeFinite", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeFinite", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBePositive(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBePositive() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isNumber(); + if (pass) { + const num: f64 = value.asNumber(); + pass = @round(num) > 0 and !std.math.isInf(num) and !std.math.isNan(num); + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBePositive", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBePositive", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeNegative(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeNegative() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isNumber(); + if (pass) { + const num: f64 = value.asNumber(); + pass = @round(num) < 0 and !std.math.isInf(num) and !std.math.isNan(num); + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeNegative", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeNegative", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeWithin(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 < 1) { + globalThis.throwInvalidArguments("toBeWithin() requires 2 arguments", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeWithin() must be called in a test", .{}); + 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 startValue = arguments[0]; + startValue.ensureStillAlive(); + + if (!startValue.isNumber()) { + globalThis.throw("toBeWithin() requires the first argument to be a number", .{}); + return .zero; + } + + const endValue = arguments[1]; + endValue.ensureStillAlive(); + + if (!endValue.isNumber()) { + globalThis.throw("toBeWithin() requires the second argument to be a number", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isNumber(); + if (pass) { + const num = value.asNumber(); + pass = num >= startValue.asNumber() and num < endValue.asNumber(); + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const start_fmt = startValue.toFmt(globalThis, &formatter); + const end_fmt = endValue.toFmt(globalThis, &formatter); + const received_fmt = value.toFmt(globalThis, &formatter); + + if (not) { + const expected_line = "Expected: not between <green>{any}<r> <d>(inclusive)<r> and <green>{any}<r> <d>(exclusive)<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeWithin", "<green>start<r><d>, <r><green>end<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ start_fmt, end_fmt, received_fmt }); + return .zero; + } + + const expected_line = "Expected: between <green>{any}<r> <d>(inclusive)<r> and <green>{any}<r> <d>(exclusive)<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toBeWithin", "<green>start<r><d>, <r><green>end<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ start_fmt, end_fmt, received_fmt }); + return .zero; + } + + pub fn toBeSymbol(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeSymbol() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isSymbol() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeSymbol", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeSymbol", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeFunction(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeFunction() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isCallable(globalThis.vm()) != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeFunction", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeFunction", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeDate(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeDate() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isDate() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeDate", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeDate", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toBeString(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalThis); + + const thisValue = callFrame.this(); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toBeString() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + const pass = value.isString() != not; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const received = value.toFmt(globalThis, &formatter); + + if (not) { + const fmt = comptime getSignature("toBeString", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + const fmt = comptime getSignature("toBeString", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n"; + globalThis.throwPretty(fmt, .{received}); + return .zero; + } + + pub fn toInclude(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("toInclude() requires 1 argument", .{}); + return .zero; + } + + const expected = arguments[0]; + expected.ensureStillAlive(); + + if (!expected.isString()) { + globalThis.throw("toInclude() requires the first argument to be a string", .{}); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toInclude() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isString(); + if (pass) { + const value_string = value.toString(globalThis).toSlice(globalThis, default_allocator).slice(); + const expected_string = expected.toString(globalThis).toSlice(globalThis, default_allocator).slice(); + pass = strings.contains(value_string, expected_string) or expected_string.len == 0; + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const value_fmt = value.toFmt(globalThis, &formatter); + const expected_fmt = expected.toFmt(globalThis, &formatter); + + if (not) { + const expected_line = "Expected to not include: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toInclude", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected to include: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toInclude", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ expected_fmt, value_fmt }); + return .zero; + } + + pub fn toStartWith(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("toStartWith() requires 1 argument", .{}); + return .zero; + } + + const expected = arguments[0]; + expected.ensureStillAlive(); + + if (!expected.isString()) { + globalThis.throw("toStartWith() requires the first argument to be a string", .{}); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toStartWith() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isString(); + if (pass) { + const value_string = value.toString(globalThis).toSlice(globalThis, default_allocator).slice(); + const expected_string = expected.toString(globalThis).toSlice(globalThis, default_allocator).slice(); + pass = strings.startsWith(value_string, expected_string) or expected_string.len == 0; + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const value_fmt = value.toFmt(globalThis, &formatter); + const expected_fmt = expected.toFmt(globalThis, &formatter); + + if (not) { + const expected_line = "Expected to not start with: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toStartWith", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected to start with: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toStartWith", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ expected_fmt, value_fmt }); + return .zero; + } + + pub fn toEndWith(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("toEndWith() requires 1 argument", .{}); + return .zero; + } + + const expected = arguments[0]; + expected.ensureStillAlive(); + + if (!expected.isString()) { + globalThis.throw("toEndWith() requires the first argument to be a string", .{}); + 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(); + + if (this.scope.tests.items.len <= this.test_id) { + globalThis.throw("toEndWith() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + var pass = value.isString(); + if (pass) { + const value_string = value.toString(globalThis).toSlice(globalThis, default_allocator).slice(); + const expected_string = expected.toString(globalThis).toSlice(globalThis, default_allocator).slice(); + pass = strings.endsWith(value_string, expected_string) or expected_string.len == 0; + } + + const not = this.op.contains(.not); + if (not) pass = !pass; + + if (pass) return thisValue; + + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true }; + const value_fmt = value.toFmt(globalThis, &formatter); + const expected_fmt = expected.toFmt(globalThis, &formatter); + + if (not) { + const expected_line = "Expected to not end with: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toEndWith", "<green>expected<r>", true) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ expected_fmt, value_fmt }); + return .zero; + } + + const expected_line = "Expected to end with: <green>{any}<r>\n"; + const received_line = "Received: <red>{any}<r>\n"; + const fmt = comptime getSignature("toEndWith", "<green>expected<r>", false) ++ "\n\n" ++ expected_line ++ received_line; + globalThis.throwPretty(fmt, .{ expected_fmt, value_fmt }); + return .zero; + } + pub const PropertyMatcherIterator = struct { received_object: JSValue, failed: bool, @@ -3389,136 +3901,39 @@ pub const TestScope = struct { promise: ?*JSInternalPromise = null, ran: bool = false, task: ?*TestRunnerTask = null, - skipped: bool = false, - is_todo: bool = false, + tag: Tag = .pass, snapshot_count: usize = 0, timeout_millis: u32 = 0, + retry_count: u32 = 0, // retry, on fail + repeat_count: u32 = 0, // retry, on pass or fail pub const Counter = struct { expected: u32 = 0, actual: u32 = 0, }; - pub fn only( - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const thisValue = callframe.this(); - const args = callframe.arguments(3); - prepare(globalThis, args.ptr[0..args.len], .only); - return thisValue; + pub fn call(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "test()", true, .pass); } - pub fn skip( - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const thisValue = callframe.this(); - const args = callframe.arguments(3); - prepare(globalThis, args.ptr[0..args.len], .skip); - return thisValue; + pub fn only(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "test.only()", true, .only); } - pub fn call( - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const thisValue = callframe.this(); - const args = callframe.arguments(3); - prepare(globalThis, args.ptr[0..args.len], .call); - return thisValue; + pub fn skip(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "test.skip()", true, .skip); } - pub fn todo( - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const thisValue = callframe.this(); - const args = callframe.arguments(3); - prepare(globalThis, args.ptr[0..args.len], .todo); - return thisValue; + pub fn todo(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "test.todo()", true, .todo); } - inline fn prepare( - globalThis: *JSC.JSGlobalObject, - args: []const JSC.JSValue, - comptime tag: @Type(.EnumLiteral), - ) void { - var label: string = ""; - if (args.len == 0) { - return; - } - - var label_value = args[0]; - var function_value = if (args.len > 1) args[1] else JSC.JSValue.zero; - - if (label_value.isEmptyOrUndefinedOrNull() or !label_value.isString()) { - function_value = label_value; - label_value = .zero; - } - - if (label_value != .zero) { - const allocator = getAllocator(globalThis); - label = (label_value.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice(); - } - - if (tag == .todo and label_value == .zero) { - globalThis.throw("test.todo() requires a description", .{}); - return; - } - - const function = function_value; - if (function.isEmptyOrUndefinedOrNull() or !function.isCell() or !function.isCallable(globalThis.vm())) { - // a callback is not required for .todo - if (tag != .todo) { - globalThis.throw("test() expects a function", .{}); - return; - } - } - - if (tag == .only) { - Jest.runner.?.setOnly(); - } - - if (tag == .todo) { - if (function != .zero) - function.protect(); - DescribeScope.active.todo_counter += 1; - DescribeScope.active.tests.append(getAllocator(globalThis), TestScope{ - .label = label, - .parent = DescribeScope.active, - .is_todo = true, - .callback = function, - }) catch unreachable; - - return; - } - - if (tag == .skip or (tag != .only and Jest.runner.?.only)) { - DescribeScope.active.skipped_counter += 1; - DescribeScope.active.tests.append(getAllocator(globalThis), TestScope{ - .label = label, - .parent = DescribeScope.active, - .skipped = true, - .callback = .zero, - }) catch unreachable; - return; - } - - function.protect(); - - DescribeScope.active.tests.append(getAllocator(globalThis), TestScope{ - .label = label, - .callback = function, - .parent = DescribeScope.active, - .timeout_millis = if (args.len > 2) @intCast(u32, @max(args[2].coerce(i32, globalThis), 0)) else Jest.runner.?.default_timeout_ms, - }) catch unreachable; + pub fn callIf(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createIfScope(globalThis, callframe, "test.if()", "if", TestScope, false); + } - if (test_elapsed_timer == null) create_tiemr: { - var timer = bun.default_allocator.create(std.time.Timer) catch unreachable; - timer.* = std.time.Timer.start() catch break :create_tiemr; - test_elapsed_timer = timer; - } + pub fn skipIf(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createIfScope(globalThis, callframe, "test.skipIf()", "skipIf", TestScope, true); } pub fn onReject(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { @@ -3612,11 +4027,11 @@ pub const TestScope = struct { if (initial_value.isAnyError()) { if (!Jest.runner.?.did_pending_test_fail) { // test failed unless it's a todo - Jest.runner.?.did_pending_test_fail = !this.is_todo; + Jest.runner.?.did_pending_test_fail = this.tag != .todo; vm.runErrorHandler(initial_value, null); } - if (this.is_todo) { + if (this.tag == .todo) { return .{ .todo = {} }; } @@ -3639,11 +4054,11 @@ pub const TestScope = struct { .Rejected => { if (!Jest.runner.?.did_pending_test_fail) { // test failed unless it's a todo - Jest.runner.?.did_pending_test_fail = !this.is_todo; + Jest.runner.?.did_pending_test_fail = this.tag != .todo; vm.runErrorHandler(promise.result(vm.global.vm()), null); } - if (this.is_todo) { + if (this.tag == .todo) { return .{ .todo = {} }; } @@ -3713,12 +4128,12 @@ pub const DescribeScope = struct { current_test_id: TestRunner.Test.ID = 0, value: JSValue = .zero, done: bool = false, - skipped: bool = false, - skipped_counter: u32 = 0, - todo_counter: u32 = 0, + is_skip: bool = false, + skip_count: u32 = 0, + tag: Tag = .pass, pub fn isAllSkipped(this: *const DescribeScope) bool { - return this.skipped or @as(usize, this.skipped_counter) >= this.tests.items.len; + return this.is_skip or @as(usize, this.skip_count) >= this.tests.items.len; } pub fn push(new: *DescribeScope) void { @@ -3904,60 +4319,28 @@ pub const DescribeScope = struct { return this.execCallback(globalObject, hook); } - pub fn skip( - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(3); - var this: *DescribeScope = DescribeScope.module; - return runDescribe(this, globalThis, arguments.ptr[0..arguments.len], true); + pub fn call(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "describe()", false, .pass); } - pub fn describe( - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(3); - var this: *DescribeScope = DescribeScope.module; - return runDescribe(this, globalThis, arguments.ptr[0..arguments.len], false); + pub fn only(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "describe.only()", false, .only); } - fn runDescribe( - this: *DescribeScope, - globalThis: *JSC.JSGlobalObject, - arguments: []const JSC.JSValue, - skipped: bool, - ) JSC.JSValue { - if (arguments.len == 0 or arguments.len > 2) { - globalThis.throwNotEnoughArguments("describe", 2, arguments.len); - return .zero; - } - - var label = ZigString.init(""); - var args = arguments; - const allocator = getAllocator(globalThis); - - if (arguments[0].isString()) { - arguments[0].toZigString(&label, globalThis); - args = args[1..]; - } - - if (args.len == 0 or !args[0].isCallable(globalThis.vm())) { - globalThis.throwInvalidArgumentType("describe", "callback", "function"); - return .zero; - } + pub fn skip(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "describe.skip()", false, .skip); + } - var callback = args[0]; + pub fn todo(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createScope(globalThis, callframe, "describe.todo()", false, .todo); + } - var scope = allocator.create(DescribeScope) catch unreachable; - scope.* = .{ - .label = (label.toSlice(allocator).cloneIfNeeded(allocator) catch unreachable).slice(), - .parent = active, - .file_id = this.file_id, - .skipped = skipped or active.skipped, - }; + pub fn callIf(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createIfScope(globalThis, callframe, "describe.if()", "if", DescribeScope, false); + } - return scope.run(globalThis, callback); + pub fn skipIf(globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(.C) JSValue { + return createIfScope(globalThis, callframe, "describe.skipIf()", "skipIf", DescribeScope, true); } pub fn run(this: *DescribeScope, globalObject: *JSC.JSGlobalObject, callback: JSC.JSValue) JSC.JSValue { @@ -3970,6 +4353,11 @@ pub const DescribeScope = struct { this.parent = this.parent orelse active; active = this; + if (callback == .zero) { + this.runTests(globalObject); + return .undefined; + } + { JSC.markBinding(@src()); globalObject.clearTerminationException(); @@ -4004,7 +4392,10 @@ pub const DescribeScope = struct { const end = @truncate(TestRunner.Test.ID, tests.len); this.pending_tests = std.DynamicBitSetUnmanaged.initFull(allocator, end) catch unreachable; - if (end == 0) return; + if (end == 0) { + // TODO: print the describe label when there are no tests + return; + } // Step 2. Update the runner with the count of how many tests we have for this block this.test_id_start = Jest.runner.?.addTestCount(end); @@ -4151,19 +4542,25 @@ pub const TestRunnerTask = struct { var test_: TestScope = this.describe.tests.items[test_id]; describe.current_test_id = test_id; - if (!describe.skipped and test_.is_todo and test_.callback.isEmpty()) { - this.processTestResult(globalThis, .{ .todo = {} }, test_, test_id, describe); - this.deinit(); - return false; - } - - if (test_.skipped or describe.skipped) { - this.processTestResult(globalThis, .{ .skip = {} }, test_, test_id, describe); + if (test_.callback == .zero or (describe.is_skip and test_.tag != .only)) { + var tag = if (describe.is_skip) describe.tag else test_.tag; + switch (tag) { + .todo => { + this.processTestResult(globalThis, .{ .todo = {} }, test_, test_id, describe); + }, + .skip => { + this.processTestResult(globalThis, .{ .skip = {} }, test_, test_id, describe); + }, + else => {}, + } this.deinit(); return false; } jsc_vm.onUnhandledRejectionCtx = this; + if (Output.is_github_action) { + jsc_vm.setOnException(printGithubAnnotation); + } if (this.needs_before_each) { this.needs_before_each = false; @@ -4259,7 +4656,7 @@ pub const TestRunnerTask = struct { } fn processTestResult(this: *TestRunnerTask, globalThis: *JSC.JSGlobalObject, result: Result, test_: TestScope, test_id: u32, describe: *DescribeScope) void { - switch (result.forceTODO(test_.is_todo)) { + switch (result.forceTODO(test_.tag == .todo)) { .pass => |count| Jest.runner.?.reportPass( test_id, this.source_file_path, @@ -4311,6 +4708,7 @@ pub const TestRunnerTask = struct { vm.onUnhandledRejectionCtx = null; } } + vm.clearOnException(); this.ref.unref(vm); @@ -4344,3 +4742,268 @@ pub const Result = union(TestRunner.Test.Status) { return this; } }; + +inline fn createScope( + globalThis: *JSGlobalObject, + callframe: *CallFrame, + comptime signature: string, + comptime is_test: bool, + comptime tag: Tag, +) JSValue { + const this = callframe.this(); + const arguments = callframe.arguments(3); + const args = arguments.ptr[0..arguments.len]; + + if (args.len == 0) { + globalThis.throwPretty("{s} expects a description or function", .{signature}); + return .zero; + } + + var description = args[0]; + var function = if (args.len > 1) args[1] else .zero; + var options = if (args.len > 2) args[2] else .zero; + + if (description.isEmptyOrUndefinedOrNull() or !description.isString()) { + function = description; + description = .zero; + } + + if (function.isEmptyOrUndefinedOrNull() or !function.isCell() or !function.isCallable(globalThis.vm())) { + if (tag != .todo) { + globalThis.throwPretty("{s} expects a function", .{signature}); + return .zero; + } + } + + var timeout_ms: u32 = Jest.runner.?.default_timeout_ms; + if (options.isNumber()) { + timeout_ms = @intCast(u32, @max(args[2].coerce(i32, globalThis), 0)); + } else if (options.isObject()) { + if (options.get(globalThis, "timeout")) |timeout| { + if (!timeout.isNumber()) { + globalThis.throwPretty("{s} expects timeout to be a number", .{signature}); + return .zero; + } + timeout_ms = @intCast(u32, @max(timeout.coerce(i32, globalThis), 0)); + } + if (options.get(globalThis, "retry")) |retries| { + if (!retries.isNumber()) { + globalThis.throwPretty("{s} expects retry to be a number", .{signature}); + return .zero; + } + // TODO: retry_count = @intCast(u32, @max(retries.coerce(i32, globalThis), 0)); + } + if (options.get(globalThis, "repeats")) |repeats| { + if (!repeats.isNumber()) { + globalThis.throwPretty("{s} expects repeats to be a number", .{signature}); + return .zero; + } + // TODO: repeat_count = @intCast(u32, @max(repeats.coerce(i32, globalThis), 0)); + } + } else if (!options.isEmptyOrUndefinedOrNull()) { + globalThis.throwPretty("{s} expects options to be a number or object", .{signature}); + return .zero; + } + + const parent = DescribeScope.active; + const allocator = getAllocator(globalThis); + const label = if (description == .zero) + "" + else + (description.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch unreachable).slice(); + + if (tag == .only) { + Jest.runner.?.setOnly(); + } else if (is_test and Jest.runner.?.only and parent.tag != .only) { + return .zero; + } + + const is_skip = tag == .skip or + (tag == .todo and (function == .zero or !Jest.runner.?.run_todo)) or + (tag != .only and Jest.runner.?.only and parent.tag != .only); + + if (is_test) { + if (is_skip) { + parent.skip_count += 1; + function.unprotect(); + } else { + function.protect(); + } + + parent.tests.append(allocator, TestScope{ + .label = label, + .parent = parent, + .tag = tag, + .callback = if (is_skip) .zero else function, + .timeout_millis = timeout_ms, + }) catch unreachable; + + if (test_elapsed_timer == null) create_timer: { + var timer = allocator.create(std.time.Timer) catch unreachable; + timer.* = std.time.Timer.start() catch break :create_timer; + test_elapsed_timer = timer; + } + } else { + var scope = allocator.create(DescribeScope) catch unreachable; + scope.* = .{ + .label = label, + .parent = parent, + .file_id = parent.file_id, + .tag = if (parent.is_skip) parent.tag else tag, + .is_skip = is_skip or parent.is_skip, + }; + + return scope.run(globalThis, function); + } + + return this; +} + +inline fn createIfScope( + globalThis: *JSGlobalObject, + callframe: *CallFrame, + comptime property: string, + comptime signature: string, + comptime Scope: type, + comptime is_skip: bool, +) JSValue { + const arguments = callframe.arguments(1); + const args = arguments.ptr[0..arguments.len]; + + if (args.len == 0) { + globalThis.throwPretty("{s} expects a condition", .{signature}); + return .zero; + } + + const name = ZigString.static(property); + const value = args[0].toBooleanSlow(globalThis); + const skip = if (is_skip) Scope.skip else Scope.call; + const call = if (is_skip) Scope.call else Scope.skip; + + if (value) { + return JSC.NewFunction(globalThis, name, 2, skip, false); + } + + return JSC.NewFunction(globalThis, name, 2, call, false); +} + +// In Github Actions, emit an annotation that renders the error and location. +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message +pub fn printGithubAnnotation(exception: *JSC.ZigException) void { + const name = exception.name; + const message = exception.message; + const frames = exception.stack.frames(); + const top_frame = if (frames.len > 0) frames[0] else null; + const dir = bun.getenvZ("GITHUB_WORKSPACE") orelse bun.fs.FileSystem.instance.top_level_dir; + const allocator = bun.default_allocator; + + var has_location = false; + + if (top_frame) |frame| { + if (!frame.position.isInvalid()) { + const source_url = frame.source_url.toSlice(allocator); + defer source_url.deinit(); + const file = bun.path.relative(dir, source_url.slice()); + Output.printError("\n::error file={s},line={d},col={d},title=", .{ + file, + frame.position.line_start + 1, + frame.position.column_start, + }); + has_location = true; + } + } + + if (!has_location) { + Output.printError("\n::error title=", .{}); + } + + if (name.len == 0 or name.eqlComptime("Error")) { + Output.printError("error", .{}); + } else { + Output.printError("{s}", .{name.githubAction()}); + } + + if (message.len > 0) { + const message_slice = message.toSlice(allocator); + defer message_slice.deinit(); + const msg = message_slice.slice(); + + var cursor: u32 = 0; + while (strings.indexOfNewlineOrNonASCIIOrANSI(msg, cursor)) |i| { + cursor = i + 1; + if (msg[i] == '\n') { + const first_line = ZigString.init(msg[0..i]); + Output.printError(": {s}::", .{first_line.githubAction()}); + break; + } + } else { + Output.printError(": {s}::", .{message.githubAction()}); + } + + while (strings.indexOfNewlineOrNonASCIIOrANSI(msg, cursor)) |i| { + cursor = i + 1; + if (msg[i] == '\n') { + break; + } + } + + if (cursor > 0) { + const body = ZigString.init(msg[cursor..]); + Output.printError("{s}", .{body.githubAction()}); + } + } else { + Output.printError("::", .{}); + } + + // TODO: cleanup and refactor to use printStackTrace() + if (top_frame) |_| { + const vm = VirtualMachine.get(); + const origin = if (vm.is_from_devserver) &vm.origin else null; + + var i: i16 = 0; + while (i < frames.len) : (i += 1) { + const frame = frames[@intCast(usize, i)]; + const source_url = frame.source_url.toSlice(allocator); + defer source_url.deinit(); + const file = bun.path.relative(dir, source_url.slice()); + const func = frame.function_name.toSlice(allocator); + + if (file.len == 0 and func.len == 0) continue; + + const has_name = std.fmt.count("{any}", .{frame.nameFormatter( + false, + )}) > 0; + + // %0A = escaped newline + if (has_name) { + Output.printError( + "%0A at {any} ({any})", + .{ + frame.nameFormatter(false), + frame.sourceURLFormatter( + file, + origin, + false, + false, + ), + }, + ); + } else { + Output.printError( + "%0A at {any}", + .{ + frame.sourceURLFormatter( + file, + origin, + false, + false, + ), + }, + ); + } + } + } + + Output.printError("\n", .{}); + Output.flush(); +} diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 47a267b7f..0431b2e10 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -983,38 +983,35 @@ pub const JestPrettyFormat = struct { defer if (comptime enable_ansi_colors) writer.writeAll(Output.prettyFmt("<r>", true)); - if (str.is16Bit()) { - this.printAs(.JSON, Writer, writer_, value, .StringObject, enable_ansi_colors); - return; - } - var has_newline = false; - if (strings.indexOfAny(str.slice(), "\n\r")) |_| { + + if (str.indexOfAny("\n\r")) |_| { has_newline = true; writer.writeAll("\n"); } writer.writeAll("\""); - var remaining = str.slice(); - while (strings.indexOfAny(remaining, "\\\r")) |i| { - switch (remaining[i]) { + var remaining = str; + while (remaining.indexOfAny("\\\r")) |i| { + switch (remaining.charAt(i)) { '\\' => { - writer.print("{s}\\", .{remaining[0 .. i + 1]}); - remaining = remaining[i + 1 ..]; + writer.print("{}\\", .{remaining.substringWithLen(0, i)}); + remaining = remaining.substring(i + 1, 0); }, '\r' => { - if (i + 1 < remaining.len and remaining[i + 1] == '\n') { - writer.print("{s}", .{remaining[0..i]}); + if (i + 1 < remaining.len and remaining.charAt(i + 1) == '\n') { + writer.print("{}", .{remaining.substringWithLen(0, i)}); } else { - writer.print("{s}\n", .{remaining[0..i]}); + writer.print("{}\n", .{remaining.substringWithLen(0, i)}); } - remaining = remaining[i + 1 ..]; + + remaining = remaining.substring(i + 1, 0); }, else => unreachable, } } - writer.writeAll(remaining); + writer.writeString(remaining); writer.writeAll("\""); if (has_newline) writer.writeAll("\n"); return; @@ -1026,7 +1023,7 @@ pub const JestPrettyFormat = struct { if (str.is16Bit()) { // streaming print - writer.print("{s}", .{str}); + writer.print("{}", .{str}); } else if (strings.isAllASCII(str.slice())) { // fast path writer.writeAll(str.slice()); diff --git a/src/cli.zig b/src/cli.zig index dc1ae0cdc..0d4c32cdf 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -215,6 +215,8 @@ pub const Arguments = struct { clap.parseParam("--timeout <NUMBER> Set the per-test timeout in milliseconds, default is 5000.") catch unreachable, clap.parseParam("--update-snapshots Update snapshot files") catch unreachable, clap.parseParam("--rerun-each <NUMBER> Re-run each test file <NUMBER> times, helps catch certain bugs") catch unreachable, + clap.parseParam("--only Only run tests that are marked with \"test.only()\"") catch unreachable, + clap.parseParam("--todo Include tests that are marked with \"test.todo()\"") catch unreachable, }; const build_params_public = public_params ++ build_only_params; @@ -390,6 +392,8 @@ pub const Arguments = struct { }; } } + ctx.test_options.run_todo = args.flag("--todo"); + ctx.test_options.only = args.flag("--only"); } ctx.args.absolute_working_dir = cwd; @@ -923,6 +927,8 @@ pub const Command = struct { default_timeout_ms: u32 = 5 * std.time.ms_per_s, update_snapshots: bool = false, repeat_count: u32 = 0, + run_todo: bool = false, + only: bool = false, }; pub const Context = struct { diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index 0e337ebf0..b7712c0de 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -48,12 +48,21 @@ const uws = @import("root").bun.uws; fn fmtStatusTextLine(comptime status: @Type(.EnumLiteral), comptime emoji: bool) []const u8 { comptime { - return switch (status) { - .pass => Output.prettyFmt("<r><green>✓<r>", emoji), - .fail => Output.prettyFmt("<r><red>✗<r>", emoji), - .skip => Output.prettyFmt("<r><yellow>-<d>", emoji), - .todo => Output.prettyFmt("<r><magenta>✎<r>", emoji), - else => @compileError("Invalid status " ++ @tagName(status)), + return switch (emoji) { + true => switch (status) { + .pass => Output.prettyFmt("<r><green>✓<r>", true), + .fail => Output.prettyFmt("<r><red>✗<r>", true), + .skip => Output.prettyFmt("<r><yellow>»<d>", true), + .todo => Output.prettyFmt("<r><magenta>✎<r>", true), + else => @compileError("Invalid status " ++ @tagName(status)), + }, + else => switch (status) { + .pass => Output.prettyFmt("<r><green>(pass)<r>", true), + .fail => Output.prettyFmt("<r><red>(fail)<r>", true), + .skip => Output.prettyFmt("<r><yellow>(skip)<d>", true), + .todo => Output.prettyFmt("<r><magenta>(todo)<r>", true), + else => @compileError("Invalid status " ++ @tagName(status)), + }, }; } } @@ -94,13 +103,9 @@ pub const CommandLineReporter = struct { break :brk map; }; - pub fn handleUpdateCount(cb: *TestRunner.Callback, _: u32, _: u32) void { - _ = cb; - } + pub fn handleUpdateCount(_: *TestRunner.Callback, _: u32, _: u32) void {} - pub fn handleTestStart(_: *TestRunner.Callback, _: Test.ID) void { - // var this: *CommandLineReporter = @fieldParentPtr(CommandLineReporter, "callback", cb); - } + pub fn handleTestStart(_: *TestRunner.Callback, _: Test.ID) void {} fn printTestLine(label: string, elapsed_ns: u64, parent: ?*jest.DescribeScope, comptime skip: bool, writer: anytype) void { var scopes_stack = std.BoundedArray(*jest.DescribeScope, 64).init(0) catch unreachable; @@ -329,6 +334,16 @@ const Scanner = struct { if (this.filter_names.len == 0) return true; for (this.filter_names) |filter_name| { + if (strings.startsWith(name, filter_name)) return true; + } + + return false; + } + + pub fn doesPathMatchFilter(this: *Scanner, name: string) bool { + if (this.filter_names.len == 0) return true; + + for (this.filter_names) |filter_name| { if (strings.contains(name, filter_name)) return true; } @@ -336,7 +351,7 @@ const Scanner = struct { } pub fn isTestFile(this: *Scanner, name: string) bool { - return this.couldBeTestFile(name) and this.doesAbsolutePathMatchFilter(name); + return this.couldBeTestFile(name) and this.doesPathMatchFilter(name); } pub fn next(this: *Scanner, entry: *FileSystem.Entry, fd: bun.StoredFileDescriptorType) void { @@ -370,7 +385,10 @@ const Scanner = struct { var parts = &[_]string{ entry.dir, entry.base() }; const path = this.fs.absBuf(parts, &this.open_dir_buf); - if (!this.doesAbsolutePathMatchFilter(path)) return; + if (!this.doesAbsolutePathMatchFilter(path)) { + const rel_path = bun.path.relative(this.fs.top_level_dir, path); + if (!this.doesPathMatchFilter(rel_path)) return; + } entry.abs_path = bun.PathString.init(this.fs.filename_store.append(@TypeOf(path), path) catch unreachable); this.results.append(entry.abs_path) catch unreachable; @@ -385,6 +403,9 @@ pub const TestCommand = struct { pub fn exec(ctx: Command.Context) !void { if (comptime is_bindgen) unreachable; + + Output.is_github_action = Output.isGithubAction(); + // print the version so you know its doing stuff if it takes a sec if (strings.eqlComptime(ctx.positionals[0], old_name)) { Output.prettyErrorln("<r><b>bun wiptest <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>", .{}); @@ -415,6 +436,8 @@ pub const TestCommand = struct { .log = ctx.log, .callback = undefined, .default_timeout_ms = ctx.test_options.default_timeout_ms, + .run_todo = ctx.test_options.run_todo, + .only = ctx.test_options.only, .snapshots = Snapshots{ .allocator = ctx.allocator, .update_snapshots = ctx.test_options.update_snapshots, @@ -717,14 +740,26 @@ pub const TestCommand = struct { var resolution = try vm.bundler.resolveEntryPoint(file_name); vm.clearEntryPoint(); - Output.prettyErrorln("<r>\n{s}:\n", .{resolution.path_pair.primary.name.filename}); - Output.flush(); + const file_path = resolution.path_pair.primary.text; + const file_title = bun.path.relative(FileSystem.instance.top_level_dir, file_path); + + // In Github Actions, append a special prefix that will group + // subsequent log lines into a collapsable group. + // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines + const file_prefix = if (Output.is_github_action) "::group::" else ""; - vm.main_hash = @truncate(u32, bun.hash(resolution.path_pair.primary.text)); + vm.main_hash = @truncate(u32, bun.hash(file_path)); var repeat_count = reporter.repeat_count; var repeat_index: u32 = 0; while (repeat_index < repeat_count) : (repeat_index += 1) { - var promise = try vm.loadEntryPoint(resolution.path_pair.primary.text); + if (repeat_count > 1) { + Output.prettyErrorln("<r>\n{s}{s}: <d>(run #{d})<r>\n", .{ file_prefix, file_title, repeat_index + 1 }); + } else { + Output.prettyErrorln("<r>\n{s}{s}:\n", .{ file_prefix, file_title }); + } + Output.flush(); + + var promise = try vm.loadEntryPoint(file_path); switch (promise.status(vm.global.vm())) { .Rejected => { @@ -789,9 +824,12 @@ pub const TestCommand = struct { vm.global.handleRejectedPromises(); if (repeat_index > 0) { vm.clearEntryPoint(); - var entry = JSC.ZigString.init(resolution.path_pair.primary.text); + var entry = JSC.ZigString.init(file_path); vm.global.deleteModuleRegistryEntry(&entry); - Output.prettyErrorln("<r>{s} <d>[RUN {d:0>4}]:<r>\n", .{ resolution.path_pair.primary.name.filename, repeat_index + 1 }); + } + + if (Output.is_github_action) { + Output.prettyErrorln("<r>\n::endgroup::\n", .{}); Output.flush(); } } diff --git a/src/js_parser.zig b/src/js_parser.zig index a9cd4379c..afec299d9 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -16156,6 +16156,7 @@ fn NewParser_( .{ .is_call_target = is_call_target, .assign_target = in.assign_target, + .is_delete_target = is_delete_target, // .is_template_tag = p.template_tag != null, }, )) |_expr| { @@ -17438,14 +17439,6 @@ fn NewParser_( p.recordUsage(p.require_ref); return p.newExpr(E.Identifier{ .ref = p.require_ref }, name_loc); } else if (!p.commonjs_named_exports_deoptimized and strings.eqlComptime(name, "exports")) { - // Deoptimizations: - // delete module.exports - // module.exports(); - - if (identifier_opts.is_call_target or identifier_opts.is_delete_target or identifier_opts.assign_target == .update) { - p.deoptimizeCommonJSNamedExports(); - return null; - } // Detect if we are doing // @@ -17453,7 +17446,13 @@ fn NewParser_( // foo: "bar" // } // - if (identifier_opts.assign_target == .replace and + // Note that it cannot be any of these: + // + // module.exports += { }; + // delete module.exports = {}; + // module.exports() + if (!(identifier_opts.is_call_target or identifier_opts.is_delete_target) and + identifier_opts.assign_target == .replace and p.stmt_expr_value == .e_binary and p.stmt_expr_value.e_binary.op == .bin_assign) { @@ -17596,6 +17595,14 @@ fn NewParser_( return p.newExpr(E.Missing{}, name_loc); } + // Deoptimizations: + // delete module.exports + // module.exports(); + if (identifier_opts.is_call_target or identifier_opts.is_delete_target or identifier_opts.assign_target != .none) { + p.deoptimizeCommonJSNamedExports(); + return null; + } + // rewrite `module.exports` to `exports` return p.newExpr(E.Identifier{ .ref = p.exports_ref }, name_loc); } else if (p.options.bundle and strings.eqlComptime(name, "id") and identifier_opts.assign_target == .none) { diff --git a/src/output.zig b/src/output.zig index 4f47d2496..a37a58abf 100644 --- a/src/output.zig +++ b/src/output.zig @@ -168,6 +168,7 @@ pub var enable_ansi_colors = Environment.isNative; pub var enable_ansi_colors_stderr = Environment.isNative; pub var enable_ansi_colors_stdout = Environment.isNative; pub var enable_buffering = Environment.isNative; +pub var is_github_action = false; pub var stderr_descriptor_type = OutputStreamDescriptor.unknown; pub var stdout_descriptor_type = OutputStreamDescriptor.unknown; @@ -176,6 +177,13 @@ pub inline fn isEmojiEnabled() bool { return enable_ansi_colors and !Environment.isWindows; } +pub fn isGithubAction() bool { + if (bun.getenvZ("GITHUB_ACTIONS")) |value| { + return strings.eqlComptime(value, "true"); + } + return false; +} + var _source_for_test: if (Environment.isTest) Output.Source else void = undefined; var _source_for_test_set = false; pub fn initTest() void { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index f7e7a3657..25d4fb01a 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -38,7 +38,7 @@ pub fn toUTF16Literal(comptime str: []const u8) []const u16 { }; } -const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1); +pub const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1); pub fn indexOfAny(self: string, comptime str: anytype) ?OptionalUsize { inline for (str) |a| { if (indexOfChar(self, a)) |i| { @@ -689,6 +689,63 @@ pub fn endsWithAny(self: string, str: string) bool { return false; } +// Formats a string to be safe to output in a Github action. +// - Encodes "\n" as "%0A" to support multi-line strings. +// https://github.com/actions/toolkit/issues/193#issuecomment-605394935 +// - Strips ANSI output as it will appear malformed. +pub fn githubActionWriter(writer: anytype, self: string) !void { + var offset: usize = 0; + const end = @truncate(u32, self.len); + while (offset < end) { + if (indexOfNewlineOrNonASCIIOrANSI(self, @truncate(u32, offset))) |i| { + const byte = self[i]; + if (byte > 0x7F) { + offset += @max(wtf8ByteSequenceLength(byte), 1); + continue; + } + if (i > 0) { + try writer.writeAll(self[offset..i]); + } + var n: usize = 1; + if (byte == '\n') { + try writer.writeAll("%0A"); + } else if (i + 1 < end) { + const next = self[i + 1]; + if (byte == '\r' and next == '\n') { + n += 1; + try writer.writeAll("%0A"); + } else if (byte == '\x1b' and next == '[') { + n += 1; + if (i + 2 < end) { + const remain = self[(i + 2)..@min(i + 5, end)]; + if (indexOfChar(remain, 'm')) |j| { + n += j + 1; + } + } + } + } + offset = i + n; + } else { + try writer.writeAll(self[offset..end]); + break; + } + } +} + +pub const GithubActionFormatter = struct { + text: string, + + pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try githubActionWriter(writer, this.text); + } +}; + +pub fn githubAction(self: string) strings.GithubActionFormatter { + return GithubActionFormatter{ + .text = self, + }; +} + pub fn quotedWriter(writer: anytype, self: string) !void { var remain = self; if (strings.containsNewlineOrNonASCIIOrQuote(remain)) { @@ -3179,6 +3236,44 @@ pub fn firstNonASCIIWithType(comptime Type: type, slice: Type) ?u32 { return null; } +pub fn indexOfNewlineOrNonASCIIOrANSI(slice_: []const u8, offset: u32) ?u32 { + const slice = slice_[offset..]; + var remaining = slice; + + if (remaining.len == 0) + return null; + + if (comptime Environment.enableSIMD) { + while (remaining.len >= ascii_vector_size) { + const vec: AsciiVector = remaining[0..ascii_vector_size].*; + const cmp = @bitCast(AsciiVectorU1, (vec > max_16_ascii)) | @bitCast(AsciiVectorU1, (vec < min_16_ascii)) | + @bitCast(AsciiVectorU1, vec == @splat(ascii_vector_size, @as(u8, '\r'))) | + @bitCast(AsciiVectorU1, vec == @splat(ascii_vector_size, @as(u8, '\n'))) | + @bitCast(AsciiVectorU1, vec == @splat(ascii_vector_size, @as(u8, '\x1b'))); + + if (@reduce(.Max, cmp) > 0) { + const bitmask = @bitCast(AsciiVectorInt, cmp); + const first = @ctz(bitmask); + + return @as(u32, first) + @intCast(u32, slice.len - remaining.len) + offset; + } + + remaining = remaining[ascii_vector_size..]; + } + + if (comptime Environment.allow_assert) std.debug.assert(remaining.len < ascii_vector_size); + } + + for (remaining) |*char_| { + const char = char_.*; + if (char > 127 or char < 0x20 or char == '\n' or char == '\r' or char == '\x1b') { + return @truncate(u32, (@ptrToInt(char_) - @ptrToInt(slice.ptr))) + offset; + } + } + + return null; +} + pub fn indexOfNewlineOrNonASCII(slice_: []const u8, offset: u32) ?u32 { return indexOfNewlineOrNonASCIICheckStart(slice_, offset, true); } |