diff options
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 119 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/bun.js/test/jest.classes.ts | 2 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 72 |
7 files changed, 200 insertions, 5 deletions
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index f9cffcdce..6a6caab67 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -6065,7 +6065,7 @@ static const HashTableValue JSExpectPrototypeTableValues[] = { { "toHaveLastReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveLastReturnedWithCallback, 1 } }, { "toHaveLength"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveLengthCallback, 1 } }, { "toHaveNthReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveNthReturnedWithCallback, 1 } }, - { "toHaveProperty"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHavePropertyCallback, 1 } }, + { "toHaveProperty"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHavePropertyCallback, 2 } }, { "toHaveReturnedTimes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedTimesCallback, 1 } }, { "toHaveReturnedWith"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toHaveReturnedWithCallback, 1 } }, { "toMatch"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, ExpectPrototype__toMatchCallback, 1 } }, diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index b2b0973db..3ff0227f7 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2609,6 +2609,125 @@ JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0, return JSC::JSValue::encode(object->getIfPropertyExists(globalObject, propertyName)); } +JSC__JSValue JSC__JSValue__getIfPropertyExistsFromPath(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, JSC__JSValue arg1) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + JSValue value = JSValue::decode(JSValue0); + JSValue path = JSValue::decode(arg1); + + if (path.isString()) { + String pathString = path.toWTFString(globalObject); + uint32_t length = pathString.length(); + + if (length == 0) { + JSValue prop = value.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(prop); + } + + // Jest doesn't check for valid dot/bracket notation. It will skip all "[" and "]", and search for + // an empty string for "." when it's the first or last character of the path, or if there are + // two in a row. + + JSValue currProp = value; + uint32_t i = 0; + uint32_t j = 0; + + // if "." is the only character, it will search for an empty string twice. + if (pathString.characterAt(0) == '.') { + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + if (currProp.isEmpty()) { + return JSValue::encode(currProp); + } + } + + while (i < length) { + UChar ic = pathString.characterAt(i); + while (ic == '[' || ic == ']' || ic == '.') { + i += 1; + if (i == length) { + + if (ic == '.') { + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(currProp); + } + + // nothing found. + if (j == 0) { + return JSValue::encode({}); + } + + return JSValue::encode(currProp); + } + + UChar previous = ic; + ic = pathString.characterAt(i); + if (previous == '.' && ic == '.') { + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, PropertyName(Identifier::EmptyIdentifier)); + RETURN_IF_EXCEPTION(scope, {}); + if (currProp.isEmpty()) { + return JSValue::encode(currProp); + } + continue; + } + } + + j = i; + UChar jc = pathString.characterAt(j); + while (!(jc == '[' || jc == ']' || jc == '.')) { + j += 1; + if (j == length) { + // break and search for property + break; + } + jc = pathString.characterAt(j); + } + + PropertyName propName = PropertyName(Identifier::fromString(vm, pathString.substring(i, j - i))); + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, propName); + RETURN_IF_EXCEPTION(scope, {}); + if (currProp.isEmpty()) { + return JSValue::encode(currProp); + } + + i = j; + } + + return JSValue::encode(currProp); + } + + if (isArray(globalObject, path)) { + // each item in array is property name, ignore dot/bracket notation + JSValue currProp = value; + forEachInArrayLike(globalObject, path.toObject(globalObject), [&](JSValue item) -> bool { + if (!(item.isString() || item.isNumber())) { + currProp = {}; + return false; + } + + JSString* propNameString = item.toString(globalObject); + RETURN_IF_EXCEPTION(scope, false); + PropertyName propName = PropertyName(propNameString->toIdentifier(globalObject)); + RETURN_IF_EXCEPTION(scope, false); + + currProp = currProp.toObject(globalObject)->getIfPropertyExists(globalObject, propName); + RETURN_IF_EXCEPTION(scope, false); + if (currProp.isEmpty()) { + return false; + } + + return true; + }); + + return JSValue::encode(currProp); + } + + return JSValue::encode({}); +} + void JSC__JSValue__getSymbolDescription(JSC__JSValue symbolValue_, JSC__JSGlobalObject* arg1, ZigString* arg2) { diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index d28b71b66..ab8286b06 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3390,6 +3390,10 @@ pub const JSValue = enum(JSValueReprInt) { return cppFn("getIfPropertyExistsImpl", .{ this, global, ptr, len }); } + pub fn getIfPropertyExistsFromPath(this: JSValue, global: *JSGlobalObject, path: JSValue) JSValue { + return cppFn("getIfPropertyExistsFromPath", .{ this, global, path }); + } + pub fn getSymbolDescription(this: JSValue, global: *JSGlobalObject, str: *ZigString) void { cppFn("getSymbolDescription", .{ this, global, str }); } @@ -3642,7 +3646,7 @@ pub const JSValue = enum(JSValueReprInt) { return this.asNullableVoid().?; } - pub const Extern = [_][]const u8{ "forEachProperty", "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals" }; + pub const Extern = [_][]const u8{ "forEachProperty", "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals", "getIfPropertyExistsFromPath" }; }; extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void; diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 9d335ee90..346cfa50e 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1669539205 +//-- AUTOGENERATED FILE -- 1669680738 #pragma once #include <stddef.h> @@ -497,6 +497,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__fromInt64NoTruncate(JSC__JSGlobalObject* arg CPP_DECL JSC__JSValue JSC__JSValue__fromUInt64NoTruncate(JSC__JSGlobalObject* arg0, uint64_t arg1); CPP_DECL void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSValue__getErrorsProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); +CPP_DECL JSC__JSValue JSC__JSValue__getIfPropertyExistsFromPath(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2); CPP_DECL JSC__JSValue JSC__JSValue__getIfPropertyExistsImpl(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, const unsigned char* arg2, uint32_t arg3); CPP_DECL uint64_t JSC__JSValue__getLengthOfArray(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__JSValue__getNameProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index 20f767d73..55e947f02 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -290,6 +290,7 @@ pub extern fn JSC__JSValue__fromInt64NoTruncate(arg0: ?*JSC__JSGlobalObject, arg pub extern fn JSC__JSValue__fromUInt64NoTruncate(arg0: ?*JSC__JSGlobalObject, arg1: u64) JSC__JSValue; pub extern fn JSC__JSValue__getClassName(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: [*c]ZigString) void; pub extern fn JSC__JSValue__getErrorsProperty(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject) JSC__JSValue; +pub extern fn JSC__JSValue__getIfPropertyExistsFromPath(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, JSValue2: JSC__JSValue) JSC__JSValue; pub extern fn JSC__JSValue__getIfPropertyExistsImpl(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: [*c]const u8, arg3: u32) JSC__JSValue; pub extern fn JSC__JSValue__getLengthOfArray(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject) u64; pub extern fn JSC__JSValue__getNameProperty(JSValue0: JSC__JSValue, arg1: ?*JSC__JSGlobalObject, arg2: [*c]ZigString) void; diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index ad2a5d6e8..1dcb9ae0a 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -102,7 +102,7 @@ export default [ }, toHaveProperty: { fn: "toHaveProperty", - length: 1, + length: 2, }, toBeCloseTo: { fn: "toBeCloseTo", diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index dafb7dc0c..34265789f 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -676,6 +676,8 @@ pub const Expect = struct { } pub fn toEqual(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { + defer this.postMatch(globalObject); + const thisValue = callFrame.this(); const _arguments = callFrame.arguments(1); const arguments: []const JSValue = _arguments.ptr[0.._arguments.len]; @@ -715,7 +717,75 @@ pub const Expect = struct { return .zero; } - pub const toHaveProperty = notImplementedJSCFn; + pub fn toHaveProperty(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { + defer this.postMatch(globalObject); + + const thisValue = callFrame.this(); + const _arguments = callFrame.arguments(2); + const arguments: []const JSValue = _arguments.ptr[0.._arguments.len]; + + if (arguments.len < 1) { + globalObject.throwInvalidArguments("toHaveProperty() requires at least 1 argument", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toHaveProperty must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const expected_property_path = arguments[0]; + expected_property_path.ensureStillAlive(); + const expected_value: ?JSValue = if (arguments.len > 1) arguments[1] else null; + if (expected_value) |ev| ev.ensureStillAlive(); + + const value = 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 (!expected_property_path.isString() and !expected_property_path.isIterable(globalObject)) { + globalObject.throw("Expected path must be a string or an array", .{}); + return .zero; + } + + const not = this.op.contains(.not); + var path_string = ZigString.Empty; + expected_property_path.toZigString(&path_string, globalObject); + + const expected_property = value.getIfPropertyExistsFromPath(globalObject, expected_property_path); + + var pass = !expected_property.isEmpty(); + + if (pass and expected_value != null) { + pass = expected_property.deepEquals(expected_value.?, globalObject); + } + + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + if (not) { + if (!expected_property.isEmpty() and expected_value != null) { + globalObject.throw("Expected property \"{any}\" to not be equal to: {any}", .{ expected_property.toFmt(globalObject, &fmt), expected_value.?.toFmt(globalObject, &fmt) }); + } else { + globalObject.throw("Expected \"{any}\" to not have property: {any}", .{ value.toFmt(globalObject, &fmt), expected_property_path.toFmt(globalObject, &fmt) }); + } + } else { + if (!expected_property.isEmpty() and expected_value != null) { + globalObject.throw("Expected property \"{any}\" to be equal to: {any}", .{ expected_property.toFmt(globalObject, &fmt), expected_value.?.toFmt(globalObject, &fmt) }); + } else { + globalObject.throw("Expected \"{any}\" to have property: {any}", .{ value.toFmt(globalObject, &fmt), expected_property_path.toFmt(globalObject, &fmt) }); + } + } + + return .zero; + } + pub const toHaveBeenCalledTimes = notImplementedJSCFn; pub const toHaveBeenCalledWith = notImplementedJSCFn; pub const toHaveBeenLastCalledWith = notImplementedJSCFn; |