aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbench/bun.lockbbin35685 -> 36074 bytes
-rw-r--r--bench/package.json3
-rw-r--r--bench/snippets/deep-equals.js509
-rw-r--r--src/bun.js/bindings/OnigurumaRegExp.cpp64
-rw-r--r--src/bun.js/bindings/OnigurumaRegExp.h64
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp31
-rw-r--r--src/bun.js/bindings/bindings.cpp540
-rw-r--r--src/bun.js/bindings/bindings.zig6
-rw-r--r--src/bun.js/bindings/headers-handwritten.h2
-rw-r--r--src/bun.js/bindings/headers.h3
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/test/jest.zig41
-rw-r--r--test/bun.js/test-test.test.ts1025
13 files changed, 2220 insertions, 69 deletions
diff --git a/bench/bun.lockb b/bench/bun.lockb
index 14530a5ec..8fea41bc1 100755
--- a/bench/bun.lockb
+++ b/bench/bun.lockb
Binary files differ
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([]);