diff options
author | 2023-06-09 19:26:36 -0700 | |
---|---|---|
committer | 2023-06-09 19:26:36 -0700 | |
commit | 76cf465cc2e87c400b6bea56cad1f17f94b91da2 (patch) | |
tree | d72518cf461407fb3c0b88d8e108057f57c8b5b6 | |
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`
22 files changed, 1659 insertions, 117 deletions
diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index 06d7b7c17..8c429d6fc 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -400,6 +400,9 @@ declare module "bun:test" { any: ( constructor: ((..._: any[]) => any) | { new (..._: any[]): any }, ) => Expect; + anything: () => Expect; + stringContaining: (str: string) => Expect; + stringMatching: (regex: RegExp | string) => Expect; }; /** * Asserts that a value matches some criteria. @@ -690,6 +693,16 @@ declare module "bun:test" { */ toMatchSnapshot(propertyMatchers?: Object, hint?: string): void; /** + * Asserts that an object matches a subset of properties. + * + * @example + * expect({ a: 1, b: 2 }).toMatchObject({ b: 2 }); + * expect({ c: new Date(), d: 2 }).toMatchObject({ d: 2 }); + * + * @param subset Subset of properties to match with. + */ + toMatchObject(subset: Object): void; + /** * Asserts that a value is empty. * * @example diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index d7d9ffd09..6987a9226 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -745,6 +745,14 @@ declare module "bun" { ): boolean; /** + * Returns true if all properties in the subset exist in the + * other and have equal values. + * + * This also powers expect().toMatchObject in `bun:test` + */ + export function deepMatch(subset: unknown, a: unknown): boolean; + + /** * tsconfig.json options supported by Bun */ interface TSConfig { diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h index f59e0e855..b16febcdb 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h @@ -5,6 +5,9 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBuildMessageConstructo std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCryptoHasherConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDirent; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDirentConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpect; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectAny; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectAnything; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectStringContaining; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExpectStringMatching; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouter; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemRouterConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForListener; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4; diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h index 3e80b933b..59263e62c 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h @@ -5,6 +5,9 @@ std::unique_ptr<IsoSubspace> m_subspaceForBuildMessageConstructor;std::unique_pt std::unique_ptr<IsoSubspace> m_subspaceForCryptoHasherConstructor;std::unique_ptr<IsoSubspace> m_subspaceForDirent; std::unique_ptr<IsoSubspace> m_subspaceForDirentConstructor;std::unique_ptr<IsoSubspace> m_subspaceForExpect; std::unique_ptr<IsoSubspace> m_subspaceForExpectConstructor;std::unique_ptr<IsoSubspace> m_subspaceForExpectAny; +std::unique_ptr<IsoSubspace> m_subspaceForExpectAnything; +std::unique_ptr<IsoSubspace> m_subspaceForExpectStringContaining; +std::unique_ptr<IsoSubspace> m_subspaceForExpectStringMatching; std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouter; std::unique_ptr<IsoSubspace> m_subspaceForFileSystemRouterConstructor;std::unique_ptr<IsoSubspace> m_subspaceForListener; std::unique_ptr<IsoSubspace> m_subspaceForMD4; diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h index 95a787f5e..4471fbab3 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h @@ -40,6 +40,24 @@ JSC::Structure* JSExpectAnyStructure() { return m_JSExpectAny.getInitializedOnMa JSC::LazyClassStructure m_JSExpectAny; bool hasJSExpectAnySetterValue { false }; mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectAnySetterValue; +JSC::Structure* JSExpectAnythingStructure() { return m_JSExpectAnything.getInitializedOnMainThread(this); } + JSC::JSObject* JSExpectAnythingConstructor() { return m_JSExpectAnything.constructorInitializedOnMainThread(this); } + JSC::JSValue JSExpectAnythingPrototype() { return m_JSExpectAnything.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSExpectAnything; + bool hasJSExpectAnythingSetterValue { false }; + mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectAnythingSetterValue; +JSC::Structure* JSExpectStringContainingStructure() { return m_JSExpectStringContaining.getInitializedOnMainThread(this); } + JSC::JSObject* JSExpectStringContainingConstructor() { return m_JSExpectStringContaining.constructorInitializedOnMainThread(this); } + JSC::JSValue JSExpectStringContainingPrototype() { return m_JSExpectStringContaining.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSExpectStringContaining; + bool hasJSExpectStringContainingSetterValue { false }; + mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectStringContainingSetterValue; +JSC::Structure* JSExpectStringMatchingStructure() { return m_JSExpectStringMatching.getInitializedOnMainThread(this); } + JSC::JSObject* JSExpectStringMatchingConstructor() { return m_JSExpectStringMatching.constructorInitializedOnMainThread(this); } + JSC::JSValue JSExpectStringMatchingPrototype() { return m_JSExpectStringMatching.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSExpectStringMatching; + bool hasJSExpectStringMatchingSetterValue { false }; + mutable JSC::WriteBarrier<JSC::Unknown> m_JSExpectStringMatchingSetterValue; JSC::Structure* JSFileSystemRouterStructure() { return m_JSFileSystemRouter.getInitializedOnMainThread(this); } JSC::JSObject* JSFileSystemRouterConstructor() { return m_JSFileSystemRouter.constructorInitializedOnMainThread(this); } JSC::JSValue JSFileSystemRouterPrototype() { return m_JSFileSystemRouter.prototypeInitializedOnMainThread(this); } diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h index 8756ed660..4e5a2c1fa 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h @@ -41,6 +41,24 @@ void GlobalObject::initGeneratedLazyClasses() { init.setStructure(WebCore::JSExpectAny::createStructure(init.vm, init.global, init.prototype)); }); + m_JSExpectAnything.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSExpectAnything::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); + init.setStructure(WebCore::JSExpectAnything::createStructure(init.vm, init.global, init.prototype)); + + }); + m_JSExpectStringContaining.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSExpectStringContaining::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); + init.setStructure(WebCore::JSExpectStringContaining::createStructure(init.vm, init.global, init.prototype)); + + }); + m_JSExpectStringMatching.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSExpectStringMatching::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); + init.setStructure(WebCore::JSExpectStringMatching::createStructure(init.vm, init.global, init.prototype)); + + }); m_JSFileSystemRouter.initLater( [](LazyClassStructure::Initializer& init) { init.setPrototype(WebCore::JSFileSystemRouter::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); @@ -190,6 +208,9 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& thisObject->m_JSDirent.visit(visitor); visitor.append(thisObject->m_JSDirentSetterValue); thisObject->m_JSExpect.visit(visitor); visitor.append(thisObject->m_JSExpectSetterValue); thisObject->m_JSExpectAny.visit(visitor); visitor.append(thisObject->m_JSExpectAnySetterValue); + thisObject->m_JSExpectAnything.visit(visitor); visitor.append(thisObject->m_JSExpectAnythingSetterValue); + thisObject->m_JSExpectStringContaining.visit(visitor); visitor.append(thisObject->m_JSExpectStringContainingSetterValue); + thisObject->m_JSExpectStringMatching.visit(visitor); visitor.append(thisObject->m_JSExpectStringMatchingSetterValue); thisObject->m_JSFileSystemRouter.visit(visitor); visitor.append(thisObject->m_JSFileSystemRouterSetterValue); thisObject->m_JSListener.visit(visitor); visitor.append(thisObject->m_JSListenerSetterValue); thisObject->m_JSMD4.visit(visitor); visitor.append(thisObject->m_JSMD4SetterValue); diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 41d9cc888..b98c122ee 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -4831,6 +4831,463 @@ void JSExpectAny::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor) } DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpectAny); +class JSExpectAnythingPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSExpectAnythingPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSExpectAnythingPrototype* ptr = new (NotNull, JSC::allocateCell<JSExpectAnythingPrototype>(vm)) JSExpectAnythingPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSExpectAnythingPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +extern "C" void ExpectAnythingClass__finalize(void*); +extern "C" JSC_DECLARE_HOST_FUNCTION(ExpectAnythingClass__call); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSExpectAnythingPrototype, JSExpectAnythingPrototype::Base); + +static const HashTableValue JSExpectAnythingPrototypeTableValues[] = {}; + +const ClassInfo JSExpectAnythingPrototype::s_info = { "ExpectAnything"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectAnythingPrototype) }; + +void JSExpectAnythingPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSExpectAnything::~JSExpectAnything() +{ + if (m_ctx) { + ExpectAnythingClass__finalize(m_ctx); + } +} +void JSExpectAnything::destroy(JSCell* cell) +{ + static_cast<JSExpectAnything*>(cell)->JSExpectAnything::~JSExpectAnything(); +} + +const ClassInfo JSExpectAnything::s_info = { "ExpectAnything"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectAnything) }; + +void JSExpectAnything::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSExpectAnything* JSExpectAnything::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSExpectAnything* ptr = new (NotNull, JSC::allocateCell<JSExpectAnything>(vm)) JSExpectAnything(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* ExpectAnything__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSExpectAnything* object = JSC::jsDynamicCast<JSExpectAnything*>(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool ExpectAnything__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSExpectAnything* object = JSC::jsDynamicCast<JSExpectAnything*>(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t ExpectAnything__ptrOffset = JSExpectAnything::offsetOfWrapped(); + +void JSExpectAnything::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSExpectAnything*>(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSExpectAnything::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSExpectAnythingPrototype::create(vm, globalObject, JSExpectAnythingPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue ExpectAnything__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSExpectAnythingStructure(); + JSExpectAnything* instance = JSExpectAnything::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} +class JSExpectStringContainingPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSExpectStringContainingPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSExpectStringContainingPrototype* ptr = new (NotNull, JSC::allocateCell<JSExpectStringContainingPrototype>(vm)) JSExpectStringContainingPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSExpectStringContainingPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +extern "C" void ExpectStringContainingClass__finalize(void*); +extern "C" JSC_DECLARE_HOST_FUNCTION(ExpectStringContainingClass__call); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSExpectStringContainingPrototype, JSExpectStringContainingPrototype::Base); + +static const HashTableValue JSExpectStringContainingPrototypeTableValues[] = {}; + +const ClassInfo JSExpectStringContainingPrototype::s_info = { "ExpectStringContaining"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectStringContainingPrototype) }; + +extern "C" void ExpectStringContainingPrototype__stringValueSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSExpectStringContaining*>(JSValue::decode(thisValue)); + thisObject->m_stringValue.set(vm, thisObject, JSValue::decode(value)); +} + +extern "C" EncodedJSValue ExpectStringContainingPrototype__stringValueGetCachedValue(JSC::EncodedJSValue thisValue) +{ + auto* thisObject = jsCast<JSExpectStringContaining*>(JSValue::decode(thisValue)); + return JSValue::encode(thisObject->m_stringValue.get()); +} + +void JSExpectStringContainingPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSExpectStringContaining::~JSExpectStringContaining() +{ + if (m_ctx) { + ExpectStringContainingClass__finalize(m_ctx); + } +} +void JSExpectStringContaining::destroy(JSCell* cell) +{ + static_cast<JSExpectStringContaining*>(cell)->JSExpectStringContaining::~JSExpectStringContaining(); +} + +const ClassInfo JSExpectStringContaining::s_info = { "ExpectStringContaining"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectStringContaining) }; + +void JSExpectStringContaining::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSExpectStringContaining* JSExpectStringContaining::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSExpectStringContaining* ptr = new (NotNull, JSC::allocateCell<JSExpectStringContaining>(vm)) JSExpectStringContaining(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* ExpectStringContaining__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSExpectStringContaining* object = JSC::jsDynamicCast<JSExpectStringContaining*>(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool ExpectStringContaining__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSExpectStringContaining* object = JSC::jsDynamicCast<JSExpectStringContaining*>(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t ExpectStringContaining__ptrOffset = JSExpectStringContaining::offsetOfWrapped(); + +void JSExpectStringContaining::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSExpectStringContaining*>(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSExpectStringContaining::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSExpectStringContainingPrototype::create(vm, globalObject, JSExpectStringContainingPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue ExpectStringContaining__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSExpectStringContainingStructure(); + JSExpectStringContaining* instance = JSExpectStringContaining::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} + +template<typename Visitor> +void JSExpectStringContaining::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSExpectStringContaining* thisObject = jsCast<JSExpectStringContaining*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_stringValue); +} + +DEFINE_VISIT_CHILDREN(JSExpectStringContaining); + +template<typename Visitor> +void JSExpectStringContaining::visitAdditionalChildren(Visitor& visitor) +{ + JSExpectStringContaining* thisObject = this; + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + visitor.append(thisObject->m_stringValue); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSExpectStringContaining); + +template<typename Visitor> +void JSExpectStringContaining::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor) +{ + JSExpectStringContaining* thisObject = jsCast<JSExpectStringContaining*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + thisObject->visitAdditionalChildren<Visitor>(visitor); +} + +DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpectStringContaining); +class JSExpectStringMatchingPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSExpectStringMatchingPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSExpectStringMatchingPrototype* ptr = new (NotNull, JSC::allocateCell<JSExpectStringMatchingPrototype>(vm)) JSExpectStringMatchingPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSExpectStringMatchingPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +extern "C" void ExpectStringMatchingClass__finalize(void*); +extern "C" JSC_DECLARE_HOST_FUNCTION(ExpectStringMatchingClass__call); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSExpectStringMatchingPrototype, JSExpectStringMatchingPrototype::Base); + +static const HashTableValue JSExpectStringMatchingPrototypeTableValues[] = {}; + +const ClassInfo JSExpectStringMatchingPrototype::s_info = { "ExpectStringMatching"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectStringMatchingPrototype) }; + +extern "C" void ExpectStringMatchingPrototype__testValueSetCachedValue(JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast<JSExpectStringMatching*>(JSValue::decode(thisValue)); + thisObject->m_testValue.set(vm, thisObject, JSValue::decode(value)); +} + +extern "C" EncodedJSValue ExpectStringMatchingPrototype__testValueGetCachedValue(JSC::EncodedJSValue thisValue) +{ + auto* thisObject = jsCast<JSExpectStringMatching*>(JSValue::decode(thisValue)); + return JSValue::encode(thisObject->m_testValue.get()); +} + +void JSExpectStringMatchingPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSExpectStringMatching::~JSExpectStringMatching() +{ + if (m_ctx) { + ExpectStringMatchingClass__finalize(m_ctx); + } +} +void JSExpectStringMatching::destroy(JSCell* cell) +{ + static_cast<JSExpectStringMatching*>(cell)->JSExpectStringMatching::~JSExpectStringMatching(); +} + +const ClassInfo JSExpectStringMatching::s_info = { "ExpectStringMatching"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSExpectStringMatching) }; + +void JSExpectStringMatching::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSExpectStringMatching* JSExpectStringMatching::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSExpectStringMatching* ptr = new (NotNull, JSC::allocateCell<JSExpectStringMatching>(vm)) JSExpectStringMatching(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* ExpectStringMatching__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSExpectStringMatching* object = JSC::jsDynamicCast<JSExpectStringMatching*>(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool ExpectStringMatching__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSExpectStringMatching* object = JSC::jsDynamicCast<JSExpectStringMatching*>(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t ExpectStringMatching__ptrOffset = JSExpectStringMatching::offsetOfWrapped(); + +void JSExpectStringMatching::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSExpectStringMatching*>(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSExpectStringMatching::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSExpectStringMatchingPrototype::create(vm, globalObject, JSExpectStringMatchingPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue ExpectStringMatching__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSExpectStringMatchingStructure(); + JSExpectStringMatching* instance = JSExpectStringMatching::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} + +template<typename Visitor> +void JSExpectStringMatching::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSExpectStringMatching* thisObject = jsCast<JSExpectStringMatching*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_testValue); +} + +DEFINE_VISIT_CHILDREN(JSExpectStringMatching); + +template<typename Visitor> +void JSExpectStringMatching::visitAdditionalChildren(Visitor& visitor) +{ + JSExpectStringMatching* thisObject = this; + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + visitor.append(thisObject->m_testValue); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSExpectStringMatching); + +template<typename Visitor> +void JSExpectStringMatching::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor) +{ + JSExpectStringMatching* thisObject = jsCast<JSExpectStringMatching*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + thisObject->visitAdditionalChildren<Visitor>(visitor); +} + +DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSExpectStringMatching); class JSFileSystemRouterPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index cf5446a1a..668cd3f6b 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -416,6 +416,168 @@ public: mutable JSC::WriteBarrier<JSC::Unknown> m_constructorValue; }; +class JSExpectAnything final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static JSExpectAnything* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSExpectAnything, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForExpectAnything.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForExpectAnything = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForExpectAnything.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForExpectAnything = std::forward<decltype(space)>(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + ; + + ~JSExpectAnything(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSExpectAnything, m_ctx); } + + void* m_ctx { nullptr }; + + JSExpectAnything(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + } + + void finishCreation(JSC::VM&); +}; + +class JSExpectStringContaining final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static JSExpectStringContaining* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSExpectStringContaining, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForExpectStringContaining.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForExpectStringContaining = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForExpectStringContaining.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForExpectStringContaining = std::forward<decltype(space)>(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + ; + + ~JSExpectStringContaining(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSExpectStringContaining, m_ctx); } + + void* m_ctx { nullptr }; + + JSExpectStringContaining(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + } + + void finishCreation(JSC::VM&); + + DECLARE_VISIT_CHILDREN; + template<typename Visitor> void visitAdditionalChildren(Visitor&); + DECLARE_VISIT_OUTPUT_CONSTRAINTS; + + mutable JSC::WriteBarrier<JSC::Unknown> m_stringValue; +}; + +class JSExpectStringMatching final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static JSExpectStringMatching* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSExpectStringMatching, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForExpectStringMatching.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForExpectStringMatching = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForExpectStringMatching.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForExpectStringMatching = std::forward<decltype(space)>(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + ; + + ~JSExpectStringMatching(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSExpectStringMatching, m_ctx); } + + void* m_ctx { nullptr }; + + JSExpectStringMatching(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + } + + void finishCreation(JSC::VM&); + + DECLARE_VISIT_CHILDREN; + template<typename Visitor> void visitAdditionalChildren(Visitor&); + DECLARE_VISIT_OUTPUT_CONSTRAINTS; + + mutable JSC::WriteBarrier<JSC::Unknown> m_testValue; +}; + class JSFileSystemRouter final : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index f2d0e248e..f31a3c1cc 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2020,6 +2020,35 @@ JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (JSGlobalObject * globalObject, } } +JSC_DECLARE_HOST_FUNCTION(functionBunDeepMatch); + +JSC_DEFINE_HOST_FUNCTION(functionBunDeepMatch, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* global = reinterpret_cast<GlobalObject*>(globalObject); + JSC::VM& vm = global->vm(); + + auto scope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() < 2) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwTypeError(globalObject, throwScope, "Expected 2 values to compare"_s); + return JSValue::encode(jsUndefined()); + } + + JSC::JSValue subset = callFrame->uncheckedArgument(0); + JSC::JSValue object = callFrame->uncheckedArgument(1); + + if (!subset.isObject() || !object.isObject()) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwTypeError(globalObject, throwScope, "Expected 2 object to match"_s); + return JSValue::encode(jsUndefined()); + } + + bool isEqual = Bun__deepMatch(object, subset, globalObject, &scope, false); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsBoolean(isEqual)); +} + JSC_DECLARE_HOST_FUNCTION(functionBunNanoseconds); JSC_DEFINE_HOST_FUNCTION(functionBunNanoseconds, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) @@ -3668,6 +3697,12 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm } { + JSC::Identifier identifier = JSC::Identifier::fromString(vm, "deepMatch"_s); + object->putDirectNativeFunction(vm, this, identifier, 2, functionBunDeepMatch, ImplementationVisibility::Public, NoIntrinsic, + JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0); + } + + { JSC::Identifier identifier = JSC::Identifier::fromString(vm, "version"_s); object->putDirect(vm, PropertyName(identifier), JSC::jsOwnedString(vm, makeString(Bun__version + 1)), 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, diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 634ec9f39..09902adb9 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3970,6 +3970,8 @@ pub const JSValue = enum(JSValueReprInt) { return cppFn("toZigString", .{ this, out, global }); } + /// this: RegExp value + /// other: string value pub fn toMatch(this: JSValue, global: *JSGlobalObject, other: JSValue) bool { return cppFn("toMatch", .{ this, global, other }); } @@ -4338,6 +4340,10 @@ pub const JSValue = enum(JSValueReprInt) { return cppFn("strictDeepEquals", .{ 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 }); + } + pub const DiffMethod = enum(u8) { none, character, @@ -4697,6 +4703,7 @@ pub const JSValue = enum(JSValueReprInt) { "isConstructor", "isInstanceOf", "stringIncludes", + "deepMatch", }; }; @@ -5034,6 +5041,10 @@ pub const CallFrame = opaque { .len = i, }; } + + pub inline fn slice(self: @This()) []const JSValue { + return self.ptr[0..self.len]; + } }; } diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index dc56a07af..e6bc953da 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -2219,6 +2219,43 @@ pub const ZigConsoleClient = struct { } else if (value.as(JSC.ResolveMessage)) |resolve_log| { resolve_log.msg.writeFormat(writer_, enable_ansi_colors) catch {}; return; + } else if (value.as(JSC.Jest.ExpectAnything) != null) { + writer.writeAll("Anything"); + return; + } else if (value.as(JSC.Jest.ExpectAny) != null) { + const constructor_value = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse return; + + this.addForNewLine("Any<".len); + writer.writeAll("Any<"); + var class_name = ZigString.init(&name_buf); + + constructor_value.getClassName(this.globalThis, &class_name); + this.addForNewLine(class_name.len); + writer.print(comptime Output.prettyFmt("<cyan>{}<r>", enable_ansi_colors), .{class_name}); + this.addForNewLine(1); + writer.writeAll(">"); + + return; + } else if (value.as(JSC.Jest.ExpectStringContaining) != null) { + const substring_value = JSC.Jest.ExpectStringContaining.stringValueGetCached(value) orelse return; + + this.addForNewLine("StringContaining ".len); + writer.writeAll("StringContaining "); + this.printAs(.String, Writer, writer_, substring_value, .String, enable_ansi_colors); + + return; + } else if (value.as(JSC.Jest.ExpectStringMatching) != null) { + const test_value = JSC.Jest.ExpectStringMatching.testValueGetCached(value) orelse return; + + this.addForNewLine("StringMatching ".len); + writer.writeAll("StringMatching "); + + const original_quote_strings = this.quote_strings; + if (test_value.isRegExp()) this.quote_strings = false; + this.printAs(.String, Writer, writer_, test_value, .String, enable_ansi_colors); + this.quote_strings = original_quote_strings; + + return; } else if (jsType != .DOMWrapper) { if (value.isCallable(this.globalThis.vm())) { return this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors); diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index a4bbd2cab..b98d59cd3 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -1176,6 +1176,227 @@ pub const JSExpectAny = struct { } } }; +pub const JSExpectAnything = struct { + const ExpectAnything = Classes.ExpectAnything; + const GetterType = fn (*ExpectAnything, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*ExpectAnything, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*ExpectAnything, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*ExpectAnything, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*ExpectAnything, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*ExpectAnything { + JSC.markBinding(@src()); + return ExpectAnything__fromJS(value); + } + + /// Create a new instance of ExpectAnything + pub fn toJS(this: *ExpectAnything, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = ExpectAnything__create(globalObject, this); + std.debug.assert(value__.as(ExpectAnything).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return ExpectAnything__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of ExpectAnything. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*ExpectAnything) bool { + JSC.markBinding(@src()); + return ExpectAnything__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *ExpectAnything, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(ExpectAnything__dangerouslySetPtr(value, null)); + } + + extern fn ExpectAnything__fromJS(JSC.JSValue) ?*ExpectAnything; + extern fn ExpectAnything__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn ExpectAnything__create(globalObject: *JSC.JSGlobalObject, ptr: ?*ExpectAnything) JSC.JSValue; + + extern fn ExpectAnything__dangerouslySetPtr(JSC.JSValue, ?*ExpectAnything) bool; + + comptime { + if (@TypeOf(ExpectAnything.finalize) != (fn (*ExpectAnything) callconv(.C) void)) { + @compileLog("ExpectAnything.finalize is not a finalizer"); + } + + if (@TypeOf(ExpectAnything.call) != StaticCallbackType) + @compileLog("Expected ExpectAnything.call to be a static callback"); + if (!JSC.is_bindgen) { + @export(ExpectAnything.call, .{ .name = "ExpectAnythingClass__call" }); + @export(ExpectAnything.finalize, .{ .name = "ExpectAnythingClass__finalize" }); + } + } +}; +pub const JSExpectStringContaining = struct { + const ExpectStringContaining = Classes.ExpectStringContaining; + const GetterType = fn (*ExpectStringContaining, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*ExpectStringContaining, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*ExpectStringContaining, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*ExpectStringContaining, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*ExpectStringContaining, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*ExpectStringContaining { + JSC.markBinding(@src()); + return ExpectStringContaining__fromJS(value); + } + + extern fn ExpectStringContainingPrototype__stringValueSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + extern fn ExpectStringContainingPrototype__stringValueGetCachedValue(JSC.JSValue) JSC.JSValue; + + /// `ExpectStringContaining.stringValue` setter + /// This value will be visited by the garbage collector. + pub fn stringValueSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + JSC.markBinding(@src()); + ExpectStringContainingPrototype__stringValueSetCachedValue(thisValue, globalObject, value); + } + + /// `ExpectStringContaining.stringValue` getter + /// This value will be visited by the garbage collector. + pub fn stringValueGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { + JSC.markBinding(@src()); + const result = ExpectStringContainingPrototype__stringValueGetCachedValue(thisValue); + if (result == .zero) + return null; + + return result; + } + + /// Create a new instance of ExpectStringContaining + pub fn toJS(this: *ExpectStringContaining, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = ExpectStringContaining__create(globalObject, this); + std.debug.assert(value__.as(ExpectStringContaining).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return ExpectStringContaining__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of ExpectStringContaining. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*ExpectStringContaining) bool { + JSC.markBinding(@src()); + return ExpectStringContaining__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *ExpectStringContaining, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(ExpectStringContaining__dangerouslySetPtr(value, null)); + } + + extern fn ExpectStringContaining__fromJS(JSC.JSValue) ?*ExpectStringContaining; + extern fn ExpectStringContaining__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn ExpectStringContaining__create(globalObject: *JSC.JSGlobalObject, ptr: ?*ExpectStringContaining) JSC.JSValue; + + extern fn ExpectStringContaining__dangerouslySetPtr(JSC.JSValue, ?*ExpectStringContaining) bool; + + comptime { + if (@TypeOf(ExpectStringContaining.finalize) != (fn (*ExpectStringContaining) callconv(.C) void)) { + @compileLog("ExpectStringContaining.finalize is not a finalizer"); + } + + if (@TypeOf(ExpectStringContaining.call) != StaticCallbackType) + @compileLog("Expected ExpectStringContaining.call to be a static callback"); + if (!JSC.is_bindgen) { + @export(ExpectStringContaining.call, .{ .name = "ExpectStringContainingClass__call" }); + @export(ExpectStringContaining.finalize, .{ .name = "ExpectStringContainingClass__finalize" }); + } + } +}; +pub const JSExpectStringMatching = struct { + const ExpectStringMatching = Classes.ExpectStringMatching; + const GetterType = fn (*ExpectStringMatching, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*ExpectStringMatching, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*ExpectStringMatching, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*ExpectStringMatching, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*ExpectStringMatching, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*ExpectStringMatching { + JSC.markBinding(@src()); + return ExpectStringMatching__fromJS(value); + } + + extern fn ExpectStringMatchingPrototype__testValueSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) void; + + extern fn ExpectStringMatchingPrototype__testValueGetCachedValue(JSC.JSValue) JSC.JSValue; + + /// `ExpectStringMatching.testValue` setter + /// This value will be visited by the garbage collector. + pub fn testValueSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { + JSC.markBinding(@src()); + ExpectStringMatchingPrototype__testValueSetCachedValue(thisValue, globalObject, value); + } + + /// `ExpectStringMatching.testValue` getter + /// This value will be visited by the garbage collector. + pub fn testValueGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { + JSC.markBinding(@src()); + const result = ExpectStringMatchingPrototype__testValueGetCachedValue(thisValue); + if (result == .zero) + return null; + + return result; + } + + /// Create a new instance of ExpectStringMatching + pub fn toJS(this: *ExpectStringMatching, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = ExpectStringMatching__create(globalObject, this); + std.debug.assert(value__.as(ExpectStringMatching).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return ExpectStringMatching__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of ExpectStringMatching. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*ExpectStringMatching) bool { + JSC.markBinding(@src()); + return ExpectStringMatching__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *ExpectStringMatching, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(ExpectStringMatching__dangerouslySetPtr(value, null)); + } + + extern fn ExpectStringMatching__fromJS(JSC.JSValue) ?*ExpectStringMatching; + extern fn ExpectStringMatching__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn ExpectStringMatching__create(globalObject: *JSC.JSGlobalObject, ptr: ?*ExpectStringMatching) JSC.JSValue; + + extern fn ExpectStringMatching__dangerouslySetPtr(JSC.JSValue, ?*ExpectStringMatching) bool; + + comptime { + if (@TypeOf(ExpectStringMatching.finalize) != (fn (*ExpectStringMatching) callconv(.C) void)) { + @compileLog("ExpectStringMatching.finalize is not a finalizer"); + } + + if (@TypeOf(ExpectStringMatching.call) != StaticCallbackType) + @compileLog("Expected ExpectStringMatching.call to be a static callback"); + if (!JSC.is_bindgen) { + @export(ExpectStringMatching.call, .{ .name = "ExpectStringMatchingClass__call" }); + @export(ExpectStringMatching.finalize, .{ .name = "ExpectStringMatchingClass__finalize" }); + } + } +}; pub const JSFileSystemRouter = struct { const FileSystemRouter = Classes.FileSystemRouter; const GetterType = fn (*FileSystemRouter, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; @@ -4622,6 +4843,9 @@ comptime { _ = JSDirent; _ = JSExpect; _ = JSExpectAny; + _ = JSExpectAnything; + _ = JSExpectStringContaining; + _ = JSExpectStringMatching; _ = JSFileSystemRouter; _ = JSListener; _ = JSMD4; diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index 4acde31e8..d5d987dce 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -6,6 +6,9 @@ pub const Classes = struct { pub const Dirent = JSC.Node.Dirent; pub const Expect = JSC.Jest.Expect; pub const ExpectAny = JSC.Jest.ExpectAny; + pub const ExpectAnything = JSC.Jest.ExpectAnything; + pub const ExpectStringContaining = JSC.Jest.ExpectStringContaining; + pub const ExpectStringMatching = JSC.Jest.ExpectStringMatching; pub const FileSystemRouter = JSC.API.FileSystemRouter; pub const Bundler = JSC.API.JSBundler; pub const JSBundler = Bundler; diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index d925fb4cd..650203653 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -284,6 +284,8 @@ extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, si template<bool isStrict> 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); +bool Bun__deepMatch(JSC::JSValue object, JSC::JSValue subset, JSC::JSGlobalObject* globalObject, JSC::ThrowScope* throwScope, bool replacePropsWithAsymmetricMatchers); + namespace Inspector { class ScriptArguments; } diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 5034b7652..9e9254bb2 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -316,6 +316,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__createStringArray(JSC__JSGlobalObject* arg0, CPP_DECL JSC__JSValue JSC__JSValue__createTypeError(const ZigString* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2); CPP_DECL JSC__JSValue JSC__JSValue__createUninitializedUint8Array(JSC__JSGlobalObject* arg0, size_t arg1); CPP_DECL bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2); +CPP_DECL bool JSC__JSValue__deepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2, bool arg3); CPP_DECL bool JSC__JSValue__eqlCell(JSC__JSValue JSValue0, JSC__JSCell* arg1); CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1); CPP_DECL JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, unsigned char arg2); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index b1f1a1974..7d075e9e6 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -217,6 +217,7 @@ pub extern fn JSC__JSValue__createStringArray(arg0: *bindings.JSGlobalObject, ar pub extern fn JSC__JSValue__createTypeError(arg0: [*c]const ZigString, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSValue__createUninitializedUint8Array(arg0: *bindings.JSGlobalObject, arg1: usize) JSC__JSValue; pub extern fn JSC__JSValue__deepEquals(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject) bool; +pub extern fn JSC__JSValue__deepMatch(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject, arg3: bool) bool; pub extern fn JSC__JSValue__eqlCell(JSValue0: JSC__JSValue, arg1: [*c]bindings.JSCell) bool; pub extern fn JSC__JSValue__eqlValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue) bool; pub extern fn JSC__JSValue__fastGet_(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: u8) JSC__JSValue; diff --git a/src/bun.js/test/jest.classes.ts b/src/bun.js/test/jest.classes.ts index de9f260d2..38eefe778 100644 --- a/src/bun.js/test/jest.classes.ts +++ b/src/bun.js/test/jest.classes.ts @@ -2,6 +2,17 @@ import { define } from "../scripts/class-definitions"; export default [ define({ + name: "ExpectAnything", + construct: false, + noConstructor: true, + call: true, + finalize: true, + JSType: "0b11101110", + configurable: false, + klass: {}, + proto: {}, + }), + define({ name: "ExpectAny", construct: false, noConstructor: true, @@ -14,6 +25,30 @@ export default [ proto: {}, }), define({ + name: "ExpectStringContaining", + construct: false, + noConstructor: true, + call: true, + finalize: true, + JSType: "0b11101110", + values: ["stringValue"], + configurable: false, + klass: {}, + proto: {}, + }), + define({ + name: "ExpectStringMatching", + construct: false, + noConstructor: true, + call: true, + finalize: true, + JSType: "0b11101110", + values: ["testValue"], + configurable: false, + klass: {}, + proto: {}, + }), + define({ name: "Expect", construct: true, call: true, diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index f2e832ff9..93edb8abd 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -36,6 +36,7 @@ const ZigString = JSC.ZigString; const JSInternalPromise = JSC.JSInternalPromise; const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; +const JSType = JSValue.JSType; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; const JSObject = JSC.JSObject; @@ -772,6 +773,108 @@ pub const Jest = struct { } }; +pub const ExpectAnything = struct { + pub usingnamespace JSC.Codegen.JSExpectAnything; + + pub fn finalize( + this: *ExpectAnything, + ) callconv(.C) void { + VirtualMachine.get().allocator.destroy(this); + } + + pub fn call(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + const anything = globalObject.bunVM().allocator.create(ExpectAnything) catch unreachable; + if (Jest.runner.?.pending_test == null) { + const err = globalObject.createErrorInstance("expect.anything() must be called in a test", .{}); + err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject)); + globalObject.throwValue(err); + return .zero; + } + + const anything_js_value = anything.toJS(globalObject); + anything_js_value.ensureStillAlive(); + + var vm = globalObject.bunVM(); + vm.autoGarbageCollect(); + + return anything_js_value; + } +}; + +pub const ExpectStringMatching = struct { + pub usingnamespace JSC.Codegen.JSExpectStringMatching; + + pub fn finalize( + this: *ExpectStringMatching, + ) callconv(.C) void { + VirtualMachine.get().allocator.destroy(this); + } + + pub fn call(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + const args = callFrame.arguments(1).slice(); + + if (args.len == 0 or (!args[0].isString() and !args[0].isRegExp())) { + const fmt = "<d>expect.<r>stringContaining<d>(<r>string<d>)<r>\n\nExpected a string or regular expression\n"; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const test_value = args[0]; + const string_matching = globalObject.bunVM().allocator.create(ExpectStringMatching) catch unreachable; + + if (Jest.runner.?.pending_test == null) { + const err = globalObject.createErrorInstance("expect.stringContaining() must be called in a test", .{}); + err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject)); + globalObject.throwValue(err); + return .zero; + } + + const string_matching_js_value = string_matching.toJS(globalObject); + ExpectStringMatching.testValueSetCached(string_matching_js_value, globalObject, test_value); + + var vm = globalObject.bunVM(); + vm.autoGarbageCollect(); + return string_matching_js_value; + } +}; + +pub const ExpectStringContaining = struct { + pub usingnamespace JSC.Codegen.JSExpectStringContaining; + + pub fn finalize( + this: *ExpectStringContaining, + ) callconv(.C) void { + VirtualMachine.get().allocator.destroy(this); + } + + pub fn call(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + const args = callFrame.arguments(1).slice(); + + if (args.len == 0 or !args[0].isString()) { + const fmt = "<d>expect.<r>stringContaining<d>(<r>string<d>)<r>\n\nExpected a string\n"; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const string_value = args[0]; + + const string_containing = globalObject.bunVM().allocator.create(ExpectStringContaining) catch unreachable; + + if (Jest.runner.?.pending_test == null) { + const err = globalObject.createErrorInstance("expect.stringContaining() must be called in a test", .{}); + err.put(globalObject, ZigString.static("name"), ZigString.init("TestNotRunningError").toValueGC(globalObject)); + globalObject.throwValue(err); + return .zero; + } + + const string_containing_js_value = string_containing.toJS(globalObject); + ExpectStringContaining.stringValueSetCached(string_containing_js_value, globalObject, string_value); + + var vm = globalObject.bunVM(); + vm.autoGarbageCollect(); + return string_containing_js_value; + } +}; pub const ExpectAny = struct { pub usingnamespace JSC.Codegen.JSExpectAny; @@ -810,7 +913,7 @@ pub const ExpectAny = struct { any.* = .{}; const any_js_value = any.toJS(globalObject); any_js_value.ensureStillAlive(); - JSC.Jest.ExpectAny.constructorValueSetCached(any_js_value, globalObject, constructor); + ExpectAny.constructorValueSetCached(any_js_value, globalObject, constructor); any_js_value.ensureStillAlive(); var vm = globalObject.bunVM(); @@ -888,7 +991,7 @@ pub const Expect = struct { pub fn call(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { const arguments_ = callframe.arguments(1); if (arguments_.len < 1) { - globalObject.throw("expect() requires one argument", .{}); + globalObject.throw("expect() requires one argument\n", .{}); return .zero; } const arguments = arguments_.ptr[0..arguments_.len]; @@ -1523,26 +1626,23 @@ pub const Expect = struct { if (pass) return thisValue; // handle failure - const diff_formatter = DiffFormatter{ .received = value, .expected = expected, .globalObject = globalObject, .not = not }; + const diff_formatter = DiffFormatter{ + .received = value, + .expected = expected, + .globalObject = globalObject, + .not = not, + }; if (not) { const signature = comptime getSignature("toEqual", "<green>expected<r>", true); const fmt = signature ++ "\n\n{any}\n"; - if (Output.enable_ansi_colors) { - globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); - return .zero; - } - globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); + globalObject.throwPretty(fmt, .{diff_formatter}); return .zero; } const signature = comptime getSignature("toEqual", "<green>expected<r>", false); const fmt = signature ++ "\n\n{any}\n"; - if (Output.enable_ansi_colors) { - globalObject.throw(Output.prettyFmt(fmt, true), .{diff_formatter}); - return .zero; - } - globalObject.throw(Output.prettyFmt(fmt, false), .{diff_formatter}); + globalObject.throwPretty(fmt, .{diff_formatter}); return .zero; } @@ -2728,14 +2828,7 @@ pub const Expect = struct { if (property_matchers) |_prop_matchers| { var prop_matchers = _prop_matchers; - var itr = PropertyMatcherIterator{ - .received_object = value, - .failed = false, - }; - - prop_matchers.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach); - - if (itr.failed) { + if (!value.deepMatch(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" ++ @@ -3627,88 +3720,6 @@ pub const Expect = struct { return .zero; } - pub const PropertyMatcherIterator = struct { - received_object: JSValue, - failed: bool, - i: usize = 0, - - pub fn forEach( - globalObject: *JSGlobalObject, - ctx_ptr: ?*anyopaque, - key_: [*c]ZigString, - value: JSValue, - _: bool, - ) callconv(.C) void { - const key: ZigString = key_.?[0]; - if (key.eqlComptime("constructor")) return; - if (key.eqlComptime("call")) return; - - var ctx: *@This() = bun.cast(*@This(), ctx_ptr orelse return); - defer ctx.i += 1; - var received_object: JSValue = ctx.received_object; - - if (received_object.get(globalObject, key.slice())) |received_value| { - if (JSC.Jest.ExpectAny.fromJS(value)) |_| { - var constructor_value = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse { - globalObject.throw("Internal consistency error: the expect.any(constructor value) was garbage collected but it should not have been!", .{}); - ctx.failed = true; - return; - }; - - if (received_value.isCell() and received_value.isInstanceOf(globalObject, constructor_value)) { - received_object.put(globalObject, &key, value); - return; - } - - // check primitives - // TODO: check the constructor for primitives by reading it from JSGlobalObject through a binding. - var constructor_name = ZigString.Empty; - constructor_value.getNameProperty(globalObject, &constructor_name); - if (received_value.isNumber() and constructor_name.eqlComptime("Number")) { - received_object.put(globalObject, &key, value); - return; - } - if (received_value.isBoolean() and constructor_name.eqlComptime("Boolean")) { - received_object.put(globalObject, &key, value); - return; - } - if (received_value.isString() and constructor_name.eqlComptime("String")) { - received_object.put(globalObject, &key, value); - return; - } - if (received_value.isBigInt() and constructor_name.eqlComptime("BigInt")) { - received_object.put(globalObject, &key, value); - return; - } - - ctx.failed = true; - return; - } - - if (value.isObject()) { - if (received_object.get(globalObject, key.slice())) |new_object| { - var itr = PropertyMatcherIterator{ - .received_object = new_object, - .failed = false, - }; - value.forEachProperty(globalObject, &itr, PropertyMatcherIterator.forEach); - if (itr.failed) { - ctx.failed = true; - } - } else { - ctx.failed = true; - } - - return; - } - - if (value.isSameValue(received_value, globalObject)) return; - } - - ctx.failed = true; - } - }; - pub fn toBeInstanceOf(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { defer this.postMatch(globalObject); @@ -3945,6 +3956,78 @@ pub const Expect = struct { unreachable; } + pub fn toMatchObject(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalObject); + const thisValue = callFrame.this(); + const args = callFrame.arguments(1).slice(); + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toMatchObject() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const not = this.op.contains(.not); + + const received_object = Expect.capturedValueGetCached(thisValue) orelse { + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + + if (!received_object.isObject()) { + const matcher_error = "\n\n<b>Matcher error<r>: <red>received<r> value must be a non-null object\n"; + if (not) { + const fmt = comptime getSignature("toMatchObject", "<green>expected<r>", true) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const fmt = comptime getSignature("toMatchObject", "<green>expected<r>", false) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + 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; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + const fmt = comptime getSignature("toMatchObject", "", false) ++ matcher_error; + globalObject.throwPretty(fmt, .{}); + return .zero; + } + + const property_matchers = args[0]; + + var pass = received_object.deepMatch(property_matchers, globalObject, true); + + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + const diff_formatter = DiffFormatter{ + .received = received_object, + .expected = property_matchers, + .globalObject = globalObject, + .not = not, + }; + + if (not) { + const signature = comptime getSignature("toMatchObject", "<green>expected<r>", true); + const fmt = signature ++ "\n\n{any}\n"; + globalObject.throwPretty(fmt, .{diff_formatter}); + return .zero; + } + + const signature = comptime getSignature("toMatchObject", "<green>expected<r>", false); + const fmt = signature ++ "\n\n{any}\n"; + globalObject.throwPretty(fmt, .{diff_formatter}); + return .zero; + } + pub const toHaveBeenCalledWith = notImplementedJSCFn; pub const toHaveBeenLastCalledWith = notImplementedJSCFn; pub const toHaveBeenNthCalledWith = notImplementedJSCFn; @@ -3953,7 +4036,6 @@ pub const Expect = struct { pub const toHaveLastReturnedWith = notImplementedJSCFn; pub const toHaveNthReturnedWith = notImplementedJSCFn; pub const toContainEqual = notImplementedJSCFn; - pub const toMatchObject = notImplementedJSCFn; pub const toMatchInlineSnapshot = notImplementedJSCFn; pub const toThrowErrorMatchingSnapshot = notImplementedJSCFn; pub const toThrowErrorMatchingInlineSnapshot = notImplementedJSCFn; @@ -3980,14 +4062,23 @@ pub const Expect = struct { return ExpectAny.call(globalObject, callFrame); } + pub fn anything(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + return ExpectAnything.call(globalObject, callFrame); + } + + pub fn stringContaining(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + return ExpectStringContaining.call(globalObject, callFrame); + } + + pub fn stringMatching(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + return ExpectStringMatching.call(globalObject, callFrame); + } + pub const extend = notImplementedStaticFn; - pub const anything = notImplementedStaticFn; pub const arrayContaining = notImplementedStaticFn; pub const assertions = notImplementedStaticFn; pub const hasAssertions = notImplementedStaticFn; pub const objectContaining = notImplementedStaticFn; - pub const stringContaining = notImplementedStaticFn; - pub const stringMatching = notImplementedStaticFn; pub const addSnapshotSerializer = notImplementedStaticFn; pub fn notImplementedJSCFn(_: *Expect, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 0431b2e10..15ab88799 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -1264,6 +1264,43 @@ pub const JestPrettyFormat = struct { } else if (value.as(JSC.ResolveMessage)) |resolve_log| { resolve_log.msg.writeFormat(writer_, enable_ansi_colors) catch {}; return; + } else if (value.as(JSC.Jest.ExpectAnything) != null) { + this.addForNewLine("Anything".len); + writer.writeAll("Anything"); + return; + } else if (value.as(JSC.Jest.ExpectAny) != null) { + const constructor_value = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse return; + + this.addForNewLine("Any<".len); + writer.writeAll("Any<"); + + var class_name = ZigString.init(&name_buf); + constructor_value.getClassName(this.globalThis, &class_name); + this.addForNewLine(class_name.len); + writer.print(comptime Output.prettyFmt("<cyan>{}<r>", enable_ansi_colors), .{class_name}); + writer.writeAll(">"); + + return; + } else if (value.as(JSC.Jest.ExpectStringContaining) != null) { + const substring_value = JSC.Jest.ExpectStringContaining.stringValueGetCached(value) orelse return; + + this.addForNewLine("StringContaining ".len); + writer.writeAll("StringContaining "); + this.printAs(.String, Writer, writer_, substring_value, .String, enable_ansi_colors); + + return; + } else if (value.as(JSC.Jest.ExpectStringMatching) != null) { + const test_value = JSC.Jest.ExpectStringMatching.testValueGetCached(value) orelse return; + + this.addForNewLine("StringMatching ".len); + writer.writeAll("StringMatching "); + + const original_quote_strings = this.quote_strings; + if (test_value.isRegExp()) this.quote_strings = false; + this.printAs(.String, Writer, writer_, test_value, .String, enable_ansi_colors); + this.quote_strings = original_quote_strings; + + return; } else if (jsType != .DOMWrapper) { if (value.isCallable(this.globalThis.vm())) { return this.printAs(.Function, Writer, writer_, value, jsType, enable_ansi_colors); @@ -1701,14 +1738,7 @@ pub const JestPrettyFormat = struct { value.getClassName(this.globalThis, &object_name); if (!strings.eqlComptime(object_name.slice(), "Object")) { - if (value.as(JSC.Jest.ExpectAny)) |_| { - var constructor = JSC.Jest.ExpectAny.constructorValueGetCached(value) orelse unreachable; - var constructor_name = ZigString.Empty; - constructor.getNameProperty(this.globalThis, &constructor_name); - writer.print("Any<{s}>", .{constructor_name}); - } else { - writer.print("{s} {{}}", .{object_name}); - } + writer.print("{s} {{}}", .{object_name}); } else { // don't write "Object" writer.writeAll("{}"); diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index 9f83e550e..e1bd42984 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -933,6 +933,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { pub fn handleHandshake(this: *WebSocket, socket: Socket, success: i32, ssl_error: uws.us_bun_verify_error_t) void { _ = socket; _ = ssl_error; + JSC.markBinding(@src()); log("WebSocket.onHandshake({d})", .{success}); JSC.markBinding(@src()); if (success == 0) { diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts index ed356aa50..ebb6ecfab 100644 --- a/test/js/bun/test/test-test.test.ts +++ b/test/js/bun/test/test-test.test.ts @@ -2522,6 +2522,215 @@ test("toBeOdd()", () => { // FUTURE: expect(new WebAssembly.Global({value:'v128', mutable:true}, 43).value).toBeOdd(); }); +describe("toMatchObject", () => { + test("with Bun.deepMatch", () => { + expect(Bun.deepMatch({ a: 1, b: 2 }, { a: 1 })).toBe(false); + expect(Bun.deepMatch({ a: 1 }, { a: 1, b: 2 })).toBe(true); + }); + test("with expect matcher", () => { + const f = Symbol.for("foo"); + const b = Symbol.for("bar"); + + class Number2 extends Number { + constructor(value) { + super(value); + } + } + class Number3 extends Number2 { + constructor(value) { + super(value); + } + } + + class Boolean2 extends Boolean { + constructor(value) { + super(value); + } + } + expect({ [f]: 2 }).toMatchObject({ [f]: 2 }); + expect({ [f]: 2 }).toMatchObject({ [f]: expect.anything() }); + expect({ [f]: new Date() }).toMatchObject({ [f]: expect.any(Date) }); + expect({ [f]: new Date() }).not.toMatchObject({ [f]: expect.any(RegExp) }); + expect({ [f]: 3 }).not.toMatchObject({ [f]: 5 }); + expect({ [f]: 3 }).not.toMatchObject({ [b]: 3 }); + expect({}).toMatchObject({}); + expect([5]).toMatchObject([5]); + expect([5]).not.toMatchObject([4]); + expect(() => { + expect({}).toMatchObject(); + }).toThrow(); + expect(() => { + expect(true).toMatchObject(true); + }).toThrow(); + expect(() => { + expect(true).toMatchObject(true); + }).toThrow(); + expect(() => { + expect(1).toMatchObject(1); + }).toThrow(); + expect(() => { + expect("a").toMatchObject("a"); + }).toThrow(); + expect(() => { + expect(null).toMatchObject(null); + }).toThrow(); + expect(() => { + expect(undefined).toMatchObject(undefined); + }).toThrow(); + expect(() => { + expect(Symbol()).toMatchObject(Symbol()); + }).toThrow(); + expect(() => { + expect(BigInt(1)).toMatchObject(BigInt(1)); + }).toThrow(); + expect([]).toMatchObject([]); + expect([1]).toMatchObject([1]); + expect([1, 2]).toMatchObject([1, 2]); + expect(() => { + expect([1]).toMatchObject([1, 2]); + }).toThrow(); + expect(() => { + expect([1, 2]).toMatchObject([1]); + }).toThrow(); + expect([]).toMatchObject({}); + expect([1]).toMatchObject({}); + expect([1, 2]).toMatchObject({ 0: 1, 1: 2 }); + expect([1, 2]).not.toMatchObject({ 0: 2 }); + expect(() => { + expect({}).toMatchObject([]); + }).toThrow(); + expect({ a: 1 }).toMatchObject({}); + expect({ a: 1 }).toMatchObject({ a: expect.anything() }); + expect({ a: 1, b: 2 }).toMatchObject({ a: 1 }); + expect({ a: 1, b: 2 }).toMatchObject({ a: 1, b: 2 }); + expect({ a: 1, b: 2 }).toMatchObject({ b: 2 }); + expect({ a: 1, b: 2 }).toMatchObject({ b: 2, a: 1 }); + expect({ a: 1, b: 2 }).toMatchObject({ a: 1, b: 2 }); + expect({}).not.toMatchObject({ a: 1 }); + expect({ a: 89 }).not.toMatchObject({ b: 90 }); + expect({ a: 1, b: 2 }).not.toMatchObject({ a: 1, b: 3 }); + expect({ a: 1, b: 2 }).not.toMatchObject({ a: 1, b: 2, c: 4 }); + expect({ a: new Date(), b: "jj" }).not.toMatchObject({ b: expect.any(Number) }); + expect({ a: "123" }).not.toMatchObject({ a: expect.stringContaining("4") }); + class DString extends String { + constructor(str) { + super(str); + } + } + expect({ a: "hello world" }).toMatchObject({ a: expect.stringContaining("wor") }); + expect({ a: "hello world" }).not.toMatchObject({ a: expect.stringContaining("wol") }); + expect({ a: "hello String" }).toMatchObject({ a: expect.stringContaining(new String("Str")) }); + expect({ a: "hello String" }).not.toMatchObject({ a: expect.stringContaining(new String("Strs")) }); + expect({ a: "hello derived String" }).toMatchObject({ a: expect.stringContaining(new DString("riv")) }); + expect({ a: "hello derived String" }).not.toMatchObject({ a: expect.stringContaining(new DString("rivd")) }); + expect({ a: "hello world" }).toMatchObject({ a: expect.stringMatching("wor") }); + expect({ a: "hello world" }).not.toMatchObject({ a: expect.stringMatching("word") }); + expect({ a: "hello world" }).toMatchObject({ a: "hello world" }); + expect({ a: "hello world" }).toMatchObject({ a: expect.stringMatching(/wor/) }); + expect({ a: "hello world" }).not.toMatchObject({ a: expect.stringMatching(/word/) }); + expect({ a: expect.stringMatching("wor") }).toMatchObject({ a: "hello world" }); + expect({ a: expect.stringMatching("word") }).not.toMatchObject({ a: "hello world" }); + expect({ a: expect.stringMatching(/wor/) }).toMatchObject({ a: "hello world" }); + expect({ a: expect.stringMatching(/word/) }).not.toMatchObject({ a: "hello world" }); + expect({ a: expect.stringMatching(/word/) }).toMatchObject({ a: "hello word" }); + expect({ a: [1, 2, 3] }).toMatchObject({ a: [1, 2, 3] }); + expect({ a: [1, 2, 3] }).toMatchObject({ a: [1, 2, 3] }); + expect({ a: [1, 2, 4] }).not.toMatchObject({ a: [1, 2, 3] }); + + expect([]).toMatchObject([]); + expect([]).toMatchObject({}); + expect({}).not.toMatchObject([]); + expect({ a: 1 }).toMatchObject({}); + expect({ a: 1 }).toMatchObject({ a: 1 }); + + expect({ a: 1 }).toMatchObject({ a: expect.anything() }); + expect({ a: null }).not.toMatchObject({ a: expect.anything() }); + expect({ a: undefined }).not.toMatchObject({ a: expect.anything() }); + + expect({ a: new Date() }).toMatchObject({ a: expect.any(Date) }); + expect({ a: new Date() }).not.toMatchObject({ a: expect.any(RegExp) }); + expect({ a: new RegExp("a", "g") }).toMatchObject({ a: expect.any(RegExp) }); + expect({ a: /a/g }).toMatchObject({ a: expect.any(RegExp) }); + + expect({ + first: new Boolean2(false), + a: { + 4: [3, 2, 2], + j: new Date(), + b: { + c: { + num: 1, + d: { + e: { + bigint: 123n, + f: { + g: { + h: { + i: new Number3(2), + bool: true, + }, + compare: "compare", + }, + }, + ignore1: 234, + ignore2: { + ignore3: 23421, + ignore4: { + ignore5: { + ignore6: "hello", + ignore7: "done", + }, + }, + }, + }, + }, + string1: "hello", + string2: "hello", + string3: "hello", + }, + }, + }, + }).toMatchObject({ + first: expect.any(Boolean2), + a: { + 4: [3, 2, expect.any(Number)], + + j: expect.any(Date), + b: { + c: { + num: expect.any(Number), + string1: expect.anything(), + string2: expect.stringContaining("ll"), + string3: expect.stringMatching(/ll/), + d: { + e: { + bigint: expect.any(BigInt), + f: { + g: { + compare: "compare", + h: { + i: expect.any(Number3), + bool: expect.any(Boolean), + }, + }, + }, + }, + }, + }, + }, + }, + }); + + var a1 = [1]; + a1[f] = 99; + expect(a1).not.toMatchObject([1]); + expect([1]).not.toMatchObject(a1); + expect({ 1: 1 }).not.toMatchObject(a1); + expect(a1).not.toMatchObject({ 1: 1 }); + expect(a1).toMatchObject(a1); + }); +}); + try { test("test this doesnt crash"); } catch (e) {} |