aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp2
-rw-r--r--src/bun.js/bindings/bindings.cpp119
-rw-r--r--src/bun.js/bindings/bindings.zig6
-rw-r--r--src/bun.js/bindings/headers.h3
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/test/jest.classes.ts2
-rw-r--r--src/bun.js/test/jest.zig72
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;