diff options
author | 2023-06-20 07:39:44 +0200 | |
---|---|---|
committer | 2023-06-19 22:39:44 -0700 | |
commit | e9e0e051569d3858cfc18b21a6aa6d1b7184f7e7 (patch) | |
tree | 3e31f68eb27b1b60c174c8970f11523d378ea43e | |
parent | 7d94a49ef403750886c2e9ebfc5a7752b8ccb882 (diff) | |
download | bun-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.ts | 31 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 93 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 9 | ||||
-rw-r--r-- | src/bun.js/test/jest.classes.ts | 12 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 181 | ||||
-rw-r--r-- | test/js/bun/test/expect.test.ts | 58 |
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(); |