Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/main/core/test/runtime/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/main/core/test/runtime/runtime.test.js |
import Runtime from '../../src/lib/runtime'; import {internalClone} from '../../src/lib/runtime/clone'; function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } describe('Runtime', () => { it('Should be exported as function', () => { assert(typeof Runtime === 'function'); }); describe('#merge', () => { it('Should be a function', () => { assert(typeof Runtime.merge === 'function'); }); it('Should merge 2 simple objects', () => { const source1 = {prop1: 1, prop2: 2}; const source2 = {prop1: 99, prop3: 3}; const result = Runtime.merge(source1, source2); assert.equal(Object.keys(result).length, 3); assert.equal(result.prop1, 99); assert.equal(result.prop2, 2); assert.equal(result.prop3, 3); }); it('Should merge more than 2 objects', () => { const source1 = {prop1: 1, prop2: 2}; const source2 = {prop1: 99, prop3: 3}; const source3 = {prop1: 100, prop4: 4}; const source4 = {prop2: 222, prop5: 5}; const result = Runtime.merge(source1, source2, source3, source4); assert.equal(Object.keys(result).length, 5); assert.equal(result.prop1, 100); assert.equal(result.prop2, 222); assert.equal(result.prop3, 3); assert.equal(result.prop4, 4); assert.equal(result.prop5, 5); }); it('Should not modify merging objects', () => { const source1 = {prop1: 1, prop2: 2}; const source1Clone = {...source1}; const source2 = {prop1: 99, prop3: 3}; const source2Clone = {...source2}; const source3 = {prop1: 100, prop4: 4}; const source3Clone = {...source3}; const source4 = {prop2: 222, prop5: 5}; const source4Clone = {...source4}; void Runtime.merge(source1, source2, source3, source4); assert.deepEqual(source1, source1Clone); assert.deepEqual(source2, source2Clone); assert.deepEqual(source3, source3Clone); assert.deepEqual(source4, source4Clone); }); it('Should merge child objects', () => { const source1 = { prop1: 1, prop2: 2, propA: [1, 2, 3], child: { prop11: 11, prop22: 22, prop99: { prop111: 111, } }, }; const source2 = { prop1: 1, prop2: 2, propA: [1, 2, 4, 5], child: { prop11: 11, prop22: 2222, prop33: 33, prop99: { prop222: 222, } }, }; const result = { prop1: 1, prop2: 2, propA: [1, 2, 4, 5], child: { prop11: 11, prop22: 2222, prop33: 33, prop99: { prop111: 111, prop222: 222, } }, }; const merged = Runtime.merge(source1, source2); assert.deepEqual(merged, result); }); it('Should merge arrays', () => { const source1 = [1, 2, 3, 4]; const source2 = [9,,,7]; const result = [9, 2, 3, 7]; const merged = Runtime.merge(source1, source2); assert.deepEqual(merged, result); }); it('Should deep merge array of objects', () => { const source1 = [{test1: 1}, {test2: 2}]; const source2 = [{test1: 99}, {test3: 3}]; const result = [{test1: 99}, {test2: 2, test3: 3}]; const merged = Runtime.merge(source1, source2); assert.deepEqual(merged, result); }); it('Should merge arrays with html elements', () => { const element = document.createElement('div'); const source1 = ['', 'test']; const source2 = [element]; const result = Runtime.merge(source1, source2); assert.ok(result[0] === element); assert.ok(result[1] === 'test'); }); describe('Memory leak detection', () => { it('Should not retain passed params', () => { let item1 = [1, 2, {test: 1}]; let item2 = [3, 4, {test: 2}]; let result = Runtime.merge(item1, item2); let isItem1Collected = false; global.weak(item1, () => { isItem1Collected = true; }); let isItem2Collected = false; global.weak(item2, () => { isItem2Collected = true; }); let isResultCollected = false; global.weak(result, () => { isResultCollected = true; }); item1 = null; item2 = null; result = null; global.gc(); assert.ok(isItem1Collected, 'Memory leak detected! "item1" is not collected'); assert.ok(isItem2Collected, 'Memory leak detected! "item2" is not collected'); assert.ok(isResultCollected, 'Memory leak detected! "result" is not collected'); }); }); }); describe('#clone', () => { it('Should be a function', () => { assert(typeof Runtime.clone === 'function'); }); it('Should clone plain object', () => { const source = { prop1: 'value1', prop2: 'value2', prop3: ['1', '2', '3'] }; const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(clone !== source); }); it('Should clone plain object with circular reference', () => { const source = { prop1: 'value1', prop2: 'value2', prop3: ['1', '2', '3'], prop4: null, }; source.prop4 = source; const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(clone !== source); }); it('Should clone array', () => { const source = [1, 2, 3, 4]; const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(clone !== source); }); it('Should clone array with circular reference', () => { const source = [1, 2, 3, 4]; source.push(source); const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(clone !== source); }); it('Should clone typed object', () => { class MyClass2 {} class MyClass extends MyClass2 { constructor() { super(); this.prop1 = 1; this.prop2 = 2; } } const source = new MyClass(); const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(clone !== source); assert(clone instanceof MyClass); assert(clone instanceof MyClass2); }); it('Should clone typed object', () => { function MyClass() { this.prop1 = 1; } MyClass.prototype.test = function() {}; const source = new MyClass(); const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(clone !== source); }); it('Should clone named object without constructor property in prototype', () => { function MyClass() {} MyClass.prototype = { getName() { return 'MyClass'; } }; const source = new MyClass(); const clone = Runtime.clone(source); assert.deepEqual(source, clone); assert(source !== clone); assert(source instanceof MyClass); assert(clone instanceof MyClass); }); describe('Memory leak detection', () => { it('Should not leak if clone object', () => { let source = { string: 'test', number: 11, object: {test: 1}, array: [1, 2, 3], element: document.createElement('div'), }; let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone array', () => { let source = ['test', 1, {test: 1}, document.createElement('div')]; source.push(source); let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone element without child', () => { let source = document.createElement('div'); let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone element with child', () => { let source = document.createElement('div'); let childElement = document.createElement('span'); let childTextNode = document.createTextNode('Hello!'); source.appendChild(childElement); source.appendChild(childTextNode); let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); childElement = null; childTextNode = null; source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone Date', () => { let source = new Date(); let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone Map', () => { let source = new Map(); let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone Set', () => { let source = new Set(); let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak if clone RegExp', () => { let source = /\w+/; let cloned = Runtime.clone(source); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); source = null; cloned = null; global.gc(); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); it('Should not leak in internalClone', () => { let map = new WeakMap(); let source = {test: 1}; let cloned = internalClone(source, map); let isMapCollected = false; global.weak(source, () => { isMapCollected = true; }); let isSourceCollected = false; global.weak(source, () => { isSourceCollected = true; }); let isClonedCollected = false; global.weak(cloned, () => { isClonedCollected = true; }); map = null; source = null; cloned = null; global.gc(); assert.ok(isMapCollected, 'Memory leak detected! "map" not collected'); assert.ok(isSourceCollected, 'Memory leak detected! "Source" not collected'); assert.ok(isClonedCollected, 'Memory leak detected! "Cloned" not collected'); }); }); }); describe('#orderBy', () => { it('Should be a function', () => { assert.ok(typeof Runtime.orderBy === 'function'); }); it('Should sort array of objects by field with string value (default asc)', () => { const arr = [ {name: 'b'}, {name: 'a'}, {name: 'c'}, {name: 'z'}, {name: 'd'}, ]; const ascResult = [ {name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}, {name: 'z'}, ]; assert.deepEqual(Runtime.orderBy(arr, ['name']), ascResult); }); it('Should sort array of objects by field with string value (asc)', () => { const arr = [ {name: 'b'}, {name: 'a'}, {name: 'c'}, {name: 'z'}, {name: 'd'}, ]; const ascResult = [ {name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}, {name: 'z'}, ]; assert.deepEqual(Runtime.orderBy(arr, ['name'], ['asc']), ascResult); }); it('Should sort array of objects by field with string value (desc)', () => { const arr = [ {name: 'b'}, {name: 'a'}, {name: 'c'}, {name: 'z'}, {name: 'd'}, ]; const descResult = [ {name: 'z'}, {name: 'd'}, {name: 'c'}, {name: 'b'}, {name: 'a'}, ]; assert.deepEqual(Runtime.orderBy(arr, ['name'], ['desc']), descResult); }); it('Should sort array of object by field with date value (asc)', () => { const time = +new Date(); const arr = [ {value: new Date(time - 4), sort: 3}, {value: new Date(time - 2), sort: 5}, {value: new Date(time - 1), sort: 6}, {value: new Date(time - 3), sort: 4}, {value: new Date(time - 6), sort: 1}, {value: new Date(time - 5), sort: 2}, ]; const ascResult = [ {value: new Date(time - 6), sort: 1}, {value: new Date(time - 5), sort: 2}, {value: new Date(time - 4), sort: 3}, {value: new Date(time - 3), sort: 4}, {value: new Date(time - 2), sort: 5}, {value: new Date(time - 1), sort: 6}, ]; assert.deepEqual(Runtime.orderBy(arr, ['value'], ['asc']), ascResult); }); it('Should sort array of object by field with date value (desc)', () => { const time = +new Date(); const arr = [ {value: new Date(time - 4), sort: 3}, {value: new Date(time - 2), sort: 5}, {value: new Date(time - 1), sort: 6}, {value: new Date(time - 3), sort: 4}, {value: new Date(time - 6), sort: 1}, {value: new Date(time - 5), sort: 2}, ]; const descResult = [ {value: new Date(time - 1), sort: 6}, {value: new Date(time - 2), sort: 5}, {value: new Date(time - 3), sort: 4}, {value: new Date(time - 4), sort: 3}, {value: new Date(time - 5), sort: 2}, {value: new Date(time - 6), sort: 1}, ]; assert.deepEqual(Runtime.orderBy(arr, ['value'], ['desc']), descResult); }); it('Should sort array of objects by multiple fields (asc, asc)', () => { const arr = [ {id: 1, range: 1}, {id: 5, range: 5}, {id: 2, range: 2}, {id: 3, range: 3}, {id: 4, range: 4}, {id: 3, range: 2}, ]; const ascAscResult = [ {id: 1, range: 1}, {id: 2, range: 2}, {id: 3, range: 2}, {id: 3, range: 3}, {id: 4, range: 4}, {id: 5, range: 5}, ]; assert.deepEqual(Runtime.orderBy(arr, ['id', 'range']), ascAscResult); }); it('Should sort array of objects by multiple fields (asc, desc)', () => { const arr = [ {id: 1, range: 1}, {id: 5, range: 5}, {id: 2, range: 2}, {id: 3, range: 3}, {id: 4, range: 4}, {id: 3, range: 2}, ]; const ascDescResult = [ {id: 1, range: 1}, {id: 2, range: 2}, {id: 3, range: 3}, {id: 3, range: 2}, {id: 4, range: 4}, {id: 5, range: 5}, ]; assert.deepEqual(Runtime.orderBy(arr, ['id', 'range'], ['asc', 'desc']), ascDescResult); }); it('Should sort array of objects by multiple fields (desc, desc)', () => { const arr = [ {id: 1, range: 1}, {id: 5, range: 5}, {id: 2, range: 2}, {id: 3, range: 3}, {id: 4, range: 4}, {id: 3, range: 2}, ]; const descDescResult = [ {id: 5, range: 5}, {id: 4, range: 4}, {id: 3, range: 3}, {id: 3, range: 2}, {id: 2, range: 2}, {id: 1, range: 1}, ]; assert.deepEqual(Runtime.orderBy(arr, ['id', 'range'], ['desc', 'desc']), descDescResult); }); it('Should sort array of objects by multiple fields (desc, asc)', () => { const arr = [ {id: 1, range: 1}, {id: 5, range: 5}, {id: 2, range: 2}, {id: 3, range: 3}, {id: 4, range: 4}, {id: 3, range: 2}, ]; const descAscResult = [ {id: 5, range: 5}, {id: 4, range: 4}, {id: 3, range: 2}, {id: 3, range: 3}, {id: 2, range: 2}, {id: 1, range: 1}, ]; assert.deepEqual(Runtime.orderBy(arr, ['id', 'range'], ['desc', 'asc']), descAscResult); }); it('Should sort array of objects by field with string case-insensitive (asc)', () => { const arr = [ {name: 'a', sort: 1}, {name: 'b', sort: 3}, {name: 'A', sort: 2}, {name: 'B', sort: 4}, ]; const caseInsensitiveResult = [ {name: 'a', sort: 1}, {name: 'A', sort: 2}, {name: 'b', sort: 3}, {name: 'B', sort: 4}, ]; assert.deepEqual(Runtime.orderBy(arr, ['name']), caseInsensitiveResult); }); it('Should sort array of objects by field with string case-insensitive (desc)', () => { const arr = [ {name: 'a', sort: 1}, {name: 'b', sort: 3}, {name: 'A', sort: 2}, {name: 'B', sort: 4}, ]; const caseInsensitiveResult = [ {name: 'b', sort: 3}, {name: 'B', sort: 4}, {name: 'a', sort: 1}, {name: 'A', sort: 2}, ]; assert.deepEqual(Runtime.orderBy(arr, ['name'], ['desc']), caseInsensitiveResult); }); it('Should implements stable asc sort algorithm', () => { const ascResult = [ {name: 'a', sort: 1}, {name: 'a', sort: 2}, {name: 'b', sort: 3}, {name: 'c', sort: 4}, {name: 'd', sort: 5}, {name: 'z', sort: 6}, ]; Array.from({length: 1000}).forEach(() => { const arr = [ ...shuffle([ {name: 'b', sort: 3}, {name: 'c', sort: 4}, {name: 'z', sort: 6}, {name: 'd', sort: 5}, ]), ...[ {name: 'a', sort: 1}, {name: 'a', sort: 2}, ] ]; assert.deepEqual(Runtime.orderBy(arr, ['name'], ['asc']), ascResult); }); }); it('Should implements stable desc sort algorithm', () => { const descResult = [ {name: 'z', sort: 6}, {name: 'd', sort: 5}, {name: 'c', sort: 4}, {name: 'b', sort: 3}, {name: 'a', sort: 1}, {name: 'a', sort: 2}, ]; Array.from({length: 1000}).forEach(() => { const arr = [ ...shuffle([ {name: 'b', sort: 3}, {name: 'c', sort: 4}, {name: 'z', sort: 6}, {name: 'd', sort: 5}, ]), ...[ {name: 'a', sort: 1}, {name: 'a', sort: 2}, ] ]; assert.deepEqual(Runtime.orderBy(arr, ['name'], ['desc']), descResult); }); }); }); describe('#destroy', () => { it('Should destroy plain object', () => { const object = { prop1: 1, prop2: 2, getAny() { return 'any'; }, }; Runtime.destroy(object); assert.throws( () => { void object.prop1; }, 'Uncaught Error: Object is destroyed', ); assert.throws( () => { void object.prop2; }, 'Uncaught Error: Object is destroyed', ); assert.throws( () => { object.getAny(); }, 'Uncaught Error: Object is destroyed', ); assert.throws( () => { object.toString(); }, 'Uncaught Error: Object is destroyed', ); assert.throws( () => { object.propertyIsEnumerable('prop1'); }, 'Uncaught Error: Object is destroyed', ); }); it('Should destroy ES6 class instance', () => { class MyClass { constructor() { this.prop1 = 1; this.prop2 = 2; } getAny() { return 'any'; } } const object = new MyClass(); Runtime.destroy(object); assert.throws( () => { void object.prop1; }, 'Uncaught Error: Object is destroyed', ); assert.throws( () => { void object.prop2; }, 'Uncaught Error: Object is destroyed', ); assert.throws( () => { object.getAny(); }, 'Uncaught Error: Object is destroyed', ); }); it('Should does not throws if passed destroyed object', () => { const object = { prop1: 1, prop2: 2, getAny() { return 'any'; }, }; Runtime.destroy(object); assert.doesNotThrow(() => { Runtime.destroy(object); }); }); it('Should does not throws if passed primitive', () => { assert.doesNotThrow(() => { Runtime.destroy(1); }); assert.doesNotThrow(() => { Runtime.destroy(true); }); assert.doesNotThrow(() => { Runtime.destroy('string'); }); assert.doesNotThrow(() => { Runtime.destroy(Symbol('111')); }); }); }); });