aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-06-01 00:19:33 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-01 00:19:33 -0700
commita4ccd4e0b4cc19f534bf639f30b7e4218400e1e8 (patch)
tree250f89bddd6e6c920645db2ad39bb7edf576edf0 /src
parentcb0f76aa73f6b85667b57015a77ac39d9c78aa0b (diff)
parent689434e012a47b9be897f6d90d6aa211b13dfc19 (diff)
downloadbun-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.zig47
-rw-r--r--src/bun.js/api/bun/subprocess.zig21
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp527
-rw-r--r--src/bun.js/bindings/bindings.zig54
-rw-r--r--src/bun.js/bindings/exports.zig2
-rw-r--r--src/bun.js/bindings/generated_classes.zig51
-rw-r--r--src/bun.js/javascript.zig20
-rw-r--r--src/bun.js/test/diff_format.zig293
-rw-r--r--src/bun.js/test/jest.classes.ts68
-rw-r--r--src/bun.js/test/jest.zig1601
-rw-r--r--src/bun.js/test/pretty_format.zig31
-rw-r--r--src/cli.zig6
-rw-r--r--src/cli/test_command.zig78
-rw-r--r--src/js_parser.zig25
-rw-r--r--src/output.zig8
-rw-r--r--src/string_immutable.zig97
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);
}