diff options
| -rw-r--r-- | .eslintrc.json | 1 | ||||
| -rw-r--r-- | ext/mixed/js/core.js | 66 | ||||
| -rw-r--r-- | test/test-core.js | 132 | 
3 files changed, 198 insertions, 1 deletions
| diff --git a/.eslintrc.json b/.eslintrc.json index 2f8306f7..3723d97d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -110,6 +110,7 @@                  "escapeRegExp": "readonly",                  "deferPromise": "readonly",                  "clone": "readonly", +                "deepEqual": "readonly",                  "generateId": "readonly",                  "promiseAnimationFrame": "readonly",                  "DynamicProperty": "readonly", diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 81330893..72ab7474 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -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); diff --git a/test/test-core.js b/test/test-core.js index 3e8b8414..2b29b7f1 100644 --- a/test/test-core.js +++ b/test/test-core.js @@ -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();  } |