aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp10
-rw-r--r--src/bun.js/bindings/bindings.cpp347
-rw-r--r--src/bun.js/bindings/bindings.zig28
-rw-r--r--src/bun.js/bindings/headers-handwritten.h3
-rw-r--r--src/bun.js/bindings/headers.h3
-rw-r--r--src/bun.js/bindings/headers.zig3
-rw-r--r--src/bun.js/test/jest.zig14
-rw-r--r--test/js/bun/test/test-test.test.ts168
8 files changed, 429 insertions, 147 deletions
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 9b3bfd2a2..5589d2add 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -2077,11 +2077,11 @@ JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (JSGlobalObject * globalObject,
Vector<std::pair<JSValue, JSValue>, 16> stack;
if (arg3.isBoolean() && arg3.asBoolean()) {
- bool isEqual = Bun__deepEquals<true>(globalObject, arg1, arg2, stack, &scope, true);
+ bool isEqual = Bun__deepEquals<true, false>(globalObject, arg1, arg2, stack, &scope, true);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(jsBoolean(isEqual));
} else {
- bool isEqual = Bun__deepEquals<false>(globalObject, arg1, arg2, stack, &scope, true);
+ bool isEqual = Bun__deepEquals<false, false>(globalObject, arg1, arg2, stack, &scope, true);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(jsBoolean(isEqual));
}
@@ -2107,13 +2107,13 @@ JSC_DEFINE_HOST_FUNCTION(functionBunDeepMatch, (JSGlobalObject * globalObject, J
if (!subset.isObject() || !object.isObject()) {
auto throwScope = DECLARE_THROW_SCOPE(vm);
- throwTypeError(globalObject, throwScope, "Expected 2 object to match"_s);
+ throwTypeError(globalObject, throwScope, "Expected 2 objects to match"_s);
return JSValue::encode(jsUndefined());
}
- bool isEqual = Bun__deepMatch(object, subset, globalObject, &scope, false);
+ bool match = Bun__deepMatch<false>(object, subset, globalObject, &scope, false);
RETURN_IF_EXCEPTION(scope, {});
- return JSValue::encode(jsBoolean(isEqual));
+ return JSValue::encode(jsBoolean(match));
}
JSC_DECLARE_HOST_FUNCTION(functionBunNanoseconds);
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 2ec1bd902..4eee81f4d 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -123,6 +123,115 @@ static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res)
using namespace JSC;
+using namespace WebCore;
+
+enum class AsymmetricMatcherResult : uint8_t {
+ PASS,
+ FAIL,
+ NOT_MATCHER,
+};
+
+AsymmetricMatcherResult matchAsymmetricMatcher(JSGlobalObject* globalObject, JSCell* matcherPropCell, JSValue otherProp, ThrowScope* throwScope)
+{
+ VM& vm = globalObject->vm();
+
+ if (auto* expectAnything = jsDynamicCast<JSExpectAnything*>(matcherPropCell)) {
+ if (otherProp.isUndefinedOrNull()) {
+ return AsymmetricMatcherResult::FAIL;
+ }
+
+ return AsymmetricMatcherResult::PASS;
+ } else if (auto* expectAny = jsDynamicCast<JSExpectAny*>(matcherPropCell)) {
+ JSValue constructorValue = expectAny->m_constructorValue.get();
+ JSObject* constructorObject = constructorValue.getObject();
+
+ if (otherProp.isPrimitive()) {
+ if (otherProp.isNumber() && globalObject->numberObjectConstructor() == constructorObject) {
+ return AsymmetricMatcherResult::PASS;
+ } else if (otherProp.isBoolean() && globalObject->booleanObjectConstructor() == constructorObject) {
+ return AsymmetricMatcherResult::PASS;
+ } else if (otherProp.isSymbol() && globalObject->symbolObjectConstructor() == constructorObject) {
+ return AsymmetricMatcherResult::PASS;
+ } else if (otherProp.isString()) {
+ if (auto* constructorFunction = jsDynamicCast<JSFunction*>(constructorObject)) {
+ String name = constructorFunction->name(vm);
+ if (name == "String"_s) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ } else if (auto* internalConstructorFunction = jsDynamicCast<InternalFunction*>(constructorObject)) {
+ String name = internalConstructorFunction->name();
+ if (name == "String"_s) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ }
+ } else if (otherProp.isBigInt()) {
+ if (auto* constructorFunction = jsDynamicCast<JSFunction*>(constructorObject)) {
+ String name = constructorFunction->name(vm);
+ if (name == "BigInt"_s) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ } else if (auto* internalConstructorFunction = jsDynamicCast<InternalFunction*>(constructorObject)) {
+ String name = internalConstructorFunction->name();
+ if (name == "BigInt"_s) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ }
+ }
+
+ return AsymmetricMatcherResult::FAIL;
+ }
+
+ if (constructorObject->hasInstance(globalObject, otherProp)) {
+ return AsymmetricMatcherResult::PASS;
+ }
+
+ return AsymmetricMatcherResult::FAIL;
+ } else if (auto* expectStringContaining = jsDynamicCast<JSExpectStringContaining*>(matcherPropCell)) {
+ JSValue expectedSubstring = expectStringContaining->m_stringValue.get();
+
+ if (otherProp.isString()) {
+ String otherString = otherProp.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
+
+ String substring = expectedSubstring.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
+
+ if (otherString.find(substring) != WTF::notFound) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ }
+
+ return AsymmetricMatcherResult::FAIL;
+ } else if (auto* expectStringMatching = jsDynamicCast<JSExpectStringMatching*>(matcherPropCell)) {
+ JSValue expectedTestValue = expectStringMatching->m_testValue.get();
+
+ if (otherProp.isString()) {
+ if (expectedTestValue.isString()) {
+ String otherString = otherProp.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
+
+ String substring = expectedTestValue.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
+
+ if (otherString.find(substring) != WTF::notFound) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ } else if (expectedTestValue.isCell() and expectedTestValue.asCell()->type() == RegExpObjectType) {
+ if (auto* regex = jsDynamicCast<RegExpObject*>(expectedTestValue)) {
+ JSString* otherString = otherProp.toString(globalObject);
+ if (regex->match(globalObject, otherString)) {
+ return AsymmetricMatcherResult::PASS;
+ }
+ }
+ }
+ }
+
+ return AsymmetricMatcherResult::FAIL;
+ }
+
+ return AsymmetricMatcherResult::NOT_MATCHER;
+}
+
template<typename PromiseType, bool isInternal>
static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObject, JSC::EncodedJSValue ctx, JSC__JSValue (*resolverFunction)(JSC__JSGlobalObject* arg0, JSC__CallFrame* callFrame), JSC__JSValue (*rejecterFunction)(JSC__JSGlobalObject* arg0, JSC__CallFrame* callFrame))
{
@@ -183,10 +292,39 @@ JSValue getIndexWithoutAccessors(JSGlobalObject* globalObject, JSObject* obj, ui
return JSValue();
}
-template<bool isStrict>
+template<bool isStrict, bool enableAsymmetricMatchers>
bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, ThrowScope* scope, bool addToStack)
{
VM& vm = globalObject->vm();
+
+ // need to check this before primitives, asymmetric matchers
+ // can match against any type of value.
+ if constexpr (enableAsymmetricMatchers) {
+ JSCell* c1 = v1.asCell();
+ JSCell* c2 = v2.asCell();
+ if (v2.isCell() && !v2.isEmpty() && c2->type() == JSC::JSType(JSDOMWrapperType)) {
+ switch (matchAsymmetricMatcher(globalObject, c2, v1, scope)) {
+ case AsymmetricMatcherResult::FAIL:
+ return false;
+ case AsymmetricMatcherResult::PASS:
+ return true;
+ case AsymmetricMatcherResult::NOT_MATCHER:
+ // continue comparison
+ break;
+ }
+ } else if (v1.isCell() && !v1.isEmpty() && c1->type() == JSC::JSType(JSDOMWrapperType)) {
+ switch (matchAsymmetricMatcher(globalObject, c1, v2, scope)) {
+ case AsymmetricMatcherResult::FAIL:
+ return false;
+ case AsymmetricMatcherResult::PASS:
+ return true;
+ case AsymmetricMatcherResult::NOT_MATCHER:
+ // continue comparison
+ break;
+ }
+ }
+ }
+
if (!v1.isEmpty() && !v2.isEmpty() && JSC::sameValue(globalObject, v1, v2)) {
return true;
}
@@ -257,7 +395,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
RETURN_IF_EXCEPTION(*scope, false);
// set has unique values, no need to count
- if (Bun__deepEquals<isStrict>(globalObject, nextValue1, nextValue2, stack, scope, false)) {
+ if (Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, nextValue1, nextValue2, stack, scope, false)) {
found = true;
if (!nextValue1.isPrimitive()) {
stack.append({ nextValue1, nextValue2 });
@@ -276,7 +414,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return false;
}
- break;
+ return true;
}
case JSMapType: {
if (c2Type != JSMapType) {
@@ -337,8 +475,8 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
JSValue value2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(1));
RETURN_IF_EXCEPTION(*scope, false);
- if (Bun__deepEquals<isStrict>(globalObject, key1, key2, stack, scope, false)) {
- if (Bun__deepEquals<isStrict>(globalObject, nextValue1, nextValue2, stack, scope, false)) {
+ if (Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, key1, key2, stack, scope, false)) {
+ if (Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, nextValue1, nextValue2, stack, scope, false)) {
found = true;
if (!nextValue1.isPrimitive()) {
stack.append({ nextValue1, nextValue2 });
@@ -358,7 +496,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return false;
}
- break;
+ return true;
}
case ArrayBufferType: {
if (c2Type != ArrayBufferType) {
@@ -399,10 +537,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
JSC::DateInstance* left = jsCast<DateInstance*>(v1);
JSC::DateInstance* right = jsCast<DateInstance*>(v2);
- if (left->structureID() == right->structureID()) {
- return left->internalNumber() == right->internalNumber();
- }
- break;
+ return left->internalNumber() == right->internalNumber();
}
case RegExpObjectType: {
if (c2Type != RegExpObjectType) {
@@ -463,10 +598,18 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return (memcmp(vector, rightVector, byteLength) == 0);
}
case StringObjectType: {
+ if (c2Type != StringObjectType) {
+ return false;
+ }
+
if (!equal(JSObject::calculatedClassName(o1), JSObject::calculatedClassName(o2))) {
return false;
}
- break;
+
+ JSString* s1 = c1->toStringInline(globalObject);
+ JSString* s2 = c2->toStringInline(globalObject);
+
+ return s1->equal(globalObject, s2);
}
case JSFunctionType: {
return false;
@@ -518,7 +661,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
}
}
- if (!Bun__deepEquals<isStrict>(globalObject, left, right, stack, scope, true)) {
+ if (!Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, left, right, stack, scope, true)) {
return false;
}
@@ -573,7 +716,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return false;
}
- if (!Bun__deepEquals<isStrict>(globalObject, prop1, prop2, stack, scope, true)) {
+ if (!Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, prop1, prop2, stack, scope, true)) {
return false;
}
@@ -633,7 +776,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return true;
}
- if (!Bun__deepEquals<isStrict>(globalObject, left, right, stack, scope, true)) {
+ if (!Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, left, right, stack, scope, true)) {
result = false;
return false;
}
@@ -707,7 +850,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return false;
}
- if (!Bun__deepEquals<isStrict>(globalObject, prop1, prop2, stack, scope, true)) {
+ if (!Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, prop1, prop2, stack, scope, true)) {
return false;
}
@@ -721,93 +864,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return true;
}
-using namespace WebCore;
-
-enum class AsymmetricMatcherResult : uint8_t {
- PASS,
- FAIL,
- NOT_MATCHER,
-};
-
-AsymmetricMatcherResult matchAsymmetricMatcher(JSGlobalObject* globalObject, JSCell* matcherPropCell, JSValue otherProp, ThrowScope* throwScope)
-{
- if (auto* expectAnything = jsDynamicCast<JSExpectAnything*>(matcherPropCell)) {
- if (otherProp.isUndefinedOrNull()) {
- return AsymmetricMatcherResult::FAIL;
- }
-
- return AsymmetricMatcherResult::PASS;
- } else if (auto* expectAny = jsDynamicCast<JSExpectAny*>(matcherPropCell)) {
- JSValue constructorValue = expectAny->m_constructorValue.get();
- JSObject* constructorObject = constructorValue.getObject();
-
- if (constructorObject->hasInstance(globalObject, otherProp)) {
- return AsymmetricMatcherResult::PASS;
- }
-
- // check for basic types
- VM& vm = globalObject->vm();
- ZigString name = {};
- JSC__JSValue__getNameProperty(JSValue::encode(constructorValue), globalObject, &name);
- StringView nameView(name.ptr, name.len);
-
- if (otherProp.isNumber() && nameView == "Number"_s) {
- return AsymmetricMatcherResult::PASS;
- } else if (otherProp.isBoolean() && nameView == "Boolean"_s) {
- return AsymmetricMatcherResult::PASS;
- } else if (otherProp.isString() && nameView == "String"_s) {
- return AsymmetricMatcherResult::PASS;
- } else if (otherProp.isBigInt() && nameView == "BigInt"_s) {
- return AsymmetricMatcherResult::PASS;
- }
-
- return AsymmetricMatcherResult::FAIL;
- } else if (auto* expectStringContaining = jsDynamicCast<JSExpectStringContaining*>(matcherPropCell)) {
- JSValue expectedSubstring = expectStringContaining->m_stringValue.get();
-
- if (otherProp.isString()) {
- String otherString = otherProp.toWTFString(globalObject);
- RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
-
- String substring = expectedSubstring.toWTFString(globalObject);
- RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
-
- if (otherString.find(substring) != WTF::notFound) {
- return AsymmetricMatcherResult::PASS;
- }
- }
-
- return AsymmetricMatcherResult::FAIL;
- } else if (auto* expectStringMatching = jsDynamicCast<JSExpectStringMatching*>(matcherPropCell)) {
- JSValue expectedTestValue = expectStringMatching->m_testValue.get();
-
- if (otherProp.isString()) {
- if (expectedTestValue.isString()) {
- String otherString = otherProp.toWTFString(globalObject);
- RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
-
- String substring = expectedTestValue.toWTFString(globalObject);
- RETURN_IF_EXCEPTION(*throwScope, AsymmetricMatcherResult::FAIL);
-
- if (otherString.find(substring) != WTF::notFound) {
- return AsymmetricMatcherResult::PASS;
- }
- } else if (expectedTestValue.isCell() and expectedTestValue.asCell()->type() == RegExpObjectType) {
- if (auto* regex = jsDynamicCast<RegExpObject*>(expectedTestValue)) {
- JSString* otherString = otherProp.toString(globalObject);
- if (regex->match(globalObject, otherString)) {
- return AsymmetricMatcherResult::PASS;
- }
- }
- }
- }
-
- return AsymmetricMatcherResult::FAIL;
- }
-
- return AsymmetricMatcherResult::NOT_MATCHER;
-}
-
+template<bool enableAsymmetricMatchers>
bool Bun__deepMatch(JSValue objValue, JSValue subsetValue, JSGlobalObject* globalObject, ThrowScope* throwScope, bool replacePropsWithAsymmetricMatchers)
{
VM& vm = globalObject->vm();
@@ -848,36 +905,38 @@ bool Bun__deepMatch(JSValue objValue, JSValue subsetValue, JSGlobalObject* globa
JSCell* subsetPropCell = subsetProp.asCell();
JSCell* propCell = prop.asCell();
- if (subsetProp.isCell() and subsetPropCell->type() == JSC::JSType(JSDOMWrapperType)) {
- switch (matchAsymmetricMatcher(globalObject, subsetPropCell, prop, throwScope)) {
- case AsymmetricMatcherResult::FAIL:
- return false;
- case AsymmetricMatcherResult::PASS:
- if (replacePropsWithAsymmetricMatchers) {
- obj->putDirect(vm, subsetProps[i], subsetProp);
+ if constexpr (enableAsymmetricMatchers) {
+ if (subsetProp.isCell() && !subsetProp.isEmpty() && subsetPropCell->type() == JSC::JSType(JSDOMWrapperType)) {
+ switch (matchAsymmetricMatcher(globalObject, subsetPropCell, prop, throwScope)) {
+ case AsymmetricMatcherResult::FAIL:
+ return false;
+ case AsymmetricMatcherResult::PASS:
+ if (replacePropsWithAsymmetricMatchers) {
+ obj->putDirect(vm, subsetProps[i], subsetProp);
+ }
+ // continue to next subset prop
+ continue;
+ case AsymmetricMatcherResult::NOT_MATCHER:
+ break;
}
- // continue to next subset prop
- continue;
- case AsymmetricMatcherResult::NOT_MATCHER:
- break;
- }
- } else if (prop.isCell() and propCell->type() == JSC::JSType(JSDOMWrapperType)) {
- switch (matchAsymmetricMatcher(globalObject, propCell, subsetProp, throwScope)) {
- case AsymmetricMatcherResult::FAIL:
- return false;
- case AsymmetricMatcherResult::PASS:
- if (replacePropsWithAsymmetricMatchers) {
- subsetObj->putDirect(vm, subsetProps[i], prop);
+ } else if (prop.isCell() && !prop.isEmpty() && propCell->type() == JSC::JSType(JSDOMWrapperType)) {
+ switch (matchAsymmetricMatcher(globalObject, propCell, subsetProp, throwScope)) {
+ case AsymmetricMatcherResult::FAIL:
+ return false;
+ case AsymmetricMatcherResult::PASS:
+ if (replacePropsWithAsymmetricMatchers) {
+ subsetObj->putDirect(vm, subsetProps[i], prop);
+ }
+ // continue to next subset prop
+ continue;
+ case AsymmetricMatcherResult::NOT_MATCHER:
+ break;
}
- // continue to next subset prop
- continue;
- case AsymmetricMatcherResult::NOT_MATCHER:
- break;
}
}
if (subsetProp.isObject() and prop.isObject()) {
- if (!Bun__deepMatch(prop, subsetProp, globalObject, throwScope, replacePropsWithAsymmetricMatchers)) {
+ if (!Bun__deepMatch<enableAsymmetricMatchers>(prop, subsetProp, globalObject, throwScope, replacePropsWithAsymmetricMatchers)) {
return false;
}
} else {
@@ -1622,7 +1681,17 @@ bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC_
ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
- return Bun__deepEquals<false>(globalObject, v1, v2, stack, &scope, true);
+ return Bun__deepEquals<false, false>(globalObject, v1, v2, stack, &scope, true);
+}
+
+bool JSC__JSValue__jestDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
+{
+ JSValue v1 = JSValue::decode(JSValue0);
+ JSValue v2 = JSValue::decode(JSValue1);
+
+ ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ Vector<std::pair<JSValue, JSValue>, 16> stack;
+ return Bun__deepEquals<false, true>(globalObject, v1, v2, stack, &scope, true);
}
bool JSC__JSValue__strictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
@@ -1632,7 +1701,17 @@ bool JSC__JSValue__strictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1
ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
- return Bun__deepEquals<true>(globalObject, v1, v2, stack, &scope, true);
+ return Bun__deepEquals<true, false>(globalObject, v1, v2, stack, &scope, true);
+}
+
+bool JSC__JSValue__jestStrictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
+{
+ JSValue v1 = JSValue::decode(JSValue0);
+ JSValue v2 = JSValue::decode(JSValue1);
+
+ ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ Vector<std::pair<JSValue, JSValue>, 16> stack;
+ return Bun__deepEquals<true, true>(globalObject, v1, v2, stack, &scope, true);
}
bool JSC__JSValue__deepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject, bool replacePropsWithAsymmetricMatchers)
@@ -1642,7 +1721,17 @@ bool JSC__JSValue__deepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__
ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
- return Bun__deepMatch(obj, subset, globalObject, &scope, replacePropsWithAsymmetricMatchers);
+ return Bun__deepMatch<false>(obj, subset, globalObject, &scope, replacePropsWithAsymmetricMatchers);
+}
+
+bool JSC__JSValue__jestDeepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject, bool replacePropsWithAsymmetricMatchers)
+{
+ JSValue obj = JSValue::decode(JSValue0);
+ JSValue subset = JSValue::decode(JSValue1);
+
+ ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
+
+ return Bun__deepMatch<true>(obj, subset, globalObject, &scope, replacePropsWithAsymmetricMatchers);
}
// This is the same as the C API version, except it returns a JSValue which may be a *Exception
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 18aaf3db9..35c9d26fa 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -4414,14 +4414,29 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("deepEquals", .{ this, other, global });
}
+ /// same as `JSValue.deepEquals`, but with jest asymmetric matchers enabled
+ pub fn jestDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
+ return cppFn("jestDeepEquals", .{ this, other, global });
+ }
+
pub fn strictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
return cppFn("strictDeepEquals", .{ this, other, global });
}
+ /// same as `JSValue.strictDeepEquals`, but with jest asymmetric matchers enabled
+ pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
+ return cppFn("jestStrictDeepEquals", .{ this, other, global });
+ }
+
pub fn deepMatch(this: JSValue, subset: JSValue, global: *JSGlobalObject, replace_props_with_asymmetric_matchers: bool) bool {
return cppFn("deepMatch", .{ this, subset, global, replace_props_with_asymmetric_matchers });
}
+ /// same as `JSValue.deepMatch`, but with jest asymmetric matchers enabled
+ pub fn jestDeepMatch(this: JSValue, subset: JSValue, global: *JSGlobalObject, replace_props_with_asymmetric_matchers: bool) bool {
+ return cppFn("jestDeepMatch", .{ this, subset, global, replace_props_with_asymmetric_matchers });
+ }
+
pub const DiffMethod = enum(u8) {
none,
character,
@@ -4705,7 +4720,6 @@ pub const JSValue = enum(JSValueReprInt) {
"createTypeError",
"createUninitializedUint8Array",
"deepEquals",
- "deepMatch",
"eqlCell",
"eqlValue",
"fastGetDirect_",
@@ -4739,13 +4753,11 @@ pub const JSValue = enum(JSValueReprInt) {
"isBoolean",
"isCallable",
"isClass",
- "isConstructor",
"isCustomGetterSetter",
"isError",
"isException",
"isGetterSetter",
"isHeapBigInt",
- "isInstanceOf",
"isInt32",
"isInt32AsAnyInt",
"isIterable",
@@ -4775,7 +4787,6 @@ pub const JSValue = enum(JSValueReprInt) {
"putIndex",
"putRecord",
"strictDeepEquals",
- "stringIncludes",
"symbolFor",
"symbolKeyFor",
"toBoolean",
@@ -4783,7 +4794,6 @@ pub const JSValue = enum(JSValueReprInt) {
"toError_",
"toInt32",
"toInt64",
- "toMatch",
"toObject",
"toPropertyKeyValue",
"toString",
@@ -4792,6 +4802,14 @@ pub const JSValue = enum(JSValueReprInt) {
"toWTFString",
"toZigException",
"toZigString",
+ "toMatch",
+ "isConstructor",
+ "isInstanceOf",
+ "stringIncludes",
+ "deepMatch",
+ "jestDeepEquals",
+ "jestStrictDeepEquals",
+ "jestDeepMatch",
};
};
diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h
index a4287cf2e..57940550f 100644
--- a/src/bun.js/bindings/headers-handwritten.h
+++ b/src/bun.js/bindings/headers-handwritten.h
@@ -338,9 +338,10 @@ extern "C" size_t Bun__encoding__byteLengthUTF16(const UChar* ptr, size_t len, E
extern "C" int64_t Bun__encoding__constructFromLatin1(void*, const unsigned char* ptr, size_t len, Encoding encoding);
extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, size_t len, Encoding encoding);
-template<bool isStrict>
+template<bool isStrict, bool enableAsymmetricMatchers>
bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSC::JSValue v1, JSC::JSValue v2, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, JSC::ThrowScope* scope, bool addToStack);
+template<bool enableAsymmetricMatchers>
bool Bun__deepMatch(JSC::JSValue object, JSC::JSValue subset, JSC::JSGlobalObject* globalObject, JSC::ThrowScope* throwScope, bool replacePropsWithAsymmetricMatchers);
extern "C" void Bun__remapStackFramePositions(JSC::JSGlobalObject*, ZigStackFrame*, size_t);
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 4b3875d83..cdf7e05f4 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -365,6 +365,9 @@ CPP_DECL bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSVa
CPP_DECL bool JSC__JSValue__isSymbol(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isTerminationException(JSC__JSValue JSValue0, JSC__VM* arg1);
CPP_DECL bool JSC__JSValue__isUInt32AsAnyInt(JSC__JSValue JSValue0);
+CPP_DECL bool JSC__JSValue__jestDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2);
+CPP_DECL bool JSC__JSValue__jestDeepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2, bool arg3);
+CPP_DECL bool JSC__JSValue__jestStrictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2);
CPP_DECL JSC__JSValue JSC__JSValue__jsBoolean(bool arg0);
CPP_DECL JSC__JSValue JSC__JSValue__jsDoubleNumber(double arg0);
CPP_DECL JSC__JSValue JSC__JSValue__jsNull();
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index 403be20f6..4dda5f30b 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -265,6 +265,9 @@ pub extern fn JSC__JSValue__isSameValue(JSValue0: JSC__JSValue, JSValue1: JSC__J
pub extern fn JSC__JSValue__isSymbol(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isTerminationException(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool;
pub extern fn JSC__JSValue__isUInt32AsAnyInt(JSValue0: JSC__JSValue) bool;
+pub extern fn JSC__JSValue__jestDeepEquals(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject) bool;
+pub extern fn JSC__JSValue__jestDeepMatch(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject, arg3: bool) bool;
+pub extern fn JSC__JSValue__jestStrictDeepEquals(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject) bool;
pub extern fn JSC__JSValue__jsBoolean(arg0: bool) JSC__JSValue;
pub extern fn JSC__JSValue__jsDoubleNumber(arg0: f64) JSC__JSValue;
pub extern fn JSC__JSValue__jsNull(...) JSC__JSValue;
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index ad60a9c5e..6c77d7aaa 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -1620,7 +1620,7 @@ pub const Expect = struct {
value.ensureStillAlive();
const not = this.op.contains(.not);
- var pass = value.deepEquals(expected, globalObject);
+ var pass = value.jestDeepEquals(expected, globalObject);
if (not) pass = !pass;
if (pass) return thisValue;
@@ -1673,7 +1673,7 @@ pub const Expect = struct {
value.ensureStillAlive();
const not = this.op.contains(.not);
- var pass = value.strictDeepEquals(expected, globalObject);
+ var pass = value.jestStrictDeepEquals(expected, globalObject);
if (not) pass = !pass;
if (pass) return thisValue;
@@ -1750,7 +1750,7 @@ pub const Expect = struct {
}
if (pass and expected_property != null) {
- pass = received_property.deepEquals(expected_property.?, globalObject);
+ pass = received_property.jestDeepEquals(expected_property.?, globalObject);
}
if (not) pass = !pass;
@@ -2828,7 +2828,7 @@ pub const Expect = struct {
if (property_matchers) |_prop_matchers| {
var prop_matchers = _prop_matchers;
- if (!value.deepMatch(prop_matchers, globalObject, true)) {
+ if (!value.jestDeepMatch(prop_matchers, globalObject, true)) {
// TODO: print diff with properties from propertyMatchers
const signature = comptime getSignature("toMatchSnapshot", "<green>propertyMatchers<r>", false);
const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++
@@ -4179,18 +4179,18 @@ pub const Expect = struct {
if (args.len < 1 or !args[0].isObject()) {
const matcher_error = "\n\n<b>Matcher error<r>: <green>expected<r> value must be a non-null object\n";
if (not) {
- const fmt = comptime getSignature("toMatchObject", "", true) ++ matcher_error;
+ const fmt = comptime getSignature("toMatchObject", "<green>expected<r>", true) ++ matcher_error;
globalObject.throwPretty(fmt, .{});
return .zero;
}
- const fmt = comptime getSignature("toMatchObject", "", false) ++ matcher_error;
+ const fmt = comptime getSignature("toMatchObject", "<green>expected<r>", false) ++ matcher_error;
globalObject.throwPretty(fmt, .{});
return .zero;
}
const property_matchers = args[0];
- var pass = received_object.deepMatch(property_matchers, globalObject, true);
+ var pass = received_object.jestDeepMatch(property_matchers, globalObject, true);
if (not) pass = !pass;
if (pass) return thisValue;
diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts
index ebb6ecfab..ed71e7652 100644
--- a/test/js/bun/test/test-test.test.ts
+++ b/test/js/bun/test/test-test.test.ts
@@ -366,6 +366,174 @@ test("deepEquals works with proxies", () => {
}
});
+test("deepEquals works with sets/maps/dates/strings", () => {
+ const f = Symbol.for("foo");
+
+ let a = new Set();
+ a.add([1, 2, 3]);
+ a.add("hello");
+ a.add({ a: 1 });
+ a.add(89);
+
+ let b = new Set();
+ b.add(89);
+ b.add({ a: 1 });
+ b.add("hello");
+ b.add([1, 2, 3]);
+
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+ expect(b).toEqual(b);
+
+ let obj = {};
+ var c = new Set();
+ obj.c = c;
+ obj.x = obj;
+ c.add(obj);
+ expect(obj).toEqual(obj);
+
+ let o1 = { a: new Set() };
+ o1.a.add(o1);
+ expect(o1).toEqual(o1);
+
+ let o2 = new Set();
+ let o3 = {};
+ o3.x = o3;
+ o2.add(o3);
+ expect(o2).toEqual(o2);
+
+ var d = new Date();
+ var e = new Date(d);
+ e[f] = "hello";
+
+ expect(d).toEqual(e);
+ expect(e).toEqual(d);
+
+ class Date2 extends Date {
+ constructor() {
+ super(...arguments);
+ }
+ }
+
+ class Date3 extends Date2 {
+ constructor() {
+ super(...arguments);
+ }
+ }
+
+ let d2 = new Date2();
+ let e2 = new Date(d2);
+ d2[f] = "hello";
+ expect(d2).toEqual(e2);
+ expect(e2).toEqual(d2);
+
+ let d3 = new Date3();
+ let e3 = new Date(d3);
+ d3[f] = "hello";
+ expect(d3).toEqual(e3);
+ expect(e3).toEqual(d3);
+
+ let d4 = new Date();
+ let e4 = new Date3(d4);
+ d4[f] = "hello";
+ expect(d4).toEqual(e4);
+ expect(e4).toEqual(d4);
+
+ let d5 = new Date2();
+ let e5 = new Date3(d5);
+ d5[f] = "hello";
+ expect(d5).toEqual(e5);
+ expect(e5).toEqual(d5);
+
+ expect(new String("a")).not.toEqual(new String("b"));
+
+ var s1 = new String("a");
+ var s2 = new String("a");
+ s1[f] = "hello";
+ expect(s1).toEqual(s2);
+
+ class String2 extends String {
+ constructor() {
+ super(...arguments);
+ }
+ }
+
+ class String3 extends String2 {
+ constructor() {
+ super(...arguments);
+ }
+ }
+
+ let string4 = {};
+ string4.__proto__ = String3.prototype;
+
+ var s3 = new String2("a");
+ var s4 = new String2("a");
+ s3[f] = "hello";
+ expect(s3).toEqual(s4);
+
+ var s5 = new String("a");
+ var s6 = new String3("a");
+ expect(s6).not.toEqual(s5);
+ expect(s5).not.toEqual(s6);
+
+ var s7 = new String2("a");
+ var s8 = new String3("a");
+ expect(s7).not.toEqual(s8);
+ expect(s8).not.toEqual(s7);
+
+ var s9 = new String2("a");
+ var s10 = new string4.constructor("a");
+ expect(s9).not.toEqual(s10);
+ expect(s10).not.toEqual(s9);
+
+ class F2 extends Function {}
+ class F3 extends F2 {}
+
+ var f1 = new Function();
+ var f2 = new F2();
+ var f3 = new F3();
+ expect(f1).not.toEqual(f2);
+ expect(f2).not.toEqual(f1);
+ expect(f2).not.toEqual(f3);
+ expect(f3).not.toEqual(f2);
+});
+
+describe("deepEquals with asymmetric matchers", () => {
+ it("should accept any string", () => {
+ expect({ name: "alice" }).toEqual({ name: expect.any(String) });
+ expect({ name: "bob" }).toEqual({ name: expect.any(String) });
+ expect({ name: "charlie" }).toEqual({ name: expect.any(String) });
+ });
+
+ it("should accept any number", () => {
+ expect({ age: 42 }).toEqual({ age: expect.any(Number) });
+ expect({ age: 69 }).toEqual({ age: expect.any(Number) });
+ expect({ age: 73 }).toEqual({ age: expect.any(Number) });
+ });
+
+ it("should accept any boolean", () => {
+ expect({ active: false }).toEqual({ active: expect.any(Boolean) });
+ expect({ active: true }).toEqual({ active: expect.any(Boolean) });
+ });
+
+ it("should not match the wrong constructors", () => {
+ function f() {
+ return 32;
+ }
+ Object.defineProperty(f, "name", { value: "String" });
+ expect({ a: "123" }).toEqual({ a: expect.any(String) });
+ expect({ a: "123" }).not.toEqual({ a: expect.any(f) });
+
+ function g() {
+ return 32;
+ }
+ Object.defineProperty(g, "name", { value: "BigInt" });
+ expect({ a: 123n }).toEqual({ a: expect.any(BigInt) });
+ expect({ a: 123n }).not.toEqual({ a: expect.any(g) });
+ });
+});
+
test("toThrow", () => {
expect(() => {
throw new Error("hello");