diff options
author | 2023-06-09 19:26:36 -0700 | |
---|---|---|
committer | 2023-06-09 19:26:36 -0700 | |
commit | 76cf465cc2e87c400b6bea56cad1f17f94b91da2 (patch) | |
tree | d72518cf461407fb3c0b88d8e108057f57c8b5b6 /src/bun.js/bindings/bindings.cpp | |
parent | 0f018ea2159f7bad499d8a2f50837a4d888b2344 (diff) | |
download | bun-76cf465cc2e87c400b6bea56cad1f17f94b91da2.tar.gz bun-76cf465cc2e87c400b6bea56cad1f17f94b91da2.tar.zst bun-76cf465cc2e87c400b6bea56cad1f17f94b91da2.zip |
`toMatchObject` and some asymmetric matchers (#3260)
* `toMatchObject` progress
* add `expect.stringContaining()`
* add `expect.stringMatching()`
* print asymmetric matchers
* cleanup
* return before printing if constructor value isn't there
* move matcher logic to cpp
* pretty format and tests
* fix formatting for snapshots
* format `stringContaining` and `stringMatching` like jest
* better test
* remove commented tests
* remove old property matcher code
* add types
* make sure all props are matched in arrays
* add `Bun.deepMatch`
Diffstat (limited to 'src/bun.js/bindings/bindings.cpp')
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 181 |
1 files changed, 179 insertions, 2 deletions
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 1e6da1e71..8aa01abb3 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -721,6 +721,175 @@ 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; +} + +bool Bun__deepMatch(JSValue objValue, JSValue subsetValue, JSGlobalObject* globalObject, ThrowScope* throwScope, bool replacePropsWithAsymmetricMatchers) +{ + VM& vm = globalObject->vm(); + JSObject* obj = objValue.getObject(); + JSObject* subsetObj = subsetValue.getObject(); + + PropertyNameArray subsetProps(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include); + subsetObj->getPropertyNames(globalObject, subsetProps, DontEnumPropertiesMode::Exclude); + + // TODO: add fast paths for: + // - two "simple" objects (using ->forEachProperty in both) + // - two "simple" arrays + // similar to what is done in deepEquals (canPerformFastPropertyEnumerationForIterationBun) + + // arrays should match exactly + if (isArray(globalObject, objValue) && isArray(globalObject, subsetValue)) { + if (obj->getArrayLength() != subsetObj->getArrayLength()) { + return false; + } + PropertyNameArray objProps(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include); + obj->getPropertyNames(globalObject, objProps, DontEnumPropertiesMode::Exclude); + if (objProps.size() != subsetProps.size()) { + return false; + } + } + + for (size_t i = 0; i < subsetProps.size(); i++) { + JSValue prop = obj->getIfPropertyExists(globalObject, subsetProps[i]); + RETURN_IF_EXCEPTION(*throwScope, false); + + if (prop.isEmpty()) { + return false; + } + + JSValue subsetProp = subsetObj->get(globalObject, subsetProps[i]); + RETURN_IF_EXCEPTION(*throwScope, false); + + 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); + } + // 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); + } + // 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)) { + return false; + } + } else { + if (!sameValue(globalObject, prop, subsetProp)) { + return false; + } + } + } + + return true; +} + extern "C" { bool WebCore__FetchHeaders__isEmpty(WebCore__FetchHeaders* arg0) @@ -753,8 +922,6 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__cast_(JSC__JSValue JSValue0, JSC__ return WebCoreCast<WebCore::JSFetchHeaders, WebCore__FetchHeaders>(JSValue0); } -using namespace WebCore; - WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject* lexicalGlobalObject, JSC__JSValue argument0_) { Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); @@ -1458,6 +1625,16 @@ bool JSC__JSValue__strictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1 return Bun__deepEquals<true>(globalObject, v1, v2, stack, &scope, true); } +bool JSC__JSValue__deepMatch(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(obj, subset, globalObject, &scope, replacePropsWithAsymmetricMatchers); +} + // This is the same as the C API version, except it returns a JSValue which may be a *Exception // We want that so we can return stack traces. JSC__JSValue JSObjectCallAsFunctionReturnValue(JSContextRef ctx, JSObjectRef object, |