aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Tiramify (A.K. Daniel) <94789999+TiranexDev@users.noreply.github.com> 2023-06-20 07:39:44 +0200
committerGravatar GitHub <noreply@github.com> 2023-06-19 22:39:44 -0700
commite9e0e051569d3858cfc18b21a6aa6d1b7184f7e7 (patch)
tree3e31f68eb27b1b60c174c8970f11523d378ea43e
parent7d94a49ef403750886c2e9ebfc5a7752b8ccb882 (diff)
downloadbun-e9e0e051569d3858cfc18b21a6aa6d1b7184f7e7.tar.gz
bun-e9e0e051569d3858cfc18b21a6aa6d1b7184f7e7.tar.zst
bun-e9e0e051569d3858cfc18b21a6aa6d1b7184f7e7.zip
feat(bun/test): Impl. "toBeArray", "toBeArrayOfSize" & "toBeTypeOf" (#3316)
* Implement toBeArray, toBeArrayOfSize, toBeTypeOf * fix typos/variable names * Add testcases for regex and dates * little fix * i didn't paste that...
-rw-r--r--packages/bun-types/bun-test.d.ts31
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp93
-rw-r--r--src/bun.js/bindings/generated_classes.zig9
-rw-r--r--src/bun.js/test/jest.classes.ts12
-rw-r--r--src/bun.js/test/jest.zig181
-rw-r--r--test/js/bun/test/expect.test.ts58
6 files changed, 384 insertions, 0 deletions
diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts
index 05fd0eac0..c26ece304 100644
--- a/packages/bun-types/bun-test.d.ts
+++ b/packages/bun-types/bun-test.d.ts
@@ -739,6 +739,27 @@ declare module "bun:test" {
*/
toBeNil(): void;
/**
+ * Asserts that a value is a `array`.
+ *
+ * @link https://jest-extended.jestcommunity.dev/docs/matchers/array/#tobearray
+ * @example
+ * expect([1]).toBeArray();
+ * expect(new Array(1)).toBeArray();
+ * expect({}).not.toBeArray();
+ */
+ toBeArray(): void;
+ /**
+ * Asserts that a value is a `array` of a certain length.
+ *
+ * @link https://jest-extended.jestcommunity.dev/docs/matchers/array/#tobearrayofsize
+ * @example
+ * expect([]).toBeArrayOfSize(0);
+ * expect([1]).toBeArrayOfSize(1);
+ * expect(new Array(1)).toBeArrayOfSize(1);
+ * expect({}).not.toBeArrayOfSize(0);
+ */
+ toBeArrayOfSize(size: number): void;
+ /**
* Asserts that a value is a `boolean`.
*
* @example
@@ -758,6 +779,16 @@ declare module "bun:test" {
*/
toBeTrue(): void;
/**
+ * Asserts that a value matches a specific type.
+ *
+ * @link https://vitest.dev/api/expect.html#tobetypeof
+ * @example
+ * expect(1).toBeTypeOf("number");
+ * expect("hello").toBeTypeOf("string");
+ * expect([]).not.toBeTypeOf("boolean");
+ */
+ toBeTypeOf(type: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'): void;
+ /**
* Asserts that a value is `false`.
*
* @example
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index b98c122ee..d51a1959a 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -2665,6 +2665,12 @@ 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__toBeArray(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeArrayCallback);
+
+extern "C" EncodedJSValue ExpectPrototype__toBeArrayOfSize(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeArrayOfSizeCallback);
+
extern "C" EncodedJSValue ExpectPrototype__toBeBoolean(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeBooleanCallback);
@@ -2746,6 +2752,9 @@ 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__toBeTypeOf(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeTypeOfCallback);
+
extern "C" EncodedJSValue ExpectPrototype__toBeUndefined(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeUndefinedCallback);
@@ -2834,6 +2843,8 @@ 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 } },
+ { "toBeArray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeArrayCallback, 0 } },
+ { "toBeArrayOfSize"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeArrayOfSizeCallback, 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 } },
@@ -2861,6 +2872,7 @@ static const HashTableValue JSExpectPrototypeTableValues[] = {
{ "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 } },
+ { "toBeTypeOf"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeTypeOfCallback, 1 } },
{ "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 } },
@@ -2967,6 +2979,60 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeCallback, (JSGlobalObject * lexica
return ExpectPrototype__toBe(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
+JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeArrayCallback, (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__toBeArray(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeArrayOfSizeCallback, (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__toBeArrayOfSize(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeBooleanCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
@@ -3696,6 +3762,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeTruthyCallback, (JSGlobalObject *
return ExpectPrototype__toBeTruthy(thisObject->wrapped(), lexicalGlobalObject, callFrame);
}
+JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeTypeOfCallback, (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__toBeTypeOf(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeUndefinedCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = lexicalGlobalObject->vm();
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index b98d59cd3..0ec65a469 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -878,6 +878,10 @@ 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.toBeArray) != CallbackType)
+ @compileLog("Expected Expect.toBeArray to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeArray)));
+ if (@TypeOf(Expect.toBeArrayOfSize) != CallbackType)
+ @compileLog("Expected Expect.toBeArrayOfSize to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeArrayOfSize)));
if (@TypeOf(Expect.toBeBoolean) != CallbackType)
@compileLog("Expected Expect.toBeBoolean to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeBoolean)));
if (@TypeOf(Expect.toBeCloseTo) != CallbackType)
@@ -932,6 +936,8 @@ pub const JSExpect = struct {
@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.toBeTypeOf) != CallbackType)
+ @compileLog("Expected Expect.toBeTypeOf to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeTypeOf)));
if (@TypeOf(Expect.toBeUndefined) != CallbackType)
@compileLog("Expected Expect.toBeUndefined to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeUndefined)));
if (@TypeOf(Expect.toBeWithin) != CallbackType)
@@ -1038,6 +1044,8 @@ pub const JSExpect = struct {
@export(Expect.stringContaining, .{ .name = "ExpectClass__stringContaining" });
@export(Expect.stringMatching, .{ .name = "ExpectClass__stringMatching" });
@export(Expect.toBe, .{ .name = "ExpectPrototype__toBe" });
+ @export(Expect.toBeArray, .{ .name = "ExpectPrototype__toBeArray" });
+ @export(Expect.toBeArrayOfSize, .{ .name = "ExpectPrototype__toBeArrayOfSize" });
@export(Expect.toBeBoolean, .{ .name = "ExpectPrototype__toBeBoolean" });
@export(Expect.toBeCloseTo, .{ .name = "ExpectPrototype__toBeCloseTo" });
@export(Expect.toBeDate, .{ .name = "ExpectPrototype__toBeDate" });
@@ -1065,6 +1073,7 @@ pub const JSExpect = struct {
@export(Expect.toBeSymbol, .{ .name = "ExpectPrototype__toBeSymbol" });
@export(Expect.toBeTrue, .{ .name = "ExpectPrototype__toBeTrue" });
@export(Expect.toBeTruthy, .{ .name = "ExpectPrototype__toBeTruthy" });
+ @export(Expect.toBeTypeOf, .{ .name = "ExpectPrototype__toBeTypeOf" });
@export(Expect.toBeUndefined, .{ .name = "ExpectPrototype__toBeUndefined" });
@export(Expect.toBeWithin, .{ .name = "ExpectPrototype__toBeWithin" });
@export(Expect.toContain, .{ .name = "ExpectPrototype__toContain" });
diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts
index 38eefe778..e5bf567a9 100644
--- a/src/bun.js/test/jest.classes.ts
+++ b/src/bun.js/test/jest.classes.ts
@@ -277,6 +277,14 @@ export default [
fn: "toBeNil",
length: 0,
},
+ toBeArray: {
+ fn: "toBeArray",
+ length: 0,
+ },
+ toBeArrayOfSize: {
+ fn: "toBeArrayOfSize",
+ length: 1,
+ },
toBeBoolean: {
fn: "toBeBoolean",
length: 0,
@@ -285,6 +293,10 @@ export default [
fn: "toBeTrue",
length: 0,
},
+ toBeTypeOf: {
+ fn: "toBeTypeOf",
+ length: 1,
+ },
toBeFalse: {
fn: "toBeFalse",
length: 0,
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index 62be3fbae..ad60a9c5e 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -3006,6 +3006,94 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toBeArray(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("toBeArray() must be called in a test", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const not = this.op.contains(.not);
+ const pass = value.jsType().isArray() != 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("toBeArray", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n";
+ globalThis.throwPretty(fmt, .{received});
+ return .zero;
+ }
+
+ const fmt = comptime getSignature("toBeArray", "", false) ++ "\n\n" ++ "Received: <red>{any}<r>\n";
+ globalThis.throwPretty(fmt, .{received});
+ return .zero;
+ }
+
+ pub fn toBeArrayOfSize(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("toBeArrayOfSize() requires 1 argument", .{});
+ 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;
+ };
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalThis.throw("toBeArrayOfSize() must be called in a test", .{});
+ return .zero;
+ }
+
+ const size = arguments[0];
+ size.ensureStillAlive();
+
+ if (!size.isAnyInt()) {
+ globalThis.throw("toBeArrayOfSize() requires the first argument to be a number", .{});
+ return .zero;
+ }
+
+ active_test_expectation_counter.actual += 1;
+
+ const not = this.op.contains(.not);
+ var pass = value.jsType().isArray() and @intCast(i32, value.getLength(globalThis)) == size.toInt32();
+
+ 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("toBeArrayOfSize", "", true) ++ "\n\n" ++ "Received: <red>{any}<r>\n";
+ globalThis.throwPretty(fmt, .{received});
+ return .zero;
+ }
+
+ const fmt = comptime getSignature("toBeArrayOfSize", "", 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);
@@ -3312,6 +3400,99 @@ pub const Expect = struct {
return .zero;
}
+ pub fn toBeTypeOf(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("toBeTypeOf() requires 1 argument", .{});
+ return .zero;
+ }
+
+ if (this.scope.tests.items.len <= this.test_id) {
+ globalThis.throw("toBeTypeOf() 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 expected = arguments[0];
+ expected.ensureStillAlive();
+
+ const expectedAsStr = expected.toString(globalThis).toSlice(globalThis, default_allocator).slice();
+ active_test_expectation_counter.actual += 1;
+
+ if (!expected.isString()) {
+ globalThis.throwInvalidArguments("toBeTypeOf() requires a string argument", .{});
+ return .zero;
+ }
+
+ if (!std.mem.eql(u8, expectedAsStr, "function") and
+ !std.mem.eql(u8, expectedAsStr, "object") and
+ !std.mem.eql(u8, expectedAsStr, "bigint") and
+ !std.mem.eql(u8, expectedAsStr, "boolean") and
+ !std.mem.eql(u8, expectedAsStr, "number") and
+ !std.mem.eql(u8, expectedAsStr, "string") and
+ !std.mem.eql(u8, expectedAsStr, "symbol") and
+ !std.mem.eql(u8, expectedAsStr, "undefined"))
+ {
+ globalThis.throwInvalidArguments("toBeTypeOf() requires a valid type string argument ('function', 'object', 'bigint', 'boolean', 'number', 'string', 'symbol', 'undefined')", .{});
+ return .zero;
+ }
+
+ const not = this.op.contains(.not);
+ var pass = false;
+ var whatIsTheType: []const u8 = "";
+
+ // Checking for function/class should be done before everything else, or it will fail.
+ if (value.isCallable(globalThis.vm())) {
+ whatIsTheType = "function";
+ } else if (value.isObject() or value.jsType().isArray() or value.isNull()) {
+ whatIsTheType = "object";
+ } else if (value.isBigInt()) {
+ whatIsTheType = "bigint";
+ } else if (value.isBoolean()) {
+ whatIsTheType = "boolean";
+ } else if (value.isNumber()) {
+ whatIsTheType = "number";
+ } else if (value.jsType().isString()) {
+ whatIsTheType = "string";
+ } else if (value.isSymbol()) {
+ whatIsTheType = "symbol";
+ } else if (value.isUndefined()) {
+ whatIsTheType = "undefined";
+ } else {
+ globalThis.throw("Internal consistency error: unknown JSValue type", .{});
+ return .zero;
+ }
+
+ pass = std.mem.eql(u8, expectedAsStr, whatIsTheType);
+
+ if (not) pass = !pass;
+ if (pass) return thisValue;
+
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalThis, .quote_strings = true };
+ const received = value.toFmt(globalThis, &formatter);
+ const expected_str = expected.toFmt(globalThis, &formatter);
+
+ if (not) {
+ const fmt = comptime getSignature("toBeTypeOf", "", true) ++ "\n\n" ++ "Expected type: not <green>{any}<r>\n" ++ "Received type: <red>\"{s}\"<r>\nReceived value: <red>{any}<r>\n";
+ globalThis.throwPretty(fmt, .{ expected_str, whatIsTheType, received });
+ return .zero;
+ }
+
+ const fmt = comptime getSignature("toBeTypeOf", "", false) ++ "\n\n" ++ "Expected type: <green>{any}<r>\n" ++ "Received type: <red>\"{s}\"<r>\nReceived value: <red>{any}<r>\n";
+ globalThis.throwPretty(fmt, .{ expected_str, whatIsTheType, received });
+ return .zero;
+ }
+
pub fn toBeWithin(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) callconv(.C) JSValue {
defer this.postMatch(globalThis);
diff --git a/test/js/bun/test/expect.test.ts b/test/js/bun/test/expect.test.ts
index 2afb9726c..fba20f1dc 100644
--- a/test/js/bun/test/expect.test.ts
+++ b/test/js/bun/test/expect.test.ts
@@ -204,6 +204,64 @@ describe("expect()", () => {
expect({}).not.toBeNil();
});
+ test("toBeArray()", () => {
+ expect([]).toBeArray();
+ expect([1, 2, 3, '🫓']).toBeArray();
+ expect(new Array()).toBeArray();
+ expect(new Array(1, 2, 3)).toBeArray();
+ expect({}).not.toBeArray();
+ expect("🫓").not.toBeArray();
+ expect(0).not.toBeArray();
+ expect(true).not.toBeArray();
+ expect(null).not.toBeArray();
+ });
+
+ test("toBeArrayOfSize()", () => {
+ expect([]).toBeArrayOfSize(0);
+ expect(new Array()).toBeArrayOfSize(0);
+ expect([1, 2, 3, '🫓']).toBeArrayOfSize(4);
+ expect(new Array<string | number>(1, 2, 3, '🫓')).toBeArrayOfSize(4);
+ expect({}).not.toBeArrayOfSize(1);
+ expect("").not.toBeArrayOfSize(1);
+ expect(0).not.toBeArrayOfSize(1)
+ });
+
+ test("toBeTypeOf()", () => {
+ expect("Bun! 🫓").toBeTypeOf("string");
+ expect(0).toBeTypeOf("number");
+ expect(true).toBeTypeOf("boolean");
+ expect([]).toBeTypeOf("object");
+ expect({}).toBeTypeOf("object");
+ expect(null).toBeTypeOf("object");
+ expect(undefined).toBeTypeOf("undefined");
+ expect(() => {}).toBeTypeOf("function");
+ expect(function () {}).toBeTypeOf("function");
+ expect(async () => {}).toBeTypeOf("function");
+ expect(async function () {}).toBeTypeOf("function");
+ expect(function* () {}).toBeTypeOf("function");
+ expect(class {}).toBeTypeOf("function");
+ expect(new Array()).toBeTypeOf("object");
+ expect(BigInt(5)).toBeTypeOf("bigint");
+ expect(/(foo|bar)/g).toBeTypeOf("object");
+ expect(new RegExp("(foo|bar)", "g")).toBeTypeOf("object");
+ expect(new Date()).toBeTypeOf("object");
+
+ expect("Bun!").not.toBeTypeOf("number");
+ expect(0).not.toBeTypeOf("string");
+ expect(true).not.toBeTypeOf("number");
+ expect([]).not.toBeTypeOf("string");
+ expect({}).not.toBeTypeOf("number");
+ expect(null).not.toBeTypeOf("string");
+ expect(undefined).not.toBeTypeOf("boolean");
+ expect(() => {}).not.toBeTypeOf("string");
+ expect(function () {}).not.toBeTypeOf("boolean");
+ expect(async () => {}).not.toBeTypeOf("object");
+ expect(class {}).not.toBeTypeOf("bigint");
+ expect(/(foo|bar)/g).not.toBeTypeOf("string");
+ expect(new RegExp("(foo|bar)", "g")).not.toBeTypeOf("number");
+ expect(new Date()).not.toBeTypeOf("string");
+ });
+
test("toBeBoolean()", () => {
expect(true).toBeBoolean();
expect(false).toBeBoolean();