aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/bindings.cpp
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-06-09 19:26:36 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-09 19:26:36 -0700
commit76cf465cc2e87c400b6bea56cad1f17f94b91da2 (patch)
treed72518cf461407fb3c0b88d8e108057f57c8b5b6 /src/bun.js/bindings/bindings.cpp
parent0f018ea2159f7bad499d8a2f50837a4d888b2344 (diff)
downloadbun-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.cpp181
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,