diff options
-rwxr-xr-x | bench/bun.lockb | bin | 35685 -> 36074 bytes | |||
-rw-r--r-- | bench/package.json | 3 | ||||
-rw-r--r-- | bench/snippets/deep-equals.js | 509 | ||||
-rw-r--r-- | src/bun.js/bindings/OnigurumaRegExp.cpp | 64 | ||||
-rw-r--r-- | src/bun.js/bindings/OnigurumaRegExp.h | 64 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 31 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 540 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-handwritten.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 41 | ||||
-rw-r--r-- | test/bun.js/test-test.test.ts | 1025 |
13 files changed, 2220 insertions, 69 deletions
diff --git a/bench/bun.lockb b/bench/bun.lockb Binary files differindex 14530a5ec..8fea41bc1 100755 --- a/bench/bun.lockb +++ b/bench/bun.lockb diff --git a/bench/package.json b/bench/package.json index c443ffeb2..3129defe8 100644 --- a/bench/package.json +++ b/bench/package.json @@ -14,5 +14,8 @@ "async": "cd async && bun run deps && bun run build && bun run bench", "sqlite": "cd sqlite && bun run deps && bun run build && bun run bench", "modules:node_os": "cd modules/node_os && bun run deps &&bun run build && bun run bench" + }, + "devDependencies": { + "fast-deep-equal": "^3.1.3" } } diff --git a/bench/snippets/deep-equals.js b/bench/snippets/deep-equals.js new file mode 100644 index 000000000..a56704721 --- /dev/null +++ b/bench/snippets/deep-equals.js @@ -0,0 +1,509 @@ +import { bench, group, run } from "mitata"; +import fastDeepEquals from "fast-deep-equal/es6/index"; +// const Date = globalThis.Date; + +function func1() {} +function func2() {} + +const s = Symbol("foo"); +const a1 = [1, 2, 3, 4]; +a1[s] = "f00"; +const a2 = [1, 2, 3, 4]; +a2[s] = "f00"; + +const e1 = new Set(); +e1.add([1, 2, 3]); +e1.add("test1"); +e1.add(498); +e1.add({ a: 1, b: 2 }); +e1.add({ a: 1, b: 434221 }); +e1.add({ a: 1, b: 25 }); +e1.add({ a: 1, b: 4 }); +e1.add({ a: 1, b: 2667 }); +e1.add({ a: 1, b: 2 }); +e1.add({ a: 1, b: 23426 }); +e1.add({ a: 1, b: 672 }); +e1.add({ a: 1, b: 28465 }); + +const e2 = new Set(); +e2.add([1, 2, 3]); +e2.add("test1"); +e2.add(498); +e1.add({ a: 1, b: 2 }); +e1.add({ a: 1, b: 434221 }); +e1.add({ a: 1, b: 25 }); +e1.add({ a: 1, b: 4 }); +e1.add({ a: 1, b: 2667 }); +e1.add({ a: 1, b: 2 }); +e1.add({ a: 1, b: 23426 }); +e1.add({ a: 1, b: 672 }); +e1.add({ a: 1, b: 28465 }); + +const d1 = new Set(); +d1.add({ a: 1, b: 2 }); +const d2 = new Set(); +d2.add({ a: 1, b: 2 }); + +const fixture = [ + { + description: "scalars", + tests: [ + { + description: "equal numbers", + value1: 1, + value2: 1, + equal: true, + }, + { + description: "not equal numbers", + value1: 1, + value2: 2, + equal: false, + }, + { + description: "number and array are not equal", + value1: 1, + value2: [], + equal: false, + }, + { + description: "0 and null are not equal", + value1: 0, + value2: null, + equal: false, + }, + { + description: "equal strings", + value1: "azzzz", + value2: "azzzz", + equal: true, + }, + { + description: "not equal strings", + value1: "azzzz", + value2: "bzzzz", + equal: false, + }, + { + description: "empty string and null are not equal", + value1: "", + value2: null, + equal: false, + }, + { + description: "null is equal to null", + value1: null, + value2: null, + equal: true, + }, + { + description: "equal booleans (true)", + value1: true, + value2: true, + equal: true, + }, + { + description: "equal booleans (false)", + value1: false, + value2: false, + equal: true, + }, + { + description: "not equal booleans", + value1: true, + value2: false, + equal: false, + }, + { + description: "1 and true are not equal", + value1: 1, + value2: true, + equal: false, + }, + { + description: "0 and false are not equal", + value1: 0, + value2: false, + equal: false, + }, + { + description: "NaN and NaN are equal", + value1: NaN, + value2: NaN, + equal: true, + }, + { + description: "0 and -0 are equal", + value1: 0, + value2: -0, + equal: true, + }, + { + description: "Infinity and Infinity are equal", + value1: Infinity, + value2: Infinity, + equal: true, + }, + { + description: "Infinity and -Infinity are not equal", + value1: Infinity, + value2: -Infinity, + equal: false, + }, + ], + }, + + { + description: "objects", + tests: [ + { + description: "empty objects are equal", + value1: {}, + value2: {}, + equal: true, + }, + { + description: 'equal objects (same properties "order")', + value1: { a: 1, b: "2" }, + value2: { a: 1, b: "2" }, + equal: true, + }, + { + description: 'equal objects (different properties "order")', + value1: { a: 1, b: "2" }, + value2: { b: "2", a: 1 }, + equal: true, + }, + { + description: "not equal objects (extra property)", + value1: { a: 1, b: "2" }, + value2: { a: 1, b: "2", c: [] }, + equal: false, + }, + { + description: "not equal objects (different property values)", + value1: { a: 1, b: "2", c: 3 }, + value2: { a: 1, b: "2", c: 4 }, + equal: false, + }, + { + description: "not equal objects (different properties)", + value1: { a: 1, b: "2", c: 3 }, + value2: { a: 1, b: "2", d: 3 }, + equal: false, + }, + { + description: "equal objects (same sub-properties)", + value1: { a: [{ b: "c" }] }, + value2: { a: [{ b: "c" }] }, + equal: true, + }, + { + description: "not equal objects (different sub-property value)", + value1: { a: [{ b: "c" }] }, + value2: { a: [{ b: "d" }] }, + equal: false, + }, + { + description: "not equal objects (different sub-property)", + value1: { a: [{ b: "c" }] }, + value2: { a: [{ c: "c" }] }, + equal: false, + }, + { + description: "empty array and empty object are not equal", + value1: {}, + value2: [], + equal: false, + }, + { + description: "object with extra undefined properties are not equal #1", + value1: {}, + value2: { foo: undefined }, + equal: false, + }, + { + description: "object with extra undefined properties are not equal #2", + value1: { foo: undefined }, + value2: {}, + equal: false, + }, + { + description: "object with extra undefined properties are not equal #3", + value1: { foo: undefined }, + value2: { bar: undefined }, + equal: false, + }, + { + description: "nulls are equal", + value1: null, + value2: null, + equal: true, + }, + { + description: "null and undefined are not equal", + value1: null, + value2: undefined, + equal: false, + }, + { + description: "null and empty object are not equal", + value1: null, + value2: {}, + equal: false, + }, + { + description: "undefined and empty object are not equal", + value1: undefined, + value2: {}, + equal: false, + }, + { + description: + "objects with different `toString` functions returning same values are equal", + value1: { toString: () => "Hello world!" }, + value2: { toString: () => "Hello world!" }, + equal: true, + }, + { + description: + "objects with `toString` functions returning different values are not equal", + value1: { toString: () => "Hello world!" }, + value2: { toString: () => "Hi!" }, + equal: false, + }, + ], + }, + + { + description: "arrays", + tests: [ + { + description: "two empty arrays are equal", + value1: [], + value2: [], + equal: true, + }, + { + description: "equal arrays", + value1: [1, 2, 3], + value2: [1, 2, 3], + equal: true, + }, + { + description: "equal arrays with symbols", + value1: a1, + value2: a2, + equal: true, + }, + // { + // description: "not equal arrays (different item)", + // value1: [1, 2, 3], + // value2: [1, 2, 4], + // equal: false, + // }, + // { + // description: "not equal arrays (different length)", + // value1: [1, 2, 3], + // value2: [1, 2], + // equal: false, + // }, + { + description: "equal arrays of objects", + value1: [ + ...Array.from({ length: 200000 }, (i) => ({ + a: 1, + b: 2, + })), + ], + value2: [ + ...Array.from({ length: 200000 }, (i) => ({ + a: 1, + b: 2, + })), + ], + equal: true, + }, + { + description: "equal objects", + value1: { + a: 1, + b: 2, + c: 3, + d: 4, + // get foo() { + // return 1; + // }, + }, + value2: { + a: 1, + b: 2, + c: 3, + d: 4, + // get foo() { + // return 1; + // }, + }, + equal: true, + }, + { + description: "equal sets", + value1: d1, + value2: d2, + equal: true, + }, + // { + // description: "not equal arrays of objects", + // value1: [{ a: "a" }, { b: "b" }], + // value2: [{ a: "a" }, { b: "c" }], + // equal: false, + // }, + // { + // description: "pseudo array and equivalent array are not equal", + // value1: { 0: 0, 1: 1, length: 2 }, + // value2: [0, 1], + // equal: false, + // }, + ], + }, + { + description: "Date objects", + tests: [ + { + description: "equal date objects", + value1: new Date("2017-06-16T21:36:48.362Z"), + value2: new Date("2017-06-16T21:36:48.362Z"), + equal: true, + }, + { + description: "not equal date objects", + value1: new Date("2017-06-16T21:36:48.362Z"), + value2: new Date("2017-01-01T00:00:00.000Z"), + equal: false, + }, + { + description: "date and string are not equal", + value1: new Date("2017-06-16T21:36:48.362Z"), + value2: "2017-06-16T21:36:48.362Z", + equal: false, + }, + { + description: "date and object are not equal", + value1: new Date("2017-06-16T21:36:48.362Z"), + value2: {}, + equal: false, + }, + ], + }, + { + description: "RegExp objects", + tests: [ + { + description: "equal RegExp objects", + value1: /foo/, + value2: /foo/, + equal: true, + }, + { + description: "not equal RegExp objects (different pattern)", + value1: /foo/, + value2: /bar/, + equal: false, + }, + { + description: "not equal RegExp objects (different flags)", + value1: /foo/, + value2: /foo/i, + equal: false, + }, + { + description: "RegExp and string are not equal", + value1: /foo/, + value2: "foo", + equal: false, + }, + { + description: "RegExp and object are not equal", + value1: /foo/, + value2: {}, + equal: false, + }, + ], + }, + { + description: "functions", + tests: [ + { + description: "same function is equal", + value1: func1, + value2: func1, + equal: true, + }, + { + description: "different functions are not equal", + value1: func1, + value2: func2, + equal: false, + }, + ], + }, + { + description: "sample objects", + tests: [ + { + description: "big object", + value1: { + prop1: "value1", + prop2: "value2", + prop3: "value3", + prop4: { + subProp1: "sub value1", + subProp2: { + subSubProp1: "sub sub value1", + subSubProp2: [1, 2, { prop2: 1, prop: 2 }, 4, 5], + }, + }, + prop5: 1000, + // prop6: new Date(2016, 2, 10), + }, + value2: { + prop5: 1000, + prop3: "value3", + prop1: "value1", + prop2: "value2", + // prop6: new Date(2016, 2, 10), + prop4: { + subProp2: { + subSubProp1: "sub sub value1", + subSubProp2: [1, 2, { prop2: 1, prop: 2 }, 4, 5], + }, + subProp1: "sub value1", + }, + }, + equal: true, + }, + ], + }, +]; + +for (let { tests, description } of fixture) { + // if (description === "sample objects") { + for (let { description: describe, value1, value2, equal } of tests) { + var expected; + group(describe, () => { + for (let equalsFn of [Bun.deepEquals, fastDeepEquals]) { + bench(equalsFn.name, () => { + expected = equalsFn(value1, value2); + if (expected !== equal) { + throw new Error( + `Expected ${expected} to be ${equal} for ${description}`, + ); + } + }); + } + }); + // } + } +} + +await run(); diff --git a/src/bun.js/bindings/OnigurumaRegExp.cpp b/src/bun.js/bindings/OnigurumaRegExp.cpp index 496a3de67..c9dedb09c 100644 --- a/src/bun.js/bindings/OnigurumaRegExp.cpp +++ b/src/bun.js/bindings/OnigurumaRegExp.cpp @@ -376,70 +376,6 @@ private: void finishCreation(JSC::VM&, JSC::JSGlobalObject*); }; -class OnigurumaRegEx final : public JSC::JSDestructibleObject { -public: - using Base = JSC::JSDestructibleObject; - - static OnigurumaRegEx* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) - { - OnigurumaRegEx* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegEx>(vm)) OnigurumaRegEx(vm, globalObject, structure); - ptr->finishCreation(vm); - return ptr; - } - - static OnigurumaRegEx* create(JSC::JSGlobalObject* globalObject, WTF::String&& pattern, WTF::String&& flags) - { - auto* structure = reinterpret_cast<Zig::GlobalObject*>(globalObject)->OnigurumaRegExpStructure(); - auto* object = create(globalObject->vm(), globalObject, structure); - object->m_flagsString = WTFMove(flags); - object->m_patternString = WTFMove(pattern); - - return object; - } - - 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<OnigurumaRegEx, UseCustomHeapCellType::No>( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForOnigurumaRegExp.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForOnigurumaRegExp = WTFMove(space); }, - [](auto& spaces) { return spaces.m_subspaceForOnigurumaRegExp.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForOnigurumaRegExp = WTFMove(space); }); - } - - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(RegExpObjectType, StructureFlags), info()); - } - - // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); - - const WTF::String& flagsString() const { return m_flagsString; } - void setFlagsString(const WTF::String& flagsString) { m_flagsString = flagsString; } - const WTF::String& patternString() const { return m_patternString; } - void setPatternString(const WTF::String& patternString) { m_patternString = patternString; } - - int32_t m_lastIndex = 0; - -private: - OnigurumaRegEx(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM&) - { - Base::finishCreation(vm()); - } - - WTF::String m_patternString = {}; - WTF::String m_flagsString = {}; -}; - const ClassInfo OnigurumaRegExpConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegExpConstructor) }; const ClassInfo OnigurumaRegExpPrototype::s_info = { "Object"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegExpPrototype) }; const ClassInfo OnigurumaRegEx::s_info = { "RegExp"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegEx) }; diff --git a/src/bun.js/bindings/OnigurumaRegExp.h b/src/bun.js/bindings/OnigurumaRegExp.h index 058492844..8ddb65a0c 100644 --- a/src/bun.js/bindings/OnigurumaRegExp.h +++ b/src/bun.js/bindings/OnigurumaRegExp.h @@ -12,6 +12,70 @@ namespace Zig { using namespace JSC; using namespace WebCore; +class OnigurumaRegEx final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + + static OnigurumaRegEx* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + OnigurumaRegEx* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegEx>(vm)) OnigurumaRegEx(vm, globalObject, structure); + ptr->finishCreation(vm); + return ptr; + } + + static OnigurumaRegEx* create(JSC::JSGlobalObject* globalObject, WTF::String&& pattern, WTF::String&& flags) + { + auto* structure = reinterpret_cast<Zig::GlobalObject*>(globalObject)->OnigurumaRegExpStructure(); + auto* object = create(globalObject->vm(), globalObject, structure); + object->m_flagsString = WTFMove(flags); + object->m_patternString = WTFMove(pattern); + + return object; + } + + 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<OnigurumaRegEx, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForOnigurumaRegExp.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForOnigurumaRegExp = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForOnigurumaRegExp.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForOnigurumaRegExp = WTFMove(space); }); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(RegExpObjectType, StructureFlags), info()); + } + + // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + + const WTF::String& flagsString() const { return m_flagsString; } + void setFlagsString(const WTF::String& flagsString) { m_flagsString = flagsString; } + const WTF::String& patternString() const { return m_patternString; } + void setPatternString(const WTF::String& patternString) { m_patternString = patternString; } + + int32_t m_lastIndex = 0; + +private: + OnigurumaRegEx(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&) + { + Base::finishCreation(vm()); + } + + WTF::String m_patternString = {}; + WTF::String m_flagsString = {}; +}; + class OnigurumaRegExpConstructor final : public JSC::InternalFunction { public: using Base = JSC::InternalFunction; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 5d7c49292..c1abb8291 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1782,6 +1782,31 @@ JSC_DEFINE_HOST_FUNCTION(functionBunEscapeHTML, (JSC::JSGlobalObject * lexicalGl } } +JSC_DECLARE_HOST_FUNCTION(functionBunDeepEquals); + +JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (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 arg1 = callFrame->argument(0); + JSC::JSValue arg2 = callFrame->argument(1); + + Vector<std::pair<JSValue, JSValue>, 16> stack; + + bool isEqual = Bun__deepEquals(globalObject, arg1, arg2, stack, &scope, true); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsBoolean(isEqual)); +} + JSC_DECLARE_HOST_FUNCTION(functionBunNanoseconds); JSC_DEFINE_HOST_FUNCTION(functionBunNanoseconds, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) @@ -3130,6 +3155,12 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm } { + JSC::Identifier identifier = JSC::Identifier::fromString(vm, "deepEquals"_s); + object->putDirectNativeFunction(vm, this, identifier, 2, functionBunDeepEquals, 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 8c0df075e..4cb163f88 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -77,6 +77,12 @@ #include "HTTPHeaderNames.h" #include "JSDOMPromiseDeferred.h" #include "JavaScriptCore/TestRunnerUtils.h" +#include "JavaScriptCore/DateInstance.h" +#include "JavaScriptCore/RegExpObject.h" +#include "JavaScriptCore/PropertyNameArray.h" +#include "JavaScriptCore/HashMapImpl.h" +#include "JavaScriptCore/HashMapImplInlines.h" +#include "OnigurumaRegExp.h" template<typename UWSResponse> static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) @@ -122,6 +128,530 @@ static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObjec } } +static bool canPerformFastPropertyEnumerationForObjectAssignBun(Structure* s) +{ + if (s->typeInfo().overridesGetOwnPropertySlot()) + return false; + if (s->typeInfo().overridesAnyFormOfGetOwnPropertyNames()) + return false; + // FIXME: Indexed properties can be handled. + // https://bugs.webkit.org/show_bug.cgi?id=185358 + if (hasIndexedProperties(s->indexingType())) + return false; + if (s->hasGetterSetterProperties()) + return false; + if (s->hasReadOnlyOrGetterSetterPropertiesExcludingProto()) + return false; + if (s->hasCustomGetterSetterProperties()) + return false; + if (s->isUncacheableDictionary()) + return false; + // Cannot perform fast [[Put]] to |target| if the property names of the |source| contain "__proto__". + if (s->hasUnderscoreProtoPropertyExcludingOriginalProto()) + return false; + return true; +} + +// adapted from underscorejs [https://underscorejs.org/docs/modules/isEqual.html] +bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, ThrowScope* scope, bool addToStack) +{ + VM& vm = globalObject->vm(); + if (!v1.isEmpty() && !v2.isEmpty() && JSC::sameValue(globalObject, v1, v2)) { + return true; + } + + if (v1.isEmpty() || v2.isEmpty()) + return v1.isEmpty() == v2.isEmpty(); + + if (v1.isPrimitive() || v2.isPrimitive()) + return false; + + RELEASE_ASSERT(v1.isCell()); + RELEASE_ASSERT(v2.isCell()); + + size_t length = stack.size(); + size_t originalLength = length; + for (size_t i = 0; i < length; i++) { + auto values = stack.at(i); + if (JSC::JSValue::strictEqual(globalObject, values.first, v1)) { + return JSC::JSValue::strictEqual(globalObject, values.second, v2); + } else if (JSC::JSValue::strictEqual(globalObject, values.second, v2)) + return false; + } + + if (addToStack) { + stack.append({ v1, v2 }); + } + + JSCell* c1 = v1.asCell(); + JSCell* c2 = v2.asCell(); + JSC::JSType c1Type = c1->type(); + JSC::JSType c2Type = c2->type(); + + switch (c1Type) { + case JSSetType: { + if (c2Type != JSSetType) { + return false; + } + + JSSet* set1 = jsCast<JSSet*>(c1); + JSSet* set2 = jsCast<JSSet*>(c2); + + if (set1->size() != set2->size()) { + return false; + } + + IterationRecord iterationRecord1 = iteratorForIterable(globalObject, v1); + bool isEqual = true; + while (true) { + JSValue next1 = iteratorStep(globalObject, iterationRecord1); + if (next1.isFalse()) { + break; + } + + JSValue nextValue1 = iteratorValue(globalObject, next1); + RETURN_IF_EXCEPTION(*scope, false); + + bool found = false; + IterationRecord iterationRecord2 = iteratorForIterable(globalObject, v2); + while (true) { + JSValue next2 = iteratorStep(globalObject, iterationRecord2); + if (UNLIKELY(next2.isFalse())) { + break; + } + + JSValue nextValue2 = iteratorValue(globalObject, next2); + RETURN_IF_EXCEPTION(*scope, false); + + // set has unique values, no need to count + if (Bun__deepEquals(globalObject, nextValue1, nextValue2, stack, scope, false)) { + found = true; + if (!nextValue1.isPrimitive()) { + stack.append({ nextValue1, nextValue2 }); + } + break; + } + } + + if (!found) { + isEqual = false; + break; + } + } + + if (!isEqual) { + return false; + } + + break; + } + case JSMapType: { + if (c2Type != JSMapType) { + return false; + } + + JSMap* map1 = jsCast<JSMap*>(c1); + JSMap* map2 = jsCast<JSMap*>(c2); + size_t leftSize = map1->size(); + + if (leftSize != map2->size()) { + return false; + } + + IterationRecord iterationRecord1 = iteratorForIterable(globalObject, v1); + bool isEqual = true; + while (true) { + JSValue next1 = iteratorStep(globalObject, iterationRecord1); + if (next1.isFalse()) { + break; + } + + JSValue nextValue1 = iteratorValue(globalObject, next1); + RETURN_IF_EXCEPTION(*scope, false); + + if (UNLIKELY(!nextValue1.isObject())) { + return false; + } + + JSObject* nextValueObject1 = asObject(nextValue1); + + JSValue key1 = nextValueObject1->getIndex(globalObject, static_cast<unsigned>(0)); + RETURN_IF_EXCEPTION(*scope, false); + + JSValue value1 = nextValueObject1->getIndex(globalObject, static_cast<unsigned>(1)); + RETURN_IF_EXCEPTION(*scope, false); + + bool found = false; + IterationRecord iterationRecord2 = iteratorForIterable(globalObject, v2); + while (true) { + JSValue next2 = iteratorStep(globalObject, iterationRecord2); + if (UNLIKELY(next2.isFalse())) { + break; + } + + JSValue nextValue2 = iteratorValue(globalObject, next2); + RETURN_IF_EXCEPTION(*scope, false); + + if (UNLIKELY(!nextValue2.isObject())) { + return false; + } + + JSObject* nextValueObject2 = asObject(nextValue2); + + JSValue key2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(0)); + RETURN_IF_EXCEPTION(*scope, false); + + JSValue value2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(1)); + RETURN_IF_EXCEPTION(*scope, false); + + if (Bun__deepEquals(globalObject, key1, key2, stack, scope, false)) { + if (Bun__deepEquals(globalObject, nextValue1, nextValue2, stack, scope, false)) { + found = true; + if (!nextValue1.isPrimitive()) { + stack.append({ nextValue1, nextValue2 }); + } + break; + } + } + } + + if (!found) { + isEqual = false; + break; + } + } + + if (!isEqual) { + return false; + } + + break; + } + case ArrayBufferType: { + if (c2Type != ArrayBufferType) { + return false; + } + + JSC::ArrayBuffer* left = jsCast<JSArrayBuffer*>(v1)->impl(); + JSC::ArrayBuffer* right = jsCast<JSArrayBuffer*>(v2)->impl(); + size_t byteLength = left->byteLength(); + + if (right->byteLength() != byteLength) { + return false; + } + + if (byteLength == 0) + return true; + + if (UNLIKELY(right->isDetached() || left->isDetached())) { + return false; + } + + const void* vector = left->data(); + const void* rightVector = right->data(); + if (UNLIKELY(!vector || !rightVector)) { + return false; + } + + if (UNLIKELY(vector == rightVector)) + return true; + + return (memcmp(vector, rightVector, byteLength) == 0); + } + case JSDateType: { + if (c2Type != JSDateType) { + return false; + } + + JSC::DateInstance* left = jsCast<DateInstance*>(v1); + JSC::DateInstance* right = jsCast<DateInstance*>(v2); + + if (left->structureID() == right->structureID()) { + return left->internalNumber() == right->internalNumber(); + } + break; + } + case RegExpObjectType: { + if (c2Type != RegExpObjectType) { + return false; + } + + if (OnigurumaRegEx* left = jsDynamicCast<OnigurumaRegEx*>(v1)) { + OnigurumaRegEx* right = jsDynamicCast<OnigurumaRegEx*>(v2); + if (UNLIKELY(!right)) { + return false; + } + + if (!equal(left->patternString(), right->patternString())) { + return false; + } + + if (!equal(left->flagsString(), right->flagsString())) { + return false; + } + + return true; + } else if (JSC::RegExpObject* left = jsDynamicCast<JSC::RegExpObject*>(v1)) { + JSC::RegExpObject* right = jsDynamicCast<JSC::RegExpObject*>(v2); + + if (UNLIKELY(!right)) { + return false; + } + + return left->regExp()->key() == right->regExp()->key(); + } + + return false; + } + case Int8ArrayType: + case Uint8ArrayType: + case Uint8ClampedArrayType: + case Int16ArrayType: + case Uint16ArrayType: + case Int32ArrayType: + case Uint32ArrayType: + case Float32ArrayType: + case Float64ArrayType: + case BigInt64ArrayType: + case BigUint64ArrayType: { + if (!isTypedArrayType(c2Type)) { + return false; + } + + JSC::JSArrayBufferView* left = jsCast<JSArrayBufferView*>(v1); + JSC::JSArrayBufferView* right = jsCast<JSArrayBufferView*>(v2); + size_t byteLength = left->byteLength(); + + if (right->byteLength() != byteLength) { + return false; + } + + if (byteLength == 0) + return true; + + if (UNLIKELY(right->isDetached() || left->isDetached())) { + return false; + } + + const void* vector = left->vector(); + const void* rightVector = right->vector(); + if (UNLIKELY(!vector || !rightVector)) { + return false; + } + + if (UNLIKELY(vector == rightVector)) + return true; + + return (memcmp(vector, rightVector, byteLength) == 0); + } + + case JSFunctionType: { + return false; + } + + default: { + break; + } + } + + bool v1Array = isArray(globalObject, v1); + RETURN_IF_EXCEPTION(*scope, false); + bool v2Array = isArray(globalObject, v2); + RETURN_IF_EXCEPTION(*scope, false); + + JSObject* o1 = v1.getObject(); + JSObject* o2 = v2.getObject(); + + if (v1Array != v2Array) + return false; + + if (v1Array && v2Array) { + JSC::JSArray* array1 = JSC::jsCast<JSC::JSArray*>(v1); + JSC::JSArray* array2 = JSC::jsCast<JSC::JSArray*>(v2); + size_t length = array1->length(); + if (length != array2->length()) { + return false; + } + + if (array1->canDoFastIndexedAccess() && array2->canDoFastIndexedAccess()) { + for (size_t i = 0; i < length; i++) { + JSValue left = o1->getIndexQuickly(i); + RETURN_IF_EXCEPTION(*scope, false); + JSValue right = o2->getIndexQuickly(i); + RETURN_IF_EXCEPTION(*scope, false); + if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) { + return false; + } + + RETURN_IF_EXCEPTION(*scope, false); + } + + } else { + for (size_t i = 0; i < length; i++) { + JSValue left = o1->getIndex(globalObject, i); + RETURN_IF_EXCEPTION(*scope, false); + JSValue right = o2->getIndex(globalObject, i); + RETURN_IF_EXCEPTION(*scope, false); + + if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) { + return false; + } + + RETURN_IF_EXCEPTION(*scope, false); + } + } + + JSC::PropertyNameArray a1(vm, PropertyNameMode::Symbols, PrivateSymbolMode::Include); + JSC::PropertyNameArray a2(vm, PropertyNameMode::Symbols, PrivateSymbolMode::Include); + JSObject::getOwnPropertyNames(o1, globalObject, a1, DontEnumPropertiesMode::Exclude); + JSObject::getOwnPropertyNames(o2, globalObject, a2, DontEnumPropertiesMode::Exclude); + + size_t propertyLength = a1.size(); + if (propertyLength != a2.size()) { + return false; + } + + // take a property name from one, try to get it from both + for (size_t i = 0; i < propertyLength; i++) { + Identifier i1 = a1[i]; + PropertyName propertyName1 = PropertyName(i1); + + JSValue prop1 = o1->get(globalObject, propertyName1); + RETURN_IF_EXCEPTION(*scope, false); + + if (UNLIKELY(!prop1)) { + return false; + } + + JSValue prop2 = o2->getIfPropertyExists(globalObject, propertyName1); + RETURN_IF_EXCEPTION(*scope, false); + + if (!prop2) { + return false; + } + + if (!Bun__deepEquals(globalObject, prop1, prop2, stack, scope, true)) { + return false; + } + + RETURN_IF_EXCEPTION(*scope, false); + } + + if (addToStack) { + stack.remove(originalLength); + } + + RETURN_IF_EXCEPTION(*scope, false); + + return true; + } + + JSC::Structure* o1Structure = o1->structure(); + if (canPerformFastPropertyEnumerationForObjectAssignBun(o1Structure)) { + JSC::Structure* o2Structure = o2->structure(); + if (canPerformFastPropertyEnumerationForObjectAssignBun(o2Structure)) { + + size_t count1 = 0; + + bool result = true; + if (o2Structure->maxOffset() != o1Structure->maxOffset()) { + return false; + } + + o1Structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { + if (entry.attributes() & PropertyAttribute::DontEnum) { + return true; + } + count1++; + + JSValue right = o2->getDirect(vm, JSC::PropertyName(entry.key())); + + if (!right) { + result = false; + return false; + } + + JSValue left = o1->getDirect(entry.offset()); + if (left == right || JSC::sameValue(globalObject, left, right)) { + return true; + } + + if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) { + result = false; + return false; + } + + return true; + }); + + if (result && o2Structure->id() != o1Structure->id()) { + size_t remain = count1; + o2Structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { + if (entry.attributes() & PropertyAttribute::DontEnum) { + return true; + } + + if (remain == 0) { + result = false; + return false; + } + + remain--; + return true; + }); + } + + if (addToStack) { + stack.remove(originalLength); + } + + return result; + } + } + + JSC::PropertyNameArray a1(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include); + JSC::PropertyNameArray a2(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include); + o1->getPropertyNames(globalObject, a1, DontEnumPropertiesMode::Exclude); + o2->getPropertyNames(globalObject, a2, DontEnumPropertiesMode::Exclude); + + size_t propertyLength = a1.size(); + if (propertyLength != a2.size()) { + return false; + } + + // take a property name from one, try to get it from both + for (size_t i = 0; i < propertyLength; i++) { + Identifier i1 = a1[i]; + PropertyName propertyName1 = PropertyName(i1); + + JSValue prop1 = o1->get(globalObject, propertyName1); + RETURN_IF_EXCEPTION(*scope, false); + + if (UNLIKELY(!prop1)) { + return false; + } + + JSValue prop2 = o2->getIfPropertyExists(globalObject, propertyName1); + RETURN_IF_EXCEPTION(*scope, false); + + if (!prop2) { + return false; + } + + if (!Bun__deepEquals(globalObject, prop1, prop2, stack, scope, true)) { + return false; + } + + RETURN_IF_EXCEPTION(*scope, false); + } + + if (addToStack) { + stack.remove(originalLength); + } + + return true; +} + extern "C" { void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool is_ssl, void* arg2) @@ -706,6 +1236,16 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, return JSC::sameValue(globalObject, left, right); } +bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject) +{ + JSC::JSValue v1 = JSC::JSValue::decode(JSValue0); + JSC::JSValue v2 = JSC::JSValue::decode(JSValue1); + + ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm()); + Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16> stack; + return Bun__deepEquals(globalObject, v1, v2, stack, &scope, true); +} + // 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 b769625ec..7b9fb74c1 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3423,6 +3423,10 @@ pub const JSValue = enum(JSValueReprInt) { return @enumToInt(this) == @enumToInt(other) or cppFn("isSameValue", .{ this, other, global }); } + pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool { + return cppFn("deepEquals", .{ this, other, global }); + } + pub fn asString(this: JSValue) *JSString { return cppFn("asString", .{ this, @@ -3611,7 +3615,7 @@ pub const JSValue = enum(JSValueReprInt) { return this.asNullableVoid().?; } - pub const Extern = [_][]const u8{ "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow" }; + pub const Extern = [_][]const u8{ "coerceToInt32", "fastGet_", "getStaticProperty", "createUninitializedUint8Array", "fromInt64NoTruncate", "fromUInt64NoTruncate", "toUInt64NoTruncate", "asPromise", "toInt64", "_then", "put", "makeWithNameAndPrototype", "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt64", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable", "toBooleanSlow", "deepEquals" }; }; extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void; diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 553259ec0..b8d071785 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -271,4 +271,6 @@ extern "C" size_t Bun__encoding__byteLengthUTF16(const UChar* ptr, size_t len, E extern "C" int64_t Bun__encoding__constructFromLatin1(void*, const unsigned char* ptr, size_t len, Encoding encoding); extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, size_t len, Encoding encoding); +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); + #endif diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index a9d106029..67dc9e393 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1669191472 +//-- AUTOGENERATED FILE -- 1669267948 #pragma once #include <stddef.h> @@ -486,6 +486,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__createRangeError(const ZigString* arg0, cons CPP_DECL JSC__JSValue JSC__JSValue__createStringArray(JSC__JSGlobalObject* arg0, ZigString* arg1, size_t arg2, bool arg3); 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__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 41f5f7178..7b649f26f 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -279,6 +279,7 @@ pub extern fn JSC__JSValue__createRangeError(arg0: [*c]const ZigString, arg1: [* pub extern fn JSC__JSValue__createStringArray(arg0: ?*JSC__JSGlobalObject, arg1: [*c]ZigString, arg2: usize, arg3: bool) JSC__JSValue; pub extern fn JSC__JSValue__createTypeError(arg0: [*c]const ZigString, arg1: [*c]const ZigString, arg2: ?*JSC__JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSValue__createUninitializedUint8Array(arg0: ?*JSC__JSGlobalObject, arg1: usize) JSC__JSValue; +pub extern fn JSC__JSValue__deepEquals(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: ?*JSC__JSGlobalObject) bool; pub extern fn JSC__JSValue__eqlCell(JSValue0: JSC__JSValue, arg1: [*c]JSC__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: ?*JSC__JSGlobalObject, arg2: u8) JSC__JSValue; diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index c7372b573..dafb7dc0c 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -675,6 +675,46 @@ pub const Expect = struct { return .zero; } + pub fn toEqual(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { + const thisValue = callFrame.this(); + const _arguments = callFrame.arguments(1); + const arguments: []const JSValue = _arguments.ptr[0.._arguments.len]; + + if (arguments.len < 1) { + globalObject.throwInvalidArguments("toEqual() requires 1 argument", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toEqual() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const expected = arguments[0]; + const value = Expect.capturedValueGetCached(thisValue) orelse { + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + value.ensureStillAlive(); + + const not = this.op.contains(.not); + var pass = value.deepEquals(expected, globalObject); + + if (not) pass = !pass; + if (pass) return thisValue; + + // handle failure + var fmt = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject }; + if (not) { + globalObject.throw("Expected values to not be equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) }); + } else { + globalObject.throw("Expected values to be equal:\n\tExpected: {any}\n\tReceived: {any}", .{ expected.toFmt(globalObject, &fmt), value.toFmt(globalObject, &fmt) }); + } + return .zero; + } + pub const toHaveProperty = notImplementedJSCFn; pub const toHaveBeenCalledTimes = notImplementedJSCFn; pub const toHaveBeenCalledWith = notImplementedJSCFn; @@ -691,7 +731,6 @@ pub const Expect = struct { pub const toBeLessThanOrEqual = notImplementedJSCFn; pub const toBeInstanceOf = notImplementedJSCFn; pub const toContainEqual = notImplementedJSCFn; - pub const toEqual = notImplementedJSCFn; pub const toMatch = notImplementedJSCFn; pub const toMatchObject = notImplementedJSCFn; pub const toMatchSnapshot = notImplementedJSCFn; diff --git a/test/bun.js/test-test.test.ts b/test/bun.js/test-test.test.ts index 8ada29ed0..79bac9e74 100644 --- a/test/bun.js/test-test.test.ts +++ b/test/bun.js/test-test.test.ts @@ -1,4 +1,1016 @@ -import { expect, test } from "@jest/globals"; +import { expect, test } from "bun:test"; +import { OnigurumaRegExp } from "bun"; + +function f1() { + return "hello!"; +} +function f2() { + return "hey!"; +} +test("deepEquals regex", () => { + expect(new OnigurumaRegExp("s", "g")).toEqual(new OnigurumaRegExp("s", "g")); + expect(new OnigurumaRegExp("s", "g")).not.toEqual( + new OnigurumaRegExp("s", "i"), + ); + expect(/a/imu).toEqual(/a/imu); + expect(/a/imu).not.toEqual(/ab/imu); + + expect(new RegExp("s", "g")).toEqual(new RegExp("s", "g")); + expect(new RegExp("s", "g")).not.toEqual(new RegExp("s", "i")); +}); + +test("deepEquals derived strings and strings", () => { + let a = new String("hello"); + let b = "hello"; + expect(a).toEqual(a); + expect(b).toEqual(b); + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + class F extends String { + constructor() { + super(); + } + } + + let f = new F("hello"); + expect(f).toEqual(f); + expect(f).not.toEqual(b); + expect(b).not.toEqual(f); + + let j = new String("hello"); + expect(f).not.toEqual(j); + + class G extends String { + constructor() { + super(); + this.x = 0; + } + } + + let g = new G("hello"); + expect(g).not.toEqual(f); + expect(f).not.toEqual(g); + expect(g).toEqual(g); + expect(g).not.toEqual(b); + expect(b).not.toEqual(g); + expect(g).not.toEqual(a); +}); + +test("deepEquals throw getters", () => { + let a = { + get x() { + throw new Error("a"); + }, + }; + + let b = { + get x() { + return 3; + }, + }; + + try { + expect(a).not.toEqual(b); + } catch (e) { + expect(e.message).toContain("a"); + } + + class B { + get x() { + throw new Error("b"); + } + } + + class C { + get x() { + return 3; + } + } + + try { + expect(new B()).not.toEqual(new C()); + } catch (e) { + expect(e.message).toContain("b"); + } + + let o = [ + { + get x() { + throw new Error("c"); + }, + }, + ]; + + let p = [ + { + get x() { + return 3; + }, + }, + ]; + + try { + expect(o).not.toEqual(p); + } catch (e) { + expect(e.message).toContain("c"); + } + + const s = Symbol("s"); + let q = { + get x() { + throw new Error("d"); + }, + }; + q[s] = 3; + + let r = { + get x() { + return 3; + }, + }; + r[s] = 3; + + try { + expect(q).not.toEqual(r); + } catch (e) { + expect(e.message).toContain("d"); + } +}); + +test("deepEquals large object", () => { + let o = {}; + for (let i = 0; i < 65; i++) { + o["bun" + i] = i; + } + expect(o).toEqual(o); + let b = {}; + for (let i = 0; i < 63; i++) { + b["bun" + i] = i; + } + expect(b).toEqual(b); + expect(o).not.toEqual(b); + expect(b).not.toEqual(o); + + let c = { d: [Array(o)] }; + let d = { d: [Array(b)] }; + expect(c).toEqual(c); + expect(d).toEqual(d); + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); + + let e = { d: [Array(o), Array(o)] }; + let f = { d: [Array(b), Array(b)] }; + expect(e).toEqual(e); + expect(f).toEqual(f); + expect(e).not.toEqual(f); + expect(f).not.toEqual(e); + + let p = []; + p[0] = {}; + for (let i = 0; i < 1000; i++) { + p[0]["bun" + i] = i; + } + let q = []; + q[0] = {}; + for (let i = 0; i < 1000; i++) { + q[0]["bun" + i] = i; + } + expect(p).toEqual(p); + expect(q).toEqual(q); + + q[0].bun789 = 788; + expect(p).not.toEqual(q); + expect(q).not.toEqual(p); + + let r = { d: {} }; + let s = { d: {} }; + for (let i = 0; i < 1000; i++) { + r.d["bun" + i] = i; + s.d["bun" + i] = i; + } + + expect(r).toEqual(r); + expect(s).toEqual(s); + + r.d.bun790 = 791; + expect(r).not.toEqual(s); + expect(s).not.toEqual(r); + + let t = []; + t[5] = {}; + let u = []; + u[5] = {}; + for (let i = 0; i < 1000; i++) { + t[5]["bun" + i] = i; + } + for (let i = 0; i < 30; i++) { + u[5]["bun" + i] = i; + } + expect(t).toEqual(t); + expect(u).toEqual(u); + expect(t).not.toEqual(u); + expect(u).not.toEqual(t); + + let v = { j: {} }; + let w = { j: {} }; + for (let i = 0; i < 1000; i++) { + v.j["bun" + i] = i; + w.j["bun" + i] = i; + } + + expect(v).toEqual(v); + expect(w).toEqual(w); + + v.j.bun999 = 1000; + expect(v).not.toEqual(w); + expect(w).not.toEqual(v); + expect(v).toEqual(v); + + v.j.bun999 = 999; + w.j.bun0 = 1; + expect(v).not.toEqual(w); + expect(w).not.toEqual(v); + expect(v).toEqual(v); + expect(w).toEqual(w); +}); + +test("deepEquals - Date", () => { + let d = new Date(); + expect(d).toEqual(d); + let b = d; + expect(b).toEqual(d); + d.setFullYear(1998); + expect(b).toEqual(d); + expect(b).not.toEqual(new Date()); + + var date = new Date(); + date.setFullYear(1995); + expect(new Date()).not.toEqual(date); +}); + +test("deepEquals toString and functions", () => { + expect({ toString: f1 }).toEqual({ + toString: f1, + }); + expect({ toString: f1 }).not.toEqual({ + toString: f2, + }); + + expect(f1).toEqual(f1); + expect(f1).not.toEqual(f2); +}); + +test("deepEquals set and map", () => { + let e = new Map(); + e.set("a", 1); + e.set("b", 2); + e.set("c", 3); + e.set(8, 6); + + let d = new Map(); + d.set("a", 1); + d.set("b", 2); + d.set("c", 3); + d.set(8, 6); + + expect(e).toEqual(d); + expect(d).toEqual(e); + + let f = new Map(); + f.set("a", 1); + f.set("b", 2); + f.set("c", 3); + f.set(8, 7); + expect(e).not.toEqual(f); + + let g = new Map(); + g.set({ a: { b: { c: 89 } } }, 1); + + let h = new Map(); + h.set({ a: { b: { c: 89 } } }, 1); + expect(g).toEqual(h); + + let i = new Map(); + i.set({ a: { b: { c: 89 } } }, 1); + i.set({ a: { b: { c: 89 } } }, 1); + expect(g).not.toEqual(i); + + let j = new Map(); + j.set({ a: { b: { c: 89 } } }, 1); + j.set({ a: { b: { c: 89 } } }, 1); + expect(i).toEqual(j); + + let p = new Map(); + p.set({ a: { b: { c: 90 } } }, 1); + expect(p).not.toEqual(g); + + let q = new Map(); + q.set({ a: { b: { c: 90 } } }, { a: { b: 45 } }); + + let r = new Map(); + r.set({ a: { b: { c: 90 } } }, { a: { b: 45 } }); + expect(q).toEqual(r); + + let s = new Map(); + s.set({ a: { b: { c: 90 } } }, { a: { b: 49 } }); + expect(q).not.toEqual(s); + + const u = { a: 1, b: 2 }; + + let a = new Set(); + a.add({ a: 1 }); + a.add([1, 2, 3]); + a.add("hello"); + a.add(89); + + let b = new Set(); + b.add({ a: 1 }); + b.add("hello"); + b.add([1, 2, 3]); + b.add(89); + expect(a).toEqual(b); + expect(b).toEqual(a); + let c = new Set(); + c.add(89); + c.add("hello"); + c.add({ a: 1 }); + c.add([1, 2, 3, 4]); + expect(a).not.toEqual(c); +}); + +test("deepEquals - symbols", () => { + const x = [5, 6]; + x[99] = 7; + + const y = [5, 6]; + y[99] = 7; + + expect(x).toEqual(y); + + const s1 = Symbol("test1"); + const s2 = Symbol("test2"); + + const o = { a: 1 }; + o[s1] = 45; + o[99] = 99; + o[s2] = 3; + + const k = { a: 1 }; + k[99] = 99; + k[s2] = 3; + k[s1] = 45; + + expect(o).toEqual(k); +}); + +test("toEqual objects and arrays", () => { + expect("hello").toEqual("hello"); + const s1 = Symbol("test1"); + const s2 = Symbol("test2"); + + expect({ a: 1, b: 2 }).toEqual({ b: 2, a: 1 }); + expect([1, 2, 3]).toEqual([1, 2, 3]); + expect({ a: 1, b: 2 }).not.toEqual({ b: 2, a: 1, c: 3 }); + expect([1, 2, 3]).not.toEqual([1, 2, 3, 4]); + expect({ a: 1, b: 2, c: 3 }).not.toEqual({ a: 1, b: 2 }); + expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]); + + let a = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + let b = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + expect(a).toEqual(b); + expect(b).toEqual(a); + a[0].a = 2; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + let c = { [Symbol("test")]: 1 }; + let d = { [Symbol("test")]: 1 }; + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); + + a = { [s1]: 1 }; + a[s1] = 1; + b = { [s2]: 1 }; + b[s2] = 1; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 2; + expect(a).not.toEqual(b); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 2; + expect(a).toEqual(b); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 3; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = { a: 2, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + expect(a).not.toEqual(b); + + // do the same tests for arrays + a = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + b = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + expect(a).toEqual(b); + expect(b).toEqual(a); + a[0].a = 2; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 2; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 2; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 3; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = [2, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + expect(a).not.toEqual(b); + + // do the same tests for objects and arrays with null and undefined + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = undefined; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = null; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = null; + expect(a).toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = undefined; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = undefined; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = null; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = null; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = undefined; + expect(a).toEqual(b); + + // similar tests for indexed objects + a = { 0: 1, 1: 2, 2: 3 }; + b = { 0: 1, 1: 2, 2: 3 }; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = { 0: 1, 1: 2, 2: 3 }; + b = { 0: 1, 1: 2, 2: 3 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 3; + expect(a).not.toEqual(b); + + a = { 0: 1, 1: 3, 2: 3 }; + b = { 0: 1, 1: 2, 2: 3 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 2; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3, 4]; + expect(a).not.toEqual(b); + + a = [1, 2, 3, 4]; + b = [1, 2, 3]; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2, c: 3 }; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2, c: 3 }; + b = { a: 1, b: 2 }; + expect(a).not.toEqual(b); +}); + +test("symbol based keys in arrays are processed correctly", () => { + const mySymbol = Symbol("test"); + + const actual1 = []; + actual1[mySymbol] = 3; + + const actual2 = []; + actual2[mySymbol] = 4; + + const expected = []; + expected[mySymbol] = 3; + + expect(actual2).not.toEqual(expected); + expect(actual1).toEqual(expected); +}); + +test("non-enumerable members should be skipped during equal", () => { + const actual = { + x: 3, + }; + Object.defineProperty(actual, "test", { + enumerable: false, + value: 5, + }); + expect(actual).toEqual({ x: 3 }); +}); + +test("non-enumerable symbolic members should be skipped during equal", () => { + const actual = { + x: 3, + }; + const mySymbol = Symbol("test"); + Object.defineProperty(actual, mySymbol, { + enumerable: false, + value: 5, + }); + expect(actual).toEqual({ x: 3 }); +}); + +test("properties with the same circularity are equal", () => { + const a = {}; + a.x = a; + const b = {}; + b.x = b; + expect(a).toEqual(b); + expect(b).toEqual(a); + + const c = { + x: a, + }; + const d = { + x: b, + }; + + expect(d).toEqual(c); + expect(c).toEqual(d); +}); + +test("arrays", () => { + expect([1, 2, 3]).toEqual([1, 2, 3]); + expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]); +}); + +test("properties with different circularity are not equal", () => { + const a = {}; + a.x = { y: a }; + const b = {}; + const bx = {}; + b.x = bx; + bx.y = bx; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + const c = {}; + c.x = a; + const d = {}; + d.x = b; + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); +}); + +test("are not equal if circularity is not on the same property", () => { + const a = {}; + const b = {}; + a.a1 = a; + b.a1 = {}; + b.a1.a1 = a; + + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + const c = {}; + c.x = { x: c }; + const d = {}; + d.x = d; + + expect(d).not.toEqual(c); + expect(c).not.toEqual(d); +}); + +test("random isEqual tests", () => { + expect(1).toEqual(1); + expect(1).not.toEqual(2); + expect(1).not.toEqual("1"); + expect(1).not.toEqual(true); + expect(1).not.toEqual(false); + expect(1).not.toEqual(null); + expect(1).not.toEqual(undefined); + expect(1).not.toEqual({}); + expect(1).not.toEqual([]); + expect(1).not.toEqual([1]); + expect(1).not.toEqual([1, 2]); + expect(1).not.toEqual([1, 2, 3]); + expect(1).not.toEqual([1, 2, 3, 4]); + expect(1).not.toEqual([1, 2, 3, 4, 5]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + + // test toEquals for objects with getters and setters + + expect([]).toEqual([]); + expect([1]).toEqual([1]); + expect([1, 2]).toEqual([1, 2]); + expect([1, 2, 3]).toEqual([1, 2, 3]); + expect({}).toEqual({}); + expect({}).not.toEqual([]); + expect([]).not.toEqual({}); + + const obj = { + get a() { + return 1; + }, + }; + expect(obj).toEqual({ a: 1 }); + expect({ a: 1 }).toEqual(obj); + expect(obj).not.toEqual({ a: 2 }); + expect({ a: 2 }).not.toEqual(obj); + + let a = new Set(); + a.add([1, 2, 3]); + a.add("hello"); + a.add({ a: 1 }); + a.add(89); + let b = new Set(); + b.add(89); + b.add({ a: 1 }); + b.add("hello"); + b.add([1, 2, 3]); + expect(a).toEqual(b); + expect(b).toEqual(a); + let c = new Set(); + c.add(89); + c.add("helo"); + c.add({ a: 1 }); + c.add([1, 2, 3]); + expect(a).not.toEqual(c); + + a = new Map(); + a.set(1, 89); + a.set("hello", 2); + a.set({ a: 1 }, 3); + a.set([1, 2, 3], 4); + b = new Map(); + b.set(1, 89); + b.set("hello", 2); + b.set({ a: 1 }, 3); + b.set([1, 2, 3], 4); + expect(a).toEqual(b); + expect(b).toEqual(a); + c = new Map(); + c.set({ a: 1 }, 3); + c.set(1, 80); + c.set([1, 2, 3], 4); + c.set("hello", 2); + expect(a).not.toEqual(c); + + a = new Set(); + a.add(89); + a.add("hello"); + a.add({ a: 1 }); + a.add([1, 2, 3]); + a.add(a); + b = new Set(); + b.add(89); + b.add("hello"); + b.add(b); + b.add({ a: 1 }); + b.add([1, 2, 3]); + expect(a).toEqual(b); + expect(b).toEqual(a); +}); + +test("testing Bun.deepEquals() using isEqual()", () => { + const t = new Uint8Array([1, 2, 3, 4, 5]); + expect(t).toEqual(t.slice()); + + var a = { foo: 1, bar: 2, baz: null }; + var b = { foo: 1, bar: 2, baz: null }; + a.baz = a; + b.baz = b; + expect(a).toEqual(b); + + var a = { car: 1, cdr: { car: 2, cdr: null } }; + var b = { car: 1, cdr: { car: 2, cdr: null } }; + a.cdr.cdr = a; + b.cdr.cdr = b.cdr; + expect(a).not.toEqual(b); + + expect(1n).not.toEqual(1); + expect(1).not.toEqual(1n); + expect(1n).toEqual(1n); + expect(undefined).not.toEqual([]); + + var a = [1, 2, 3, null]; + var b = [1, 2, 3, null]; + a[3] = b; + b[3] = a; + expect(a).toEqual(b); + + var a = [1, 2, 3, null]; + var b = [1, 2, 3, null]; + a[3] = a; + b[3] = a; + expect(a).toEqual(b); + + var a = [1, [2, [3, null]]]; + var b = [1, [2, [3, null]]]; + a[1][1][1] = a; + b[1][1][1] = b[1][1]; + expect(a).not.toEqual(b); + + const foo = [1]; + foo[1] = foo; + + expect(foo).toEqual([1, foo]); + + expect(1).toEqual(1); + expect([1]).toEqual([1]); + + // expect(a).toEqual(a); + expect([1, 2, 3]).toEqual([1, 2, 3]); + + let o = { a: 1, b: 2 }; + expect(o).toEqual(o); + expect(o).toEqual({ a: 1, b: 2 }); + expect(o).toEqual({ b: 2, a: 1 }); + expect({ a: 1, b: 2 }).toEqual(o); + expect({ b: 2, a: 1 }).toEqual(o); + expect(o).not.toEqual({ a: 1, b: 2, c: 3 }); + expect({ a: 1, b: 2, c: 3, d: 4 }).not.toEqual(o); + expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 }); + expect({ a: 1, b: 2 }).not.toEqual({ a: 1 }); + + expect("a").toEqual("a"); + expect("aaaa").toEqual("aaaa"); + expect("aaaa").not.toEqual("aaaaa"); + expect("aaaa").not.toEqual("aaba"); + expect("a").not.toEqual("b"); + + expect(undefined).not.toEqual(null); + expect(null).not.toEqual(undefined); + expect(undefined).not.toEqual(0); + expect(0).not.toEqual(undefined); + expect(null).not.toEqual(0); + expect(0).not.toEqual(null); + expect(undefined).not.toEqual(""); + expect("").not.toEqual(undefined); + expect(null).not.toEqual(""); + expect("").not.toEqual(null); + expect(undefined).not.toEqual(false); + expect(false).not.toEqual(undefined); + expect(null).not.toEqual(false); + expect(false).not.toEqual(null); + expect(undefined).not.toEqual(true); + expect(true).not.toEqual(undefined); + expect(null).not.toEqual(true); + expect(true).not.toEqual(null); + expect([]).not.toEqual(undefined); + expect(null).not.toEqual([]); + expect([]).not.toEqual(null); + + expect(0).toEqual(0); + expect(-0).toEqual(-0); + expect(0).not.toEqual(-0); + expect(-0).not.toEqual(0); + + expect(NaN).toEqual(NaN); + + expect(null).toEqual(null); + expect(undefined).toEqual(undefined); + + expect(1).toEqual(1); + expect(1).not.toEqual(2); + + expect(NaN).toEqual(NaN); + expect(NaN).toEqual(0 / 0); + expect(Infinity).toEqual(Infinity); + expect(Infinity).toEqual(1 / 0); + expect(-Infinity).toEqual(-Infinity); + expect(-Infinity).toEqual(-1 / 0); +}); + +// test("toHaveProperty()", () => { +// const a = new Array(["a", "b", "c"]); +// // expect(a).toHaveProperty("0.1", "b"); +// const b = new Array("a", "b", "c"); +// expect({ a: { b: { c: 1 } } }).toHaveProperty(b); +// const c = { +// a: { b: 1 }, +// "a.b": 2, +// }; +// const d = new Array("a.b"); +// expect(c).toHaveProperty(d, 2); +// const houseForSale = { +// bath: true, +// bedrooms: 4, +// kitchen: { +// amenities: ["oven", "stove", "washer"], +// area: 20, +// wallColor: "white", +// "nice.oven": true, +// }, +// livingroom: { +// amenities: [ +// { +// couch: [ +// ["large", { dimensions: [20, 20] }], +// ["small", { dimensions: [10, 10] }], +// ], +// }, +// ], +// }, +// sunroom: "yes", +// "ceiling.height": 20, +// "nono.nooooo": 3, +// nono: { nooooo: 5 }, +// }; +// expect(houseForSale).toHaveProperty("nono.nooooo"); +// expect(houseForSale).toHaveProperty(["nono", "nooooo"]); +// expect(houseForSale).toHaveProperty(["nono.nooooo"]); +// expect(houseForSale).not.toHaveProperty("."); +// expect(houseForSale).not.toHaveProperty("]"); +// expect(houseForSale).not.toHaveProperty("["); +// expect(houseForSale).not.toHaveProperty("[]"); +// expect(houseForSale).not.toHaveProperty("[[]]"); +// expect(houseForSale).not.toHaveProperty("[["); +// expect(houseForSale).not.toHaveProperty("]]"); +// expect(houseForSale).not.toHaveProperty("[]]"); +// expect(houseForSale).not.toHaveProperty("[[]"); +// expect(houseForSale).not.toHaveProperty(".]"); +// expect(houseForSale).not.toHaveProperty(".["); +// expect(houseForSale).not.toHaveProperty("[."); +// expect(houseForSale).not.toHaveProperty("]."); +// expect(houseForSale).not.toHaveProperty("].["); +// expect(houseForSale).not.toHaveProperty("].]"); +// expect(houseForSale).not.toHaveProperty("[.]"); +// expect(houseForSale).not.toHaveProperty("[.["); +// expect(houseForSale).toHaveProperty("bath"); +// expect(houseForSale).not.toHaveProperty("jacuzzi"); +// // // expect(houseForSale).toHaveProperty("jacuzzi"); +// // // // expect(houseForSale).not.toHaveProperty("bath"); +// expect(houseForSale).toHaveProperty("bath", true); +// expect(houseForSale).not.toHaveProperty("bath", false); +// // // // expect(houseForSale).toHaveProperty("bath", false); +// // // // expect(houseForSale).not.toHaveProperty("bath", true); +// expect(houseForSale).toHaveProperty("bedrooms", 4); +// expect(houseForSale).toHaveProperty(["sunroom"], "yes"); +// expect(houseForSale).toHaveProperty("kitchen.area", 20); +// expect(houseForSale).toHaveProperty("kitchen.amenities", [ +// "oven", +// "stove", +// "washer", +// ]); +// expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 21); +// expect(houseForSale).toHaveProperty(["kitchen", "area"], 20); +// expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 29); +// expect(houseForSale).toHaveProperty( +// ["kitchen", "amenities"], +// ["oven", "stove", "washer"], +// ); +// expect(houseForSale).toHaveProperty("kitchen.amenities[2]", "washer"); +// expect(houseForSale).toHaveProperty(["kitchen", "amenities", 1], "stove"); +// expect(houseForSale).toHaveProperty(["kitchen", "amenities", 0], "oven"); +// expect(houseForSale).toHaveProperty( +// "livingroom.amenities[0].couch[0][1].dimensions[0]", +// 20, +// ); +// expect(houseForSale).toHaveProperty(["kitchen", "nice.oven"]); +// expect(houseForSale).not.toHaveProperty(["kitchen", "open"]); +// expect(houseForSale).toHaveProperty(["ceiling.height"], 20); +// expect({ a: { b: 1 } }).toHaveProperty("a.b"); +// expect({ a: [2, 3, 4] }).toHaveProperty("a.0"); +// expect({ a: [2, 3, 4] }).toHaveProperty("a.1"); +// expect({ a: [2, 3, 4] }).toHaveProperty("a.2"); +// expect({ a: [2, 3, 4] }).toHaveProperty("a[1]"); +// expect([2, 3, 4]).toHaveProperty("1"); +// expect([2, 3, 4]).toHaveProperty("[1]"); +// expect([2, [6, 9], 4]).toHaveProperty("1.1"); +// expect([2, [6, 9], 4]).toHaveProperty("1[1]"); +// expect([2, [6, 9], 4]).toHaveProperty("[1].1"); +// expect([2, [6, 9], 4]).toHaveProperty("[1][1]"); +// expect([2, [6, 9], 4]).toHaveProperty([0], 2); +// expect({ a: { b: 1 } }).toHaveProperty("a.b"); +// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a.2.1.b"); +// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a"); +// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1].b"); +// expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1]"); +// expect({ a: [1, 2, [3, { b: 1 }]] }).not.toHaveProperty("a[2][1].c"); +// expect("test").toHaveProperty("length"); +// expect({}).toHaveProperty("constructor"); +// expect({}).toHaveProperty("constructor.name"); +// expect({}).toHaveProperty("constructor.name", "Object"); +// expect(new Date()).toHaveProperty("getTime"); +// }); test("toBe()", () => { const a = 1; @@ -52,7 +1064,7 @@ test("toContain()", () => { expect("test").toContain("es"); expect("test").toContain("est"); - expect("test").toContain("test"); + // expect("test").not.toContain("test"); expect(["test", "es"]).toContain("es"); expect("").toContain(""); expect([""]).toContain(""); @@ -63,6 +1075,15 @@ test("toContain()", () => { const a = new Uint16Array([1, 2, 3]); expect(a).toContain(2); expect(a).not.toContain(4); + expect([2, "2335", 5, true, false, null, undefined]).toContain(5); + expect([2, "2335", 5, true, false, null, undefined]).toContain("2335"); + expect([2, "2335", 5, true, false, null, undefined]).toContain(true); + expect([2, "2335", 5, true, false, null, undefined]).toContain(false); + expect([2, "2335", 5, true, false, null, undefined]).toContain(null); + expect([2, "2335", 5, true, false, null, undefined]).toContain(undefined); + expect([2, "2335", 5, true, false, null, undefined]).not.toContain(3); + expect([2, "2335", 5, true, false, null, undefined]).not.not.not.toContain(3); + // expect([4, 5, 6]).not.toContain(5); expect([]).not.toContain([]); |