mirror of
https://github.com/toss/es-toolkit.git
synced 2024-11-24 11:45:26 +03:00
feat(isEqual): Implement isEqual
This commit is contained in:
parent
db79ff3355
commit
ce412ff996
14
benchmarks/bundle-size/isEqual.spec.ts
Normal file
14
benchmarks/bundle-size/isEqual.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { getBundleSize } from "./utils/getBundleSize"
|
||||
|
||||
describe('isEqual bundle size', () => {
|
||||
it('lodash-es', async () => {
|
||||
const bundleSize = await getBundleSize('lodash-es', 'isEqual');
|
||||
expect(bundleSize).toMatchInlineSnapshot(`12872`);
|
||||
});
|
||||
|
||||
it('es-toolkit', async () => {
|
||||
const bundleSize = await getBundleSize('es-toolkit', 'isEqual');
|
||||
expect(bundleSize).toMatchInlineSnapshot(`2728`);
|
||||
})
|
||||
});
|
@ -9,6 +9,6 @@ describe('zipObjectDeep bundle size', () => {
|
||||
|
||||
it('es-toolkit/compat', async () => {
|
||||
const bundleSize = await getBundleSize('es-toolkit/compat', 'zipObjectDeep');
|
||||
expect(bundleSize).toMatchInlineSnapshot(`938`);
|
||||
expect(bundleSize).toMatchInlineSnapshot(`992`);
|
||||
})
|
||||
});
|
@ -9,7 +9,7 @@
|
||||
"esbuild": "0.23.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"vitest": "^2.0.2"
|
||||
"vitest": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4",
|
||||
|
@ -2,54 +2,81 @@ import { bench, describe } from 'vitest';
|
||||
import { isEqual as isEqualToolkit } from 'es-toolkit';
|
||||
import { isEqual as isEqualLodash } from 'lodash';
|
||||
|
||||
describe('isEqual', () => {
|
||||
// describe('isEqual primitives', () => {
|
||||
// bench('es-toolkit/isEqual', () => {
|
||||
// isEqualToolkit(1, 1);
|
||||
// isEqualToolkit(NaN, NaN);
|
||||
// isEqualToolkit(+0, -0);
|
||||
|
||||
// isEqualToolkit(true, true);
|
||||
// isEqualToolkit(true, false);
|
||||
|
||||
// isEqualToolkit('hello', 'hello');
|
||||
// isEqualToolkit('hello', 'world');
|
||||
// });
|
||||
|
||||
// bench('lodash/isEqual', () => {
|
||||
// isEqualLodash(1, 1);
|
||||
// isEqualLodash(NaN, NaN);
|
||||
// isEqualLodash(+0, -0);
|
||||
|
||||
// isEqualLodash(true, true);
|
||||
// isEqualLodash(true, false);
|
||||
|
||||
// isEqualLodash('hello', 'hello');
|
||||
// isEqualLodash('hello', 'world');
|
||||
// });
|
||||
// });
|
||||
|
||||
|
||||
// describe('isEqual dates', () => {
|
||||
// bench('es-toolkit/isEqual', () => {
|
||||
// isEqualToolkit(new Date('2020-01-01'), new Date('2020-01-01'));
|
||||
// isEqualToolkit(new Date('2020-01-01'), new Date('2021-01-01'));
|
||||
// });
|
||||
|
||||
// bench('lodash', () => {
|
||||
// isEqualLodash(new Date('2020-01-01'), new Date('2020-01-01'));
|
||||
// isEqualLodash(new Date('2020-01-01'), new Date('2021-01-01'));
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('isEqual RegExps', () => {
|
||||
// bench('es-toolkit/isEqual', () => {
|
||||
// isEqualToolkit(/hello/g, /hello/g);
|
||||
// isEqualToolkit(/hello/g, /hello/i);
|
||||
// });
|
||||
|
||||
// bench('lodash', () => {
|
||||
// isEqualLodash(/hello/g, /hello/g);
|
||||
// isEqualLodash(/hello/g, /hello/i);
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('isEqual objects', () => {
|
||||
bench('es-toolkit/isEqual', () => {
|
||||
isEqualToolkit(1, 1);
|
||||
isEqualToolkit('hello', 'hello');
|
||||
isEqualToolkit(true, true);
|
||||
|
||||
isEqualToolkit('hello', 'world');
|
||||
isEqualToolkit(true, false);
|
||||
|
||||
isEqualToolkit(NaN, NaN);
|
||||
isEqualToolkit(+0, -0);
|
||||
|
||||
isEqualToolkit(new Date('2020-01-01'), new Date('2020-01-01'));
|
||||
isEqualToolkit(new Date('2020-01-01'), new Date('2021-01-01'));
|
||||
|
||||
isEqualToolkit(/hello/g, /hello/g);
|
||||
isEqualToolkit(/hello/g, /hello/i);
|
||||
|
||||
isEqualToolkit({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } });
|
||||
isEqualToolkit({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } });
|
||||
isEqualToolkit({ a: 1, b: 2 }, { a: 1, b: 2 });
|
||||
|
||||
isEqualToolkit([1, 2, 3], [1, 2, 3]);
|
||||
isEqualToolkit([1, 2, 3], [1, 2, 4]);
|
||||
});
|
||||
|
||||
bench('lodash/isEqual', () => {
|
||||
isEqualLodash(1, 1);
|
||||
isEqualLodash('hello', 'hello');
|
||||
isEqualLodash(true, true);
|
||||
|
||||
isEqualLodash('hello', 'world');
|
||||
isEqualLodash(true, false);
|
||||
|
||||
isEqualLodash(NaN, NaN);
|
||||
isEqualLodash(+0, -0);
|
||||
|
||||
isEqualLodash(new Date('2020-01-01'), new Date('2020-01-01'));
|
||||
isEqualLodash(new Date('2020-01-01'), new Date('2021-01-01'));
|
||||
|
||||
isEqualLodash(/hello/g, /hello/g);
|
||||
isEqualLodash(/hello/g, /hello/i);
|
||||
|
||||
bench('lodash', () => {
|
||||
isEqualLodash({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } });
|
||||
isEqualLodash({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } });
|
||||
isEqualLodash({ a: 1, b: 2 }, { a: 1, b: 2 });
|
||||
|
||||
isEqualLodash([1, 2, 3], [1, 2, 3]);
|
||||
isEqualLodash([1, 2, 3], [1, 2, 4]);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
// describe('isEqual arrays', () => {
|
||||
// bench('es-toolkit/isEqual', () => {
|
||||
// isEqualToolkit([1, 2, 3], [1, 2, 3]);
|
||||
// isEqualToolkit([1, 2, 3], [1, 2, 4]);
|
||||
// });
|
||||
|
||||
// bench('lodash', () => {
|
||||
// isEqualLodash([1, 2, 3], [1, 2, 3]);
|
||||
// isEqualLodash([1, 2, 3], [1, 2, 4]);
|
||||
// });
|
||||
// })
|
||||
|
||||
|
||||
|
@ -204,7 +204,7 @@ Even if a feature is marked "in review," it might already be under review to ens
|
||||
| [isDate](https://lodash.com/docs/4.17.15#isDate) | ❌ |
|
||||
| [isElement](https://lodash.com/docs/4.17.15#isElement) | ❌ |
|
||||
| [isEmpty](https://lodash.com/docs/4.17.15#isEmpty) | ❌ |
|
||||
| [isEqual](https://lodash.com/docs/4.17.15#isEqual) | ❌ |
|
||||
| [isEqual](https://lodash.com/docs/4.17.15#isEqual) | ✅ |
|
||||
| [isEqualWith](https://lodash.com/docs/4.17.15#isEqualWith) | ❌ |
|
||||
| [isError](https://lodash.com/docs/4.17.15#isError) | ❌ |
|
||||
| [isFinite](https://lodash.com/docs/4.17.15#isFinite) | ❌ |
|
||||
|
@ -205,7 +205,7 @@ chunk([1, 2, 3, 4], 0);
|
||||
| [isDate](https://lodash.com/docs/4.17.15#isDate) | ❌ |
|
||||
| [isElement](https://lodash.com/docs/4.17.15#isElement) | ❌ |
|
||||
| [isEmpty](https://lodash.com/docs/4.17.15#isEmpty) | ❌ |
|
||||
| [isEqual](https://lodash.com/docs/4.17.15#isEqual) | ❌ |
|
||||
| [isEqual](https://lodash.com/docs/4.17.15#isEqual) | ✅ |
|
||||
| [isEqualWith](https://lodash.com/docs/4.17.15#isEqualWith) | ❌ |
|
||||
| [isError](https://lodash.com/docs/4.17.15#isError) | ❌ |
|
||||
| [isFinite](https://lodash.com/docs/4.17.15#isFinite) | ❌ |
|
||||
|
@ -204,7 +204,7 @@ chunk([1, 2, 3, 4], 0);
|
||||
| [isDate](https://lodash.com/docs/4.17.15#isDate) | ❌ |
|
||||
| [isElement](https://lodash.com/docs/4.17.15#isElement) | ❌ |
|
||||
| [isEmpty](https://lodash.com/docs/4.17.15#isEmpty) | ❌ |
|
||||
| [isEqual](https://lodash.com/docs/4.17.15#isEqual) | ❌ |
|
||||
| [isEqual](https://lodash.com/docs/4.17.15#isEqual) | ✅ |
|
||||
| [isEqualWith](https://lodash.com/docs/4.17.15#isEqualWith) | ❌ |
|
||||
| [isError](https://lodash.com/docs/4.17.15#isError) | ❌ |
|
||||
| [isFinite](https://lodash.com/docs/4.17.15#isFinite) | ❌ |
|
||||
|
6
src/compat/_internal/arrayViews.ts
Normal file
6
src/compat/_internal/arrayViews.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { typedArrays } from "./typedArrays";
|
||||
|
||||
export const arrayViews = [
|
||||
...typedArrays,
|
||||
'DataView'
|
||||
];
|
4
src/compat/_internal/getSymbols.ts
Normal file
4
src/compat/_internal/getSymbols.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export function getSymbols(object: {}) {
|
||||
return Object.getOwnPropertySymbols(object)
|
||||
.filter(symbol => object.propertyIsEnumerable(symbol));
|
||||
}
|
26
src/compat/_internal/tags.ts
Normal file
26
src/compat/_internal/tags.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export const regexpTag = '[object RegExp]';
|
||||
export const stringTag = '[object String]';
|
||||
export const numberTag = '[object Number]';
|
||||
export const booleanTag = '[object Boolean]';
|
||||
export const argumentsTag = '[object Arguments]';
|
||||
export const symbolTag = '[object Symbol]';
|
||||
export const dateTag = '[object Date]';
|
||||
export const mapTag = '[object Map]';
|
||||
export const setTag = '[object Set]';
|
||||
export const arrayTag = '[object Array]';
|
||||
export const functionTag = '[object Function]';
|
||||
export const arrayBufferTag = '[object ArrayBuffer]';
|
||||
export const objectTag = '[object Object]';
|
||||
export const errorTag = '[object Error]';
|
||||
export const dataViewTag = '[object DataView]'
|
||||
export const uint8ArrayTag = '[object Uint8Array]';
|
||||
export const uint8ClampedArrayTag = '[object Uint8ClampedArray]';
|
||||
export const uint16ArrayTag = '[object Uint16Array]';
|
||||
export const uint32ArrayTag = '[object Uint32Array]';
|
||||
export const bigUint64ArrayTag = '[object BigUint64Array]';
|
||||
export const int8ArrayTag = '[object Int8Array]';
|
||||
export const int16ArrayTag = '[object Int16Array]';
|
||||
export const int32ArrayTag = '[object Int32Array]';
|
||||
export const bigInt64ArrayTag = '[object BigInt64Array]';
|
||||
export const float32ArrayTag = '[object Float32Array]';
|
||||
export const float64ArrayTag = '[object Float64Array]';
|
@ -1,4 +1,5 @@
|
||||
import { cloneDeep as cloneDeepToolkit, copyProperties } from '../../object/cloneDeep.ts';
|
||||
import { argumentsTag, booleanTag, numberTag, stringTag } from '../_internal/tags.ts';
|
||||
|
||||
/**
|
||||
* Creates a deep clone of the given object.
|
||||
@ -52,9 +53,9 @@ export function cloneDeep<T>(obj: T): T {
|
||||
}
|
||||
|
||||
switch (Object.prototype.toString.call(obj)) {
|
||||
case '[object Number]':
|
||||
case '[object String]':
|
||||
case '[object Boolean]': {
|
||||
case numberTag:
|
||||
case stringTag:
|
||||
case booleanTag: {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const result = new obj.constructor(obj?.valueOf()) as T;
|
||||
@ -62,7 +63,7 @@ export function cloneDeep<T>(obj: T): T {
|
||||
return result;
|
||||
}
|
||||
|
||||
case '[object Arguments]': {
|
||||
case argumentsTag: {
|
||||
const result = {} as any;
|
||||
|
||||
copyProperties(result, obj);
|
||||
|
736
src/compat/predicate/isEqual.spec.ts
Normal file
736
src/compat/predicate/isEqual.spec.ts
Normal file
@ -0,0 +1,736 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { noop } from "../../function/noop";
|
||||
import { stubFalse } from "../_internal/stubFalse";
|
||||
import { isEqual } from "es-toolkit/compat";
|
||||
import { args } from "../_internal/args";
|
||||
import { arrayViews } from "../_internal/arrayViews";
|
||||
|
||||
|
||||
describe('isEqual', () => {
|
||||
const symbol1 = Symbol ? Symbol('a') : true;
|
||||
const symbol2 = Symbol ? Symbol('b') : false;
|
||||
|
||||
it('should compare primitives', () => {
|
||||
const pairs = [
|
||||
[1, 1, true],
|
||||
[1, Object(1), true],
|
||||
[1, '1', false],
|
||||
[1, 2, false],
|
||||
[-0, -0, true],
|
||||
[0, 0, true],
|
||||
[0, Object(0), true],
|
||||
[Object(0), Object(0), true],
|
||||
[-0, 0, true],
|
||||
[0, '0', false],
|
||||
[0, null, false],
|
||||
[NaN, NaN, true],
|
||||
[NaN, Object(NaN), true],
|
||||
[Object(NaN), Object(NaN), true],
|
||||
[NaN, 'a', false],
|
||||
[NaN, Infinity, false],
|
||||
['a', 'a', true],
|
||||
['a', Object('a'), true],
|
||||
[Object('a'), Object('a'), true],
|
||||
['a', 'b', false],
|
||||
['a', ['a'], false],
|
||||
[true, true, true],
|
||||
[true, Object(true), true],
|
||||
[Object(true), Object(true), true],
|
||||
[true, 1, false],
|
||||
[true, 'a', false],
|
||||
[false, false, true],
|
||||
[false, Object(false), true],
|
||||
[Object(false), Object(false), true],
|
||||
[false, 0, false],
|
||||
[false, '', false],
|
||||
[symbol1, symbol1, true],
|
||||
[symbol1, Object(symbol1), true],
|
||||
[Object(symbol1), Object(symbol1), true],
|
||||
[symbol1, symbol2, false],
|
||||
[null, null, true],
|
||||
[null, undefined, false],
|
||||
[null, {}, false],
|
||||
[null, '', false],
|
||||
[undefined, undefined, true],
|
||||
[undefined, null, false],
|
||||
[undefined, '', false],
|
||||
];
|
||||
|
||||
const expected = pairs.map((pair) => pair[2]);
|
||||
|
||||
const actual = pairs.map((pair) => isEqual(pair[0], pair[1]));
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should compare arrays', () => {
|
||||
let array1: unknown[] = [true, null, 1, 'a', undefined];
|
||||
let array2: unknown[] = [true, null, 1, 'a', undefined];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { e: 1 }];
|
||||
array2 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { e: 1 }];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1 = [1];
|
||||
array1[2] = 3;
|
||||
|
||||
array2 = [1];
|
||||
array2[1] = undefined;
|
||||
array2[2] = 3;
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1 = [
|
||||
Object(1),
|
||||
false,
|
||||
Object('a'),
|
||||
/x/,
|
||||
new Date(2012, 4, 23),
|
||||
['a', 'b', [Object('c')]],
|
||||
{ a: 1 },
|
||||
];
|
||||
array2 = [
|
||||
1,
|
||||
Object(false),
|
||||
'a',
|
||||
/x/,
|
||||
new Date(2012, 4, 23),
|
||||
['a', Object('b'), ['c']],
|
||||
{ a: 1 },
|
||||
];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1 = [1, 2, 3];
|
||||
array2 = [3, 2, 1];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(false);
|
||||
|
||||
array1 = [1, 2];
|
||||
array2 = [1, 2, 3];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should treat arrays with identical values but different non-index properties as equal', () => {
|
||||
let array1: any = [1, 2, 3];
|
||||
let array2: any = [1, 2, 3];
|
||||
|
||||
array1.every =
|
||||
array1.filter =
|
||||
array1.forEach =
|
||||
array1.indexOf =
|
||||
array1.lastIndexOf =
|
||||
array1.map =
|
||||
array1.some =
|
||||
array1.reduce =
|
||||
array1.reduceRight =
|
||||
null;
|
||||
|
||||
array2.concat =
|
||||
array2.join =
|
||||
array2.pop =
|
||||
array2.reverse =
|
||||
array2.shift =
|
||||
array2.slice =
|
||||
array2.sort =
|
||||
array2.splice =
|
||||
array2.unshift =
|
||||
null;
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1 = [1, 2, 3];
|
||||
array1.a = 1;
|
||||
|
||||
array2 = [1, 2, 3];
|
||||
array2.b = 1;
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1 = /c/.exec('abcde');
|
||||
array2 = ['c'];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
});
|
||||
|
||||
it('should compare sparse arrays', () => {
|
||||
const array = Array(1);
|
||||
|
||||
expect(isEqual(array, Array(1))).toBe(true);
|
||||
expect(isEqual(array, [undefined])).toBe(true);
|
||||
expect(isEqual(array, Array(2))).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare plain objects', () => {
|
||||
let object1: any = { a: true, b: null, c: 1, d: 'a', e: undefined };
|
||||
let object2: any = { a: true, b: null, c: 1, d: 'a', e: undefined };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
|
||||
object1 = { a: [1, 2, 3], b: new Date(2012, 4, 23), c: /x/, d: { e: 1 } };
|
||||
object2 = { a: [1, 2, 3], b: new Date(2012, 4, 23), c: /x/, d: { e: 1 } };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
|
||||
object1 = { a: 1, b: 2, c: 3 };
|
||||
object2 = { a: 3, b: 2, c: 1 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
|
||||
object1 = { a: 1, b: 2, c: 3 };
|
||||
object2 = { d: 1, e: 2, f: 3 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
|
||||
object1 = { a: 1, b: 2 };
|
||||
object2 = { a: 1, b: 2, c: 3 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare objects regardless of key order', () => {
|
||||
const object1 = { a: 1, b: 2, c: 3 };
|
||||
const object2 = { c: 3, a: 1, b: 2 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
});
|
||||
|
||||
it('should compare nested objects', () => {
|
||||
const object1 = {
|
||||
a: [1, 2, 3],
|
||||
b: true,
|
||||
c: Object(1),
|
||||
d: 'a',
|
||||
e: {
|
||||
f: ['a', Object('b'), 'c'],
|
||||
g: Object(false),
|
||||
h: new Date(2012, 4, 23),
|
||||
i: noop,
|
||||
j: 'a',
|
||||
},
|
||||
};
|
||||
|
||||
const object2 = {
|
||||
a: [1, Object(2), 3],
|
||||
b: Object(true),
|
||||
c: 1,
|
||||
d: Object('a'),
|
||||
e: {
|
||||
f: ['a', 'b', 'c'],
|
||||
g: false,
|
||||
h: new Date(2012, 4, 23),
|
||||
i: noop,
|
||||
j: 'a',
|
||||
},
|
||||
};
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
});
|
||||
|
||||
it('should compare object instances', () => {
|
||||
function Foo() {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
this.a = 1;
|
||||
}
|
||||
Foo.prototype.a = 1;
|
||||
|
||||
function Bar() {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
this.a = 1;
|
||||
}
|
||||
Bar.prototype.a = 2;
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual(new Foo(), new Foo())).toBe(true);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual(new Foo(), new Bar())).toBe(false);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual({ a: 1 }, new Foo())).toBe(false);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual({ a: 2 }, new Bar())).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare objects with constructor properties', () => {
|
||||
expect(isEqual({ constructor: 1 }, { constructor: 1 })).toBe(true);
|
||||
expect(isEqual({ constructor: 1 }, { constructor: '1' })).toBe(false);
|
||||
expect(isEqual({ constructor: [1] }, { constructor: [1] })).toBe(true);
|
||||
expect(isEqual({ constructor: [1] }, { constructor: ['1'] })).toBe(false);
|
||||
expect(isEqual({ constructor: Object }, {})).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare arrays with circular references', () => {
|
||||
let array1: any[] = [];
|
||||
let array2: any[] = [];
|
||||
|
||||
array1.push(array1);
|
||||
array2.push(array2);
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1.push('b');
|
||||
array2.push('b');
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1.push('c');
|
||||
array2.push('d');
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(false);
|
||||
|
||||
array1 = ['a', 'b', 'c'];
|
||||
array1[1] = array1;
|
||||
array2 = ['a', ['a', 'b', 'c'], 'c'];
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should have transitive equivalence for circular references of arrays', () => {
|
||||
const array1: any[] = [];
|
||||
const array2: any[] = [array1];
|
||||
const array3: any[] = [array2];
|
||||
|
||||
array1[0] = array1;
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
expect(isEqual(array2, array3)).toBe(true);
|
||||
expect(isEqual(array1, array3)).toBe(true);
|
||||
});
|
||||
|
||||
it('should compare objects with circular references', () => {
|
||||
let object1: any = {};
|
||||
let object2: any = {};
|
||||
|
||||
object1.a = object1;
|
||||
object2.a = object2;
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
|
||||
object1.b = 0;
|
||||
object2.b = Object(0);
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
|
||||
object1.c = Object(1);
|
||||
object2.c = Object(2);
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
|
||||
object1 = { a: 1, b: 2, c: 3 };
|
||||
object1.b = object1;
|
||||
object2 = { a: 1, b: { a: 1, b: 2, c: 3 }, c: 3 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should have transitive equivalence for circular references of objects', () => {
|
||||
const object1: any = {};
|
||||
const object2: any = { a: object1 };
|
||||
const object3: any = { a: object2 };
|
||||
|
||||
object1.a = object1;
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
expect(isEqual(object2, object3)).toBe(true);
|
||||
expect(isEqual(object1, object3)).toBe(true);
|
||||
});
|
||||
|
||||
it('should compare objects with multiple circular references', () => {
|
||||
const array1: any = [{}];
|
||||
const array2: any = [{}];
|
||||
|
||||
(array1[0].a = array1).push(array1);
|
||||
(array2[0].a = array2).push(array2);
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1[0].b = 0;
|
||||
array2[0].b = Object(0);
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(true);
|
||||
|
||||
array1[0].c = Object(1);
|
||||
array2[0].c = Object(2);
|
||||
|
||||
expect(isEqual(array1, array2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare objects with complex circular references', () => {
|
||||
const object1: any = {
|
||||
foo: { b: { c: { d: {} } } },
|
||||
bar: { a: 2 },
|
||||
};
|
||||
|
||||
const object2: any = {
|
||||
foo: { b: { c: { d: {} } } },
|
||||
bar: { a: 2 },
|
||||
};
|
||||
|
||||
object1.foo.b.c.d = object1;
|
||||
object1.bar.b = object1.foo.b;
|
||||
|
||||
object2.foo.b.c.d = object2;
|
||||
object2.bar.b = object2.foo.b;
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
});
|
||||
|
||||
it('should compare objects with shared property values', () => {
|
||||
const object1: any = {
|
||||
a: [1, 2],
|
||||
};
|
||||
|
||||
const object2: any = {
|
||||
a: [1, 2],
|
||||
b: [1, 2],
|
||||
};
|
||||
|
||||
object1.b = object1.a;
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
});
|
||||
|
||||
it('should treat objects created by `Object.create(null)` like plain objects', () => {
|
||||
function Foo() {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
this.a = 1;
|
||||
}
|
||||
Foo.prototype.constructor = null;
|
||||
|
||||
const object1 = Object.create(null);
|
||||
object1.a = 1;
|
||||
|
||||
const object2 = { a: 1 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual(new Foo(), object2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should avoid common type coercions', () => {
|
||||
expect(isEqual(true, Object(false))).toBe(false);
|
||||
expect(isEqual(Object(false), Object(0))).toBe(false);
|
||||
expect(isEqual(false, Object(''))).toBe(false);
|
||||
expect(isEqual(Object(36), Object('36'))).toBe(false);
|
||||
expect(isEqual(0, '')).toBe(false);
|
||||
expect(isEqual(1, true)).toBe(false);
|
||||
expect(isEqual(1337756400000, new Date(2012, 4, 23))).toBe(false);
|
||||
expect(isEqual('36', 36)).toBe(false);
|
||||
expect(isEqual(36, '36')).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare `arguments` objects', () => {
|
||||
const args1 = (function () {
|
||||
return arguments;
|
||||
})();
|
||||
const args2 = (function () {
|
||||
return arguments;
|
||||
})();
|
||||
const args3 = (function (..._: any[]) {
|
||||
return arguments;
|
||||
})(1, 2);
|
||||
|
||||
expect(isEqual(args1, args2)).toBe(true);
|
||||
expect(isEqual(args1, args3)).toBe(false);
|
||||
});
|
||||
|
||||
it('should treat `arguments` objects like `Object` objects', () => {
|
||||
const object = { 0: 1, 1: 2, 2: 3 };
|
||||
|
||||
function Foo() { }
|
||||
Foo.prototype = object;
|
||||
|
||||
expect(isEqual(args, object)).toBe(true);
|
||||
expect(isEqual(object, args)).toBe(true);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual(args, new Foo())).toBe(false);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(isEqual(new Foo(), args)).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare array buffers', () => {
|
||||
const buffer = new Int8Array([-1]).buffer;
|
||||
|
||||
expect(isEqual(buffer, new Uint8Array([255]).buffer)).toBe(true);
|
||||
expect(isEqual(buffer, new ArrayBuffer(1))).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare array views', () => {
|
||||
const pairs = arrayViews.map((type, viewIndex) => {
|
||||
const otherType = arrayViews[(viewIndex + 1) % arrayViews.length];
|
||||
const CtorA =
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
globalThis[type] ||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
function (n) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
this.n = n;
|
||||
};
|
||||
const CtorB =
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
globalThis[otherType] ||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
function (n) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
this.n = n;
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const bufferA = globalThis[type] ? new ArrayBuffer(8) : 8;
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const bufferB = globalThis[otherType] ? new ArrayBuffer(8) : 8;
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const bufferC = globalThis[otherType] ? new ArrayBuffer(16) : 16;
|
||||
|
||||
return [
|
||||
new CtorA(bufferA),
|
||||
new CtorA(bufferA),
|
||||
new CtorB(bufferB),
|
||||
new CtorB(bufferC),
|
||||
];
|
||||
});
|
||||
|
||||
const expected = pairs.map(() => [true, false, false]);
|
||||
|
||||
const actual = pairs.map((pair) => [
|
||||
isEqual(pair[0], pair[1]),
|
||||
isEqual(pair[0], pair[2]),
|
||||
isEqual(pair[2], pair[3]),
|
||||
]);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should compare buffers', () => {
|
||||
const buffer = Buffer.from([1]);
|
||||
|
||||
expect(isEqual(buffer, Buffer.from([1]))).toBe(true);
|
||||
expect(isEqual(buffer, Buffer.from([2]))).toBe(false);
|
||||
expect(isEqual(buffer, new Uint8Array([1]))).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare date objects', () => {
|
||||
const date = new Date(2012, 4, 23);
|
||||
|
||||
expect(isEqual(date, new Date(2012, 4, 23))).toBe(true);
|
||||
expect(isEqual(new Date('a'), new Date('b'))).toBe(true);
|
||||
expect(isEqual(date, new Date(2013, 3, 25))).toBe(false);
|
||||
expect(isEqual(date, { getTime: () => +date })).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare error objects', () => {
|
||||
const pairs = [
|
||||
'Error',
|
||||
'EvalError',
|
||||
'RangeError',
|
||||
'ReferenceError',
|
||||
'SyntaxError',
|
||||
'TypeError',
|
||||
'URIError',
|
||||
].map((type, index, errorTypes) => {
|
||||
const otherType = errorTypes[++index % errorTypes.length];
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const CtorA = globalThis[type];
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const CtorB = globalThis[otherType];
|
||||
|
||||
return [new CtorA('a'), new CtorA('a'), new CtorB('a'), new CtorB('b')];
|
||||
});
|
||||
|
||||
const expected = pairs.map(() => [true, false, false]);
|
||||
|
||||
const actual = pairs.map((pair) => [
|
||||
isEqual(pair[0], pair[1]),
|
||||
isEqual(pair[0], pair[2]),
|
||||
isEqual(pair[2], pair[3]),
|
||||
]);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should compare functions', () => {
|
||||
function a() {
|
||||
return 1 + 2;
|
||||
}
|
||||
function b() {
|
||||
return 1 + 2;
|
||||
}
|
||||
|
||||
expect(isEqual(a, a)).toBe(true);
|
||||
expect(isEqual(a, b)).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare maps', () => {
|
||||
[
|
||||
[new Map(), new Map()],
|
||||
].forEach((maps) => {
|
||||
const map1 = maps[0];
|
||||
const map2 = maps[1];
|
||||
|
||||
map1.set('a', 1);
|
||||
map2.set('b', 2);
|
||||
expect(isEqual(map1, map2)).toBe(false);
|
||||
|
||||
map1.set('b', 2);
|
||||
map2.set('a', 1);
|
||||
expect(isEqual(map1, map2)).toBe(true);
|
||||
|
||||
map1.delete('a');
|
||||
map1.set('a', 1);
|
||||
expect(isEqual(map1, map2)).toBe(true);
|
||||
|
||||
map2.delete('a');
|
||||
expect(isEqual(map1, map2)).toBe(false);
|
||||
|
||||
map1.clear();
|
||||
map2.clear();
|
||||
});
|
||||
});
|
||||
|
||||
it('should compare maps with circular references', () => {
|
||||
const map1 = new Map();
|
||||
const map2 = new Map();
|
||||
|
||||
map1.set('a', map1);
|
||||
map2.set('a', map2);
|
||||
expect(isEqual(map1, map2)).toBe(true);
|
||||
|
||||
map1.set('b', 1);
|
||||
map2.set('b', 2);
|
||||
expect(isEqual(map1, map2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare promises by reference', () => {
|
||||
[
|
||||
[Promise.resolve(1), Promise.resolve(1)],
|
||||
].forEach(
|
||||
(promises) => {
|
||||
const promise1 = promises[0];
|
||||
const promise2 = promises[1];
|
||||
|
||||
expect(isEqual(promise1, promise2)).toBe(false);
|
||||
expect(isEqual(promise1, promise1)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should compare regexes', () => {
|
||||
expect(isEqual(/x/gim, /x/gim)).toBe(true);
|
||||
expect(isEqual(/x/gim, /x/gim)).toBe(true);
|
||||
expect(isEqual(/x/gi, /x/g)).toBe(false);
|
||||
expect(isEqual(/x/, /y/)).toBe(false);
|
||||
|
||||
expect(
|
||||
isEqual(/x/g, { global: true, ignoreCase: false, multiline: false, source: 'x' })
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare sets', () => {
|
||||
[
|
||||
[new Set(), new Set()],
|
||||
].forEach((sets) => {
|
||||
const set1 = sets[0];
|
||||
const set2 = sets[1];
|
||||
|
||||
set1.add(1);
|
||||
set2.add(2);
|
||||
expect(isEqual(set1, set2)).toBe(false);
|
||||
|
||||
set1.add(2);
|
||||
set2.add(1);
|
||||
expect(isEqual(set1, set2)).toBe(true);
|
||||
|
||||
set1.delete(1);
|
||||
set1.add(1);
|
||||
expect(isEqual(set1, set2)).toBe(true);
|
||||
|
||||
set2.delete(1);
|
||||
expect(isEqual(set1, set2)).toBe(false);
|
||||
|
||||
set1.clear();
|
||||
set2.clear();
|
||||
});
|
||||
});
|
||||
|
||||
it('should compare sets with circular references', () => {
|
||||
const set1 = new Set();
|
||||
const set2 = new Set();
|
||||
|
||||
set1.add(set1);
|
||||
set2.add(set2);
|
||||
expect(isEqual(set1, set2)).toBe(true);
|
||||
|
||||
set1.add(1);
|
||||
set2.add(2);
|
||||
expect(isEqual(set1, set2)).toBe(false);
|
||||
});
|
||||
|
||||
it('should compare symbol properties', () => {
|
||||
const symbol1 = Symbol('a');
|
||||
const symbol2 = Symbol('b');
|
||||
|
||||
const object1: any = { a: 1 };
|
||||
const object2: any = { a: 1 };
|
||||
|
||||
object1[symbol1] = { a: { b: 2 } };
|
||||
object2[symbol1] = { a: { b: 2 } };
|
||||
|
||||
Object.defineProperty(object2, symbol2, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: 2,
|
||||
});
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(true);
|
||||
|
||||
object2[symbol1] = { a: 1 };
|
||||
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
|
||||
delete object2[symbol1];
|
||||
object2[Symbol('a')] = { a: { b: 2 } };
|
||||
expect(isEqual(object1, object2)).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should return `false` for objects with custom `toString` methods', () => {
|
||||
let primitive: any;
|
||||
const object = {
|
||||
toString: function () {
|
||||
return primitive;
|
||||
},
|
||||
};
|
||||
const values = [true, null, 1, 'a', undefined];
|
||||
const expected = values.map(stubFalse);
|
||||
|
||||
const actual = values.map((value) => {
|
||||
primitive = value;
|
||||
return isEqual(object, value);
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
@ -18,8 +18,8 @@ describe('isEqual', () => {
|
||||
expect(isEqual(NaN, NaN)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for +0 and -0 comparisons', () => {
|
||||
expect(isEqual(+0, -0)).toBe(false);
|
||||
it('should return true for +0 and -0 comparisons', () => {
|
||||
expect(isEqual(+0, -0)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for equal Date objects', () => {
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { union } from "../array/union.ts";
|
||||
|
||||
import { argumentsTag, arrayBufferTag, arrayTag, bigInt64ArrayTag, bigUint64ArrayTag, booleanTag, dataViewTag, dateTag, errorTag, float32ArrayTag, float64ArrayTag, functionTag, int16ArrayTag, int32ArrayTag, int8ArrayTag, mapTag, numberTag, objectTag, regexpTag, setTag, stringTag, symbolTag, uint16ArrayTag, uint32ArrayTag, uint8ArrayTag, uint8ClampedArrayTag } from "../compat/_internal/tags.ts";
|
||||
import { getSymbols } from "../compat/_internal/getSymbols.ts";
|
||||
import { getTag } from "../compat/_internal/getTag.ts";
|
||||
import { isPlainObject } from "./isPlainObject.ts";
|
||||
|
||||
/**
|
||||
* Checks if two values are equal, including support for `Date`, `RegExp`, and deep object comparison.
|
||||
@ -14,44 +18,223 @@ import { union } from "../array/union.ts";
|
||||
* isEqual(new Date('2020-01-01'), new Date('2020-01-01')); // true
|
||||
* isEqual([1, 2, 3], [1, 2, 3]); // true
|
||||
*/
|
||||
export function isEqual(a: unknown, b: unknown): boolean {
|
||||
export function isEqual(a: any, b: any): boolean {
|
||||
if (typeof a === typeof b) {
|
||||
switch (typeof a) {
|
||||
case 'bigint':
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
case 'symbol':
|
||||
case 'undefined': {
|
||||
return a === b;
|
||||
}
|
||||
case 'number': {
|
||||
return a === b || Object.is(a, b);
|
||||
}
|
||||
case 'function': {
|
||||
return a === b;
|
||||
}
|
||||
case 'object': {
|
||||
return areObjectsEqual(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return areObjectsEqual(a, b);
|
||||
}
|
||||
|
||||
function areObjectsEqual(a: any, b: any, stack?: Map<any, any>) {
|
||||
if (Object.is(a, b)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() === b.getTime();
|
||||
let aTag = getTag(a);
|
||||
let bTag = getTag(b);
|
||||
|
||||
if (aTag === argumentsTag) {
|
||||
aTag = objectTag;
|
||||
}
|
||||
|
||||
if (a instanceof RegExp && b instanceof RegExp) {
|
||||
return a.source === b.source && a.flags === b.flags;
|
||||
if (bTag === argumentsTag) {
|
||||
bTag = objectTag;
|
||||
}
|
||||
|
||||
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
|
||||
if (aTag !== bTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aKeys = Object.keys(a);
|
||||
const bKeys = Object.keys(b);
|
||||
switch (aTag) {
|
||||
case regexpTag:
|
||||
case stringTag:
|
||||
return a.toString() === b.toString();
|
||||
|
||||
if (aKeys.length !== bKeys.length) {
|
||||
return false;
|
||||
}
|
||||
case numberTag: {
|
||||
const x = a.valueOf();
|
||||
const y = b.valueOf();
|
||||
|
||||
// check if all keys in both arrays match
|
||||
if (union(aKeys, bKeys).length !== aKeys.length) {
|
||||
return false;
|
||||
}
|
||||
return x === y || Number.isNaN(x) && Number.isNaN(y);
|
||||
}
|
||||
|
||||
for (let i = 0; i < aKeys.length; i++) {
|
||||
const propKey = aKeys[i];
|
||||
const aProp = (a as any)[propKey];
|
||||
const bProp = (b as any)[propKey];
|
||||
|
||||
if (!isEqual(aProp, bProp)) {
|
||||
return false;
|
||||
case booleanTag:
|
||||
case dateTag:
|
||||
case symbolTag:
|
||||
return Object.is(a.valueOf(), b.valueOf());
|
||||
|
||||
case regexpTag: {
|
||||
return a.source === b.source && a.flags === b.flags;
|
||||
}
|
||||
|
||||
case functionTag: {
|
||||
return a == b;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
stack = stack ?? new Map();
|
||||
|
||||
const aStack = stack.get(a);
|
||||
const bStack = stack.get(b);
|
||||
|
||||
if (aStack != null && bStack != null) {
|
||||
return aStack === b;
|
||||
}
|
||||
|
||||
stack.set(a, b);
|
||||
stack.set(b, a);
|
||||
|
||||
try {
|
||||
switch (aTag) {
|
||||
case mapTag: {
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [key, value] of a.entries()) {
|
||||
if (!b.has(key) || !areObjectsEqual(value, b.get(key), stack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case setTag: {
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aValues = Array.from(a.values());
|
||||
const bValues = Array.from(b.values());
|
||||
|
||||
for (let i = 0; i < aValues.length; i++) {
|
||||
const aValue = aValues[i];
|
||||
const index = bValues.findIndex(bValue => {
|
||||
return areObjectsEqual(aValue, bValue, stack);
|
||||
})
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bValues.splice(index, 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case arrayTag:
|
||||
case uint8ArrayTag:
|
||||
case uint8ClampedArrayTag:
|
||||
case uint16ArrayTag:
|
||||
case uint32ArrayTag:
|
||||
case bigUint64ArrayTag:
|
||||
case int8ArrayTag:
|
||||
case int16ArrayTag:
|
||||
case int32ArrayTag:
|
||||
case bigInt64ArrayTag:
|
||||
case float32ArrayTag:
|
||||
case float64ArrayTag: {
|
||||
// Buffers are also treated as [object Uint8Array]s.
|
||||
if (Buffer.isBuffer(a) !== Buffer.isBuffer(b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!areObjectsEqual(a[i], b[i], stack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case arrayBufferTag: {
|
||||
if (a.byteLength !== b.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areObjectsEqual(new Uint8Array(a), new Uint8Array(b), stack);
|
||||
}
|
||||
|
||||
case dataViewTag: {
|
||||
if (a.byteLength !== b.byteLength || a.byteOffset !== b.byteOffset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areObjectsEqual(a.buffer, b.buffer, stack);
|
||||
}
|
||||
|
||||
case errorTag: {
|
||||
return a.name === b.name && a.message === b.message;
|
||||
}
|
||||
|
||||
case objectTag: {
|
||||
if (a == null || b == null) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
const areEqualInstances =
|
||||
areObjectsEqual(a.constructor, b.constructor, stack) ||
|
||||
(isPlainObject(a) && isPlainObject(b));
|
||||
|
||||
if (!areEqualInstances) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aKeys = [...Object.keys(a), ...getSymbols(a)];
|
||||
const bKeys = [...Object.keys(b), ...getSymbols(b)];
|
||||
|
||||
if (aKeys.length !== bKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < aKeys.length; i++) {
|
||||
const propKey = aKeys[i];
|
||||
const aProp = (a as any)[propKey];
|
||||
|
||||
if (!(b as any).hasOwnProperty(propKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bProp = (b as any)[propKey];
|
||||
|
||||
if (!areObjectsEqual(aProp, bProp, stack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
stack.delete(a);
|
||||
stack.delete(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
108
yarn.lock
108
yarn.lock
@ -3352,24 +3352,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/expect@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@vitest/expect@npm:2.0.2"
|
||||
"@vitest/expect@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/expect@npm:2.0.5"
|
||||
dependencies:
|
||||
"@vitest/spy": "npm:2.0.2"
|
||||
"@vitest/utils": "npm:2.0.2"
|
||||
"@vitest/spy": "npm:2.0.5"
|
||||
"@vitest/utils": "npm:2.0.5"
|
||||
chai: "npm:^5.1.1"
|
||||
tinyrainbow: "npm:^1.2.0"
|
||||
checksum: 10c0/6f541f2f25244f41e9054699713ac9aedf1c82b82f6e0d4d4863565b352ff32794c2220f23603a01fc22b1eecbb9ea8e09eb2c93d80f7322c2b438a5e084ec08
|
||||
checksum: 10c0/08cb1b0f106d16a5b60db733e3d436fa5eefc68571488eb570dfe4f599f214ab52e4342273b03dbe12331cc6c0cdc325ac6c94f651ad254cd62f3aa0e3d185aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/pretty-format@npm:2.0.2, @vitest/pretty-format@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@vitest/pretty-format@npm:2.0.2"
|
||||
"@vitest/pretty-format@npm:2.0.5, @vitest/pretty-format@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/pretty-format@npm:2.0.5"
|
||||
dependencies:
|
||||
tinyrainbow: "npm:^1.2.0"
|
||||
checksum: 10c0/85749fae2ebcf7950c7a019a11b4272def00ee6568b6179e377e7374fb3b9ef6bd5bbef16c110b17881a3d1c772e315cb13a852758b08296b5a4bc665426952b
|
||||
checksum: 10c0/236c0798c5170a0b5ad5d4bd06118533738e820b4dd30079d8fbcb15baee949d41c60f42a9f769906c4a5ce366d7ef11279546070646c0efc03128c220c31f37
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3384,13 +3384,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/runner@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@vitest/runner@npm:2.0.2"
|
||||
"@vitest/runner@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/runner@npm:2.0.5"
|
||||
dependencies:
|
||||
"@vitest/utils": "npm:2.0.2"
|
||||
"@vitest/utils": "npm:2.0.5"
|
||||
pathe: "npm:^1.1.2"
|
||||
checksum: 10c0/f4454b67f0c11318515ed6498cf8aa58ae18b6630a13d31201b55626c1c166dc07ceedd11ef373229595559724a6d3e8c961a8dfd8cc627c3b82bb38de0be40e
|
||||
checksum: 10c0/d0ed3302a7e015bf44b7c0df9d8f7da163659e082d86f9406944b5a31a61ab9ddc1de530e06176d1f4ef0bde994b44bff4c7dab62aacdc235c8fc04b98e4a72a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3405,14 +3405,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/snapshot@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@vitest/snapshot@npm:2.0.2"
|
||||
"@vitest/snapshot@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/snapshot@npm:2.0.5"
|
||||
dependencies:
|
||||
"@vitest/pretty-format": "npm:2.0.2"
|
||||
"@vitest/pretty-format": "npm:2.0.5"
|
||||
magic-string: "npm:^0.30.10"
|
||||
pathe: "npm:^1.1.2"
|
||||
checksum: 10c0/7cb5e16d8a10ce71ec33cec57b191d28b82c4204b986a0ad04a956b401ae47d019f28a3680ee4256105bf9259f6df1208bff93fc4dc4b3e333a8c273f6b39200
|
||||
checksum: 10c0/7bf38474248f5ae0aac6afad511785d2b7a023ac5158803c2868fd172b5b9c1a569fb1dd64a09a49e43fd342cab71ea485ada89b7f08d37b1622a5a0ac00271d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3425,12 +3425,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/spy@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@vitest/spy@npm:2.0.2"
|
||||
"@vitest/spy@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/spy@npm:2.0.5"
|
||||
dependencies:
|
||||
tinyspy: "npm:^3.0.0"
|
||||
checksum: 10c0/7ef32945fc2a83add963da9baf35c6c2fa5b35afb03ab96fe1289ef5bbf3e85ef30bb6b80706f06935351ef10399551d37a993d58958260fe4b21f605a08c1a0
|
||||
checksum: 10c0/70634c21921eb271b54d2986c21d7ab6896a31c0f4f1d266940c9bafb8ac36237846d6736638cbf18b958bd98e5261b158a6944352742accfde50b7818ff655e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3446,15 +3446,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/utils@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@vitest/utils@npm:2.0.2"
|
||||
"@vitest/utils@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/utils@npm:2.0.5"
|
||||
dependencies:
|
||||
"@vitest/pretty-format": "npm:2.0.2"
|
||||
"@vitest/pretty-format": "npm:2.0.5"
|
||||
estree-walker: "npm:^3.0.3"
|
||||
loupe: "npm:^3.1.1"
|
||||
tinyrainbow: "npm:^1.2.0"
|
||||
checksum: 10c0/d1f99ab1ea38ff36150405b2df390ec09cd0f014d05d3ae500c44bdc8746a14f34be3bf76c2a44c352b1c40d130bdf477c1043159ac62f2d63765aa1a8bb82a5
|
||||
checksum: 10c0/0d1de748298f07a50281e1ba058b05dcd58da3280c14e6f016265e950bd79adab6b97822de8f0ea82d3070f585654801a9b1bcf26db4372e51cf7746bf86d73b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -4207,7 +4207,7 @@ __metadata:
|
||||
esbuild: "npm:0.23.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
lodash-es: "npm:^4.17.21"
|
||||
vitest: "npm:^2.0.2"
|
||||
vitest: "npm:^2.0.5"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@ -10482,9 +10482,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-node@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "vite-node@npm:2.0.2"
|
||||
"vite-node@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "vite-node@npm:2.0.5"
|
||||
dependencies:
|
||||
cac: "npm:^6.7.14"
|
||||
debug: "npm:^4.3.5"
|
||||
@ -10493,7 +10493,7 @@ __metadata:
|
||||
vite: "npm:^5.0.0"
|
||||
bin:
|
||||
vite-node: vite-node.mjs
|
||||
checksum: 10c0/cf6fa40844134bd11d149ada94313ee2a47756ba7a98e143698b37c73b72c5850a9aaa799bd3076c09520e1be17079665846c763d3696b59359b88be77f299b6
|
||||
checksum: 10c0/affcc58ae8d45bce3e8bc3b5767acd57c24441634e2cd967cf97f4e5ed2bcead1714b60150cdf7ee153ebad47659c5cd419883207e1a95b69790331e3243749f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -10683,17 +10683,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vitest@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "vitest@npm:2.0.2"
|
||||
"vitest@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "vitest@npm:2.0.5"
|
||||
dependencies:
|
||||
"@ampproject/remapping": "npm:^2.3.0"
|
||||
"@vitest/expect": "npm:2.0.2"
|
||||
"@vitest/pretty-format": "npm:^2.0.2"
|
||||
"@vitest/runner": "npm:2.0.2"
|
||||
"@vitest/snapshot": "npm:2.0.2"
|
||||
"@vitest/spy": "npm:2.0.2"
|
||||
"@vitest/utils": "npm:2.0.2"
|
||||
"@vitest/expect": "npm:2.0.5"
|
||||
"@vitest/pretty-format": "npm:^2.0.5"
|
||||
"@vitest/runner": "npm:2.0.5"
|
||||
"@vitest/snapshot": "npm:2.0.5"
|
||||
"@vitest/spy": "npm:2.0.5"
|
||||
"@vitest/utils": "npm:2.0.5"
|
||||
chai: "npm:^5.1.1"
|
||||
debug: "npm:^4.3.5"
|
||||
execa: "npm:^8.0.1"
|
||||
@ -10704,13 +10704,13 @@ __metadata:
|
||||
tinypool: "npm:^1.0.0"
|
||||
tinyrainbow: "npm:^1.2.0"
|
||||
vite: "npm:^5.0.0"
|
||||
vite-node: "npm:2.0.2"
|
||||
why-is-node-running: "npm:^2.2.2"
|
||||
vite-node: "npm:2.0.5"
|
||||
why-is-node-running: "npm:^2.3.0"
|
||||
peerDependencies:
|
||||
"@edge-runtime/vm": "*"
|
||||
"@types/node": ^18.0.0 || >=20.0.0
|
||||
"@vitest/browser": 2.0.2
|
||||
"@vitest/ui": 2.0.2
|
||||
"@vitest/browser": 2.0.5
|
||||
"@vitest/ui": 2.0.5
|
||||
happy-dom: "*"
|
||||
jsdom: "*"
|
||||
peerDependenciesMeta:
|
||||
@ -10728,7 +10728,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
vitest: vitest.mjs
|
||||
checksum: 10c0/4ef4d8d5a32ee91f34715b8ae1895062df9ca36be5a88ff916ee4bb2a5e01dfa3f105031f7a5939c98f6401b3a6b2bfd1de6caab0ac84cca0e2df364a19c3526
|
||||
checksum: 10c0/b4e6cca00816bf967a8589111ded72faa12f92f94ccdd0dcd0698ffcfdfc52ec662753f66b387549c600ac699b993fd952efbd99dc57fcf4d1c69a2f1022b259
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -10923,6 +10923,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"why-is-node-running@npm:^2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "why-is-node-running@npm:2.3.0"
|
||||
dependencies:
|
||||
siginfo: "npm:^2.0.0"
|
||||
stackback: "npm:0.0.2"
|
||||
bin:
|
||||
why-is-node-running: cli.js
|
||||
checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"win-release@npm:^1.0.0":
|
||||
version: 1.1.1
|
||||
resolution: "win-release@npm:1.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user