diff options
author | 2023-05-26 19:24:20 -0700 | |
---|---|---|
committer | 2023-05-26 19:24:20 -0700 | |
commit | 1a30b4fe2903186658ef0c70e9c6cdbb5c5bed6b (patch) | |
tree | bb08e4774e1526e1fbf1fa6c920f9db5b64b31b4 /src | |
parent | 4298f36fc9745a130a3296a05e9577e0c9bae6fe (diff) | |
download | bun-1a30b4fe2903186658ef0c70e9c6cdbb5c5bed6b.tar.gz bun-1a30b4fe2903186658ef0c70e9c6cdbb5c5bed6b.tar.zst bun-1a30b4fe2903186658ef0c70e9c6cdbb5c5bed6b.zip |
Implement `expect().toBeEmpty()` (#3060)
* Implement `expect().toBeEmpty()`
* Fix formatting on test
* Finish up expect().toBeEmpty()
* Update expect.test.ts
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 31 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 41 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 3 | ||||
-rw-r--r-- | src/bun.js/test/jest.classes.ts | 21 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 89 |
5 files changed, 176 insertions, 9 deletions
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index d338bcb9c..5482c461f 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -2671,6 +2671,9 @@ JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeCloseToCallback); extern "C" EncodedJSValue ExpectPrototype__toBeDefined(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ExpectPrototype__toBeDefinedCallback); +extern "C" EncodedJSValue ExpectPrototype__toBeEmpty(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +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); @@ -2779,6 +2782,7 @@ static const HashTableValue JSExpectPrototypeTableValues[] = { { "toBe"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeCallback, 1 } }, { "toBeCloseTo"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeCloseToCallback, 1 } }, { "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 } }, { "toBeFalsy"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeFalsyCallback, 0 } }, { "toBeGreaterThan"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toBeGreaterThanCallback, 1 } }, @@ -2945,6 +2949,33 @@ JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeDefinedCallback, (JSGlobalObject * return ExpectPrototype__toBeDefined(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeEmptyCallback, (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__toBeEmpty(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(ExpectPrototype__toBeEvenCallback, (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 3bf2b9172..99c2306f4 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3113,6 +3113,10 @@ pub const JSValue = enum(JSValueReprInt) { pub const LastMaybeFalsyCellPrimitive = JSType.HeapBigInt; pub const LastJSCObject = JSType.DerivedStringObject; // This is the last "JSC" Object type. After this, we have embedder's (e.g., WebCore) extended object types. + pub inline fn isString(this: JSType) bool { + return this == .String; + } + pub inline fn isStringLike(this: JSType) bool { return switch (this) { .String, .StringObject, .DerivedStringObject => true, @@ -3127,6 +3131,42 @@ pub const JSValue = enum(JSValueReprInt) { }; } + pub inline fn isArrayLike(this: JSType) bool { + return switch (this) { + .Array, + .DerivedArray, + + .ArrayBuffer, + .BigInt64Array, + .BigUint64Array, + .Float32Array, + .Float64Array, + .Int16Array, + .Int32Array, + .Int8Array, + .Uint16Array, + .Uint32Array, + .Uint8Array, + .Uint8ClampedArray, + => true, + else => false, + }; + } + + pub inline fn isSet(this: JSType) bool { + return switch (this) { + .JSSet, .JSWeakSet => true, + else => false, + }; + } + + pub inline fn isMap(this: JSType) bool { + return switch (this) { + .JSMap, .JSWeakMap => true, + else => false, + }; + } + pub inline fn isIndexable(this: JSType) bool { return switch (this) { .Object, @@ -3787,6 +3827,7 @@ pub const JSValue = enum(JSValueReprInt) { return jsType(this).isStringLike(); } + pub fn isBigInt(this: JSValue) bool { return cppFn("isBigInt", .{this}); } diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 43acdc98b..a30774aa1 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -882,6 +882,8 @@ pub const JSExpect = struct { @compileLog("Expected Expect.toBeCloseTo to be a callback but received " ++ @typeName(@TypeOf(Expect.toBeCloseTo))); 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.toBeFalsy) != CallbackType) @@ -1002,6 +1004,7 @@ pub const JSExpect = struct { @export(Expect.toBe, .{ .name = "ExpectPrototype__toBe" }); @export(Expect.toBeCloseTo, .{ .name = "ExpectPrototype__toBeCloseTo" }); @export(Expect.toBeDefined, .{ .name = "ExpectPrototype__toBeDefined" }); + @export(Expect.toBeEmpty, .{ .name = "ExpectPrototype__toBeEmpty" }); @export(Expect.toBeEven, .{ .name = "ExpectPrototype__toBeEven" }); @export(Expect.toBeFalsy, .{ .name = "ExpectPrototype__toBeFalsy" }); @export(Expect.toBeGreaterThan, .{ .name = "ExpectPrototype__toBeGreaterThan" }); diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index 612fc0268..bc2dbb1a1 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -121,10 +121,6 @@ export default [ fn: "toBeCloseTo", length: 1, }, - toBeEven: { - fn: "toBeEven", - length: 0, - }, toBeGreaterThan: { fn: "toBeGreaterThan", length: 1, @@ -141,10 +137,6 @@ export default [ fn: "toBeLessThanOrEqual", length: 1, }, - toBeOdd: { - fn: "toBeOdd", - length: 0, - }, toBeInstanceOf: { fn: "toBeInstanceOf", length: 1, @@ -229,6 +221,19 @@ export default [ getter: "getRejects", this: true, }, + // jest-extended + toBeEmpty: { + fn: "toBeEmpty", + length: 0, + }, + toBeEven: { + fn: "toBeEven", + length: 0, + }, + toBeOdd: { + fn: "toBeOdd", + length: 0, + }, }, }), ]; diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index ad9cf9b5b..58a6a3efe 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -1304,7 +1304,7 @@ pub const Expect = struct { const actual_length = value.getLengthIfPropertyExistsInternal(globalObject); - if (actual_length == std.math.f64_max) { + if (actual_length == std.math.inf(f64)) { var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; globalObject.throw("Received value does not have a length property: {any}", .{value.toFmt(globalObject, &fmt)}); return .zero; @@ -3008,6 +3008,93 @@ pub const Expect = struct { return thisValue; } + pub fn toBeEmpty(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { + defer this.postMatch(globalObject); + + const thisValue = callFrame.this(); + const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse { + globalObject.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) { + globalObject.throw("toBeEmpty() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + var pass = false; + var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject, .quote_strings = true }; + + const actual_length = value.getLengthIfPropertyExistsInternal(globalObject); + + if (actual_length == std.math.inf(f64)) { + if (value.jsTypeLoose().isObject()) { + if (value.isIterable(globalObject)) { + var any_properties_in_iterator = false; + value.forEach(globalObject, &any_properties_in_iterator, struct { + pub fn anythingInIterator( + _: *JSC.VM, + _: *JSGlobalObject, + any_: ?*anyopaque, + _: JSValue, + ) callconv(.C) void { + bun.cast(*bool, any_.?).* = true; + } + }.anythingInIterator); + pass = !any_properties_in_iterator; + } else { + var props_iter = JSC.JSPropertyIterator(.{ + .skip_empty_name = false, + + .include_value = true, + }).init(globalObject, value.asObjectRef()); + defer props_iter.deinit(); + pass = props_iter.len == 0; + } + } else { + const signature = comptime getSignature("toBeEmpty", "", false); + const fmt = signature ++ "\n\nExpected value to be a string, object, or iterable" ++ + "\n\nReceived: <red>{any}<r>\n"; + globalObject.throwPretty(fmt, .{value.toFmt(globalObject, &formatter)}); + return .zero; + } + } else if (std.math.isNan(actual_length)) { + globalObject.throw("Received value has non-number length property: {}", .{actual_length}); + return .zero; + } else { + pass = actual_length == 0; + } + + if (not and pass) { + const signature = comptime getSignature("toBeEmpty", "", true); + const fmt = signature ++ "\n\nExpected value <b>not<r> to be a string, object, or iterable" ++ + "\n\nReceived: <red>{any}<r>\n"; + globalObject.throwPretty(fmt, .{value.toFmt(globalObject, &formatter)}); + return .zero; + } + + if (not) pass = !pass; + if (pass) return thisValue; + + if (not) { + const signature = comptime getSignature("toBeEmpty", "", true); + const fmt = signature ++ "\n\nExpected value <b>not<r> to be empty" ++ + "\n\nReceived: <red>{any}<r>\n"; + globalObject.throwPretty(fmt, .{value.toFmt(globalObject, &formatter)}); + return .zero; + } + + const signature = comptime getSignature("toBeEmpty", "", false); + const fmt = signature ++ "\n\nExpected value to be empty" ++ + "\n\nReceived: <red>{any}<r>\n"; + globalObject.throwPretty(fmt, .{value.toFmt(globalObject, &formatter)}); + return .zero; + } + pub const PropertyMatcherIterator = struct { received_object: JSValue, failed: bool, |