diff options
author | 2023-06-20 19:06:58 -0700 | |
---|---|---|
committer | 2023-06-20 19:06:58 -0700 | |
commit | 50064352346f48da74f66caca9d30b81a77c89b9 (patch) | |
tree | be2738fbad1b0d0208daa18cb40d247b41c4d0dd /src | |
parent | adb451eec6b8286a4ee18b16b5b87644d5ef3020 (diff) | |
download | bun-50064352346f48da74f66caca9d30b81a77c89b9.tar.gz bun-50064352346f48da74f66caca9d30b81a77c89b9.tar.zst bun-50064352346f48da74f66caca9d30b81a77c89b9.zip |
enable asymmetric matchers in `expect.toEqual`, `expect.toStrictEqual`, and `expect.toHaveProperty` (#3367)
* add asymmetric matchers to `deepEquals`
* fix comparison of a few jstypes
* clean up and tests
* fix merge
* improve `expect.any` for primitives
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 10 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 347 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 28 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-handwritten.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 3 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 14 |
7 files changed, 261 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; |