Add a core deepEqual function (#1018)
* Add a core deepEqual function * Add tests
This commit is contained in:
parent
eb8069a494
commit
219dfb4917
@ -110,6 +110,7 @@
|
||||
"escapeRegExp": "readonly",
|
||||
"deferPromise": "readonly",
|
||||
"clone": "readonly",
|
||||
"deepEqual": "readonly",
|
||||
"generateId": "readonly",
|
||||
"promiseAnimationFrame": "readonly",
|
||||
"DynamicProperty": "readonly",
|
||||
|
@ -176,6 +176,72 @@ const clone = (() => {
|
||||
return clone;
|
||||
})();
|
||||
|
||||
const deepEqual = (() => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
function deepEqual(value1, value2) {
|
||||
if (value1 === value2) { return true; }
|
||||
|
||||
const type = typeof value1;
|
||||
if (typeof value2 !== type) { return false; }
|
||||
|
||||
switch (type) {
|
||||
case 'object':
|
||||
case 'function':
|
||||
return deepEqualInternal(value1, value2, new Set());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function deepEqualInternal(value1, value2, visited1) {
|
||||
if (value1 === value2) { return true; }
|
||||
|
||||
const type = typeof value1;
|
||||
if (typeof value2 !== type) { return false; }
|
||||
|
||||
switch (type) {
|
||||
case 'object':
|
||||
case 'function':
|
||||
{
|
||||
if (value1 === null || value2 === null) { return false; }
|
||||
const array = Array.isArray(value1);
|
||||
if (array !== Array.isArray(value2)) { return false; }
|
||||
if (visited1.has(value1)) { return false; }
|
||||
visited1.add(value1);
|
||||
return array ? areArraysEqual(value1, value2, visited1) : areObjectsEqual(value1, value2, visited1);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function areObjectsEqual(value1, value2, visited1) {
|
||||
const keys1 = Object.keys(value1);
|
||||
const keys2 = Object.keys(value2);
|
||||
if (keys1.length !== keys2.length) { return false; }
|
||||
|
||||
const keys1Set = new Set(keys1);
|
||||
for (const key of keys2) {
|
||||
if (!keys1Set.has(key) || !deepEqualInternal(value1[key], value2[key], visited1)) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function areArraysEqual(value1, value2, visited1) {
|
||||
const length = value1.length;
|
||||
if (length !== value2.length) { return false; }
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (!deepEqualInternal(value1[i], value2[i], visited1)) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return deepEqual;
|
||||
})();
|
||||
|
||||
function generateId(length) {
|
||||
const array = new Uint8Array(length);
|
||||
crypto.getRandomValues(array);
|
||||
|
@ -32,7 +32,7 @@ const vm = new VM({
|
||||
vm.execute([
|
||||
'mixed/js/core.js'
|
||||
]);
|
||||
const [DynamicProperty] = vm.get(['DynamicProperty']);
|
||||
const [DynamicProperty, deepEqual] = vm.get(['DynamicProperty', 'deepEqual']);
|
||||
|
||||
|
||||
function testDynamicProperty() {
|
||||
@ -161,9 +161,139 @@ function testDynamicProperty() {
|
||||
}
|
||||
}
|
||||
|
||||
function testDeepEqual() {
|
||||
const data = [
|
||||
// Simple tests
|
||||
{
|
||||
value1: 0,
|
||||
value2: 0,
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: null,
|
||||
value2: null,
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: 'test',
|
||||
value2: 'test',
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: true,
|
||||
value2: true,
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: 0,
|
||||
value2: 1,
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: null,
|
||||
value2: false,
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: 'test1',
|
||||
value2: 'test2',
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: true,
|
||||
value2: false,
|
||||
expected: false
|
||||
},
|
||||
|
||||
// Simple object tests
|
||||
{
|
||||
value1: {},
|
||||
value2: {},
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: {},
|
||||
value2: [],
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: [],
|
||||
value2: [],
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: {},
|
||||
value2: null,
|
||||
expected: false
|
||||
},
|
||||
|
||||
// Complex object tests
|
||||
{
|
||||
value1: [1],
|
||||
value2: [],
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: [1],
|
||||
value2: [1],
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: [1],
|
||||
value2: [2],
|
||||
expected: false
|
||||
},
|
||||
|
||||
{
|
||||
value1: {},
|
||||
value2: {test: 1},
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: {test: 1},
|
||||
value2: {test: 1},
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
value1: {test: 1},
|
||||
value2: {test: {test2: false}},
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: {test: {test2: true}},
|
||||
value2: {test: {test2: false}},
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
value1: {test: {test2: [true]}},
|
||||
value2: {test: {test2: [true]}},
|
||||
expected: true
|
||||
},
|
||||
|
||||
// Recursive
|
||||
{
|
||||
value1: (() => { const x = {}; x.x = x; return x; })(),
|
||||
value2: (() => { const x = {}; x.x = x; return x; })(),
|
||||
expected: false
|
||||
}
|
||||
];
|
||||
|
||||
let index = 0;
|
||||
for (const {value1, value2, expected} of data) {
|
||||
const actual1 = deepEqual(value1, value2);
|
||||
assert.strictEqual(actual1, expected, `Failed for test ${index}`);
|
||||
|
||||
const actual2 = deepEqual(value2, value1);
|
||||
assert.strictEqual(actual2, expected, `Failed for test ${index}`);
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function main() {
|
||||
testDynamicProperty();
|
||||
testDeepEqual();
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user