UNPKG

data-context

Version:

Watch data changes in the browser and node.js

869 lines (762 loc) 32.2 kB
/** Copyright (c) 2024, Manuel Lõhmus (MIT). */ importModules(["data-context"], function (DC) { var { parse, stringify } = DC; /***** Init TESTS *********************************************************/ testRunner('Init TESTS ', { skip: false }, (test) => { // UNDEFINED test('DC(undefined) ', { skip: false }, (check) => { return check(DC(undefined)).mustBe(undefined); }); // NULL test('DC(null) ', { skip: false }, (check) => { return check(DC(null)).mustBe(null); }); // BOOLEAN test('DC(false) ', { skip: false }, (check) => { return check(DC(false).valueOf()).mustBe(false); }); test('DC(true) ', { skip: false }, (check) => { return check(DC(true).valueOf()).mustBe(true); }); // NUMBER test('DC(123) ', { skip: false }, (check) => { return check(DC(123).valueOf()).mustBe(123); }); test('DC(-456) ', { skip: false }, (check) => { return check(DC(-456).valueOf()).mustBe(-456); }); test('DC(-456) + 123 ', { skip: false }, (check) => { return check(DC(-456) + 123).mustBe(-333); }); // STRING test('DC("") ', { skip: false }, (check) => { return check(DC("").valueOf()).mustBe(""); }); test('DC("test") ', { skip: false }, (check) => { return check(DC("test").valueOf()).mustBe("test"); }); test('DC("test") + 123 ', { skip: false }, (check) => { return check(DC("test") + 123).mustBe("test123"); }); // ARRAY test('DC([]) ', { skip: false }, (check) => { return check(DC([]).toString()).mustBe("[" + [].toString() + "]"); }); test('DC(["test"]) ', { skip: false }, (check) => { return check(DC(["test"]).toString()).mustBe("[\"test\"]"); }); test('DC(["test"]).push(123) ', { skip: false }, (check) => { var dc = DC(["test"]); dc.push(123); return check(dc.toString()).mustBe("[\"test\",123]"); }); // OBJECT; test('DC({}) ', { skip: false }, (check) => { return check(Object.keys(DC({})).toString()).mustBe(""); }); test('DC({ key: "val" }) ', { skip: false }, (check) => { var dc = DC({ key: "val" }); check(Object.keys(dc).toString()).mustBe("key"); return DC.convertPrimitiveTypes ? check(Object.entries(dc).toString()).mustBe("key,\"val\"") : check(Object.entries(dc).toString()).mustBe("key,val"); }); test('DC({}).key = 123 ', { skip: false }, (check) => { var dc = DC({}); dc.key = 123; check(Object.keys(dc).toString()).mustBe("key"); return check(Object.entries(dc).toString()).mustBe("key,123"); }); }); /***** Event Emitter TESTS ************************************************/ testRunner('Event Emitter TESTS ', { skip: false }, (test) => { test('VALUE set_event ', { skip: false }, (check) => { var parent = DC({}); var arr = DC([0]); parent.dc = arr; parent.on("-set", function (event) { check(event.newValue).mustBe(123); check(event.propertyPath.pop()).mustBe("dc"); //check(typeof event.timestamp).mustBe("number"); return true; }, true); arr.on("-set", function (event) { check(event.oldValue + '').mustBe('0'); check(event.newValue).mustBe(123); check(event.propertyPath.pop() + '').mustBe("0"); //check(typeof event.timestamp).mustBe("number"); return true; }, true); arr[0] = 123; return check(arr[0] + '').mustBe('123'); }); test('OBJECT delete_event ', { skip: false }, (check) => { var parent = DC({ dc: { number: 123 } }); parent.on("-delete", function (event) { check(event.oldValue).mustBe(789); check(event.propertyPath.pop()).mustBe("dc"); //check(typeof event.timestamp).mustBe("number"); event.oldValue = 456; }); parent.dc.on("-delete", function (event) { check(event.oldValue).mustBe(123); check(event.propertyPath.pop()).mustBe("number"); //check(typeof event.timestamp).mustBe("number"); event.oldValue = 789; }); delete parent.dc.number; return check(parent.dc.number).mustBe(undefined); }); test('ARRAY pop ', { skip: false }, (check) => { var dc = DC([1, 2, 3]); dc.on("-delete", function (event) { check(typeof event.oldValue).mustBe("number"); check(typeof parseInt(event.propertyPath.pop())).mustBe("number"); //check(typeof event.timestamp).mustBe("number"); }, true); check(dc.pop() + "").mustBe("3"); check(dc.pop() + "").mustBe("2"); check(dc.pop() + "").mustBe("1"); return true; }); test('ARRAY push ', { skip: false }, (check) => { var dc = DC([]); dc.on("-set", function (event) { check(typeof event.newValue.valueOf()).mustBe("number"); check(typeof parseInt(event.propertyPath.pop())).mustBe("number"); //check(typeof event.timestamp).mustBe("number"); }, true); dc.on("-new", function (event) { check(typeof event.newValue.valueOf()).mustBe("number"); }, true); check(dc.push(1) + "").mustBe("1"); check(dc.push(2) + "").mustBe("2"); check(dc.push(3) + "").mustBe("3"); return true; }); }); /***** Parse TESTS ********************************************************/ testRunner('Parse TESTS ', { skip: false }, (test) => { test('parse(undefined) ', { skip: false }, (check) => { return check(parse(undefined)).mustBe(undefined); }); test('parse(null) ', { skip: false }, (check) => { return check(parse(null)).mustBe(null); }); test('parse(0) ', { skip: false }, (check) => { return check(parse(0)).mustBe(0); }); test('parse(-0) ', { skip: false }, (check) => { return check(parse(-0)).mustBe(0); }); test('parse(0x01) ', { skip: false }, (check) => { return check(parse(0x01)).mustBe(1); }); test('parse("") ', { skip: false }, (check) => { return check(parse("")).mustBe(undefined); }); test('parse(\'""\')?.valueOf() ', { skip: false }, (check) => { return check(parse('""')?.valueOf()).mustBe(''); }); test('parse(\'"abc"\')?.valueOf() ', { skip: false }, (check) => { return check(parse('"abc"')?.valueOf()).mustBe('abc'); }); test('parse(\'"\\uD800"\')?.valueOf() ', { skip: false }, (check) => { return check(parse('"\uD800"')?.valueOf()).mustBe('\uD800'); }); test('parse(\'null\') ', { skip: false }, (check) => { return check(parse('null')).mustBe(null); }); test('parse(\'false\')?.valueOf() ', { skip: false }, (check) => { return check(parse('false')?.valueOf()).mustBe(false); }); test('parse(\'true\')?.valueOf() ', { skip: false }, (check) => { return check(parse('true')?.valueOf()).mustBe(true); }); test('parse(\'0\')?.valueOf() ', { skip: false }, (check) => { return check(parse('0')?.valueOf()).mustBe(0); }); test('parse(\'123\')?.valueOf() ', { skip: false }, (check) => { return check(parse('123')?.valueOf()).mustBe(123); }); test('parse(\'-123\')?.valueOf() ', { skip: false }, (check) => { return check(parse('-123')?.valueOf()).mustBe(-123); }); test('parse(\'[]\') ', { skip: false }, (check) => { return check(Array.isArray(parse('[]'))).mustBe(true); }); test('parse(\'[\n]\') ', { skip: false }, (check) => { return check(Array.isArray(parse('[\n]'))).mustBe(true); }); test('parse(\'["abc"]\') ', { skip: false }, (check) => { return check(Array.isArray(parse('[ "abc" ]'))).mustBe(true); }); test('parse(\'{}\') ', { skip: false }, (check) => { var data = parse('{}')?.valueOf(); check(data).mustNotBe(null); check(typeof data).mustBe("object"); check(Object.keys(data).length).mustBe(0); return true; }); test('parse(\'{\n}\') ', { skip: false }, (check) => { var data = parse('{\n}')?.valueOf(); check(data).mustNotBe(null); check(typeof data).mustBe("object"); check(Object.keys(data).length).mustBe(0); return true; }); test('parse(\'[1]\') ', { skip: false }, (check) => { var data = parse('[1]'); check(Array.isArray(data)).mustBe(true); check(data[0]?.valueOf()).mustBe(1); return true; }); test('parse(\'{"arr":[1]}\') ', { skip: false }, (check) => { var data = parse('{"arr":[1]}'); check(Array.isArray(data.arr)).mustBe(true); check(data.arr[0]?.valueOf()).mustBe(1); return true; }); test('parse(\'[{"nr":1}]\') ', { skip: false }, (check) => { var data = parse('[{"nr":1}]'); check(Array.isArray(data)).mustBe(true); check(data[0].nr?.valueOf()).mustBe(1); return true; }); }); /***** Stringify TESTS ****************************************************/ testRunner('Stringify TESTS ', { skip: false }, (test) => { test('stringify(undefined) ', { skip: false }, (check) => { return check(stringify(undefined)).mustBe(undefined); }); test('stringify(null) ', { skip: false }, (check) => { return check(stringify(null)).mustBe('null'); }); test('stringify(true) ', { skip: false }, (check) => { return check(stringify(true)).mustBe('true'); }); test('stringify(false) ', { skip: false }, (check) => { return check(stringify(false)).mustBe('false'); }); test('stringify(0) ', { skip: false }, (check) => { return check(stringify(0)).mustBe('0'); }); test('stringify(-0) ', { skip: false }, (check) => { return check(stringify(-0)).mustBe('0'); }); test('stringify(0.123) ', { skip: false }, (check) => { return check(stringify(0.123)).mustBe('0.123'); }); test('stringify(-0.123) ', { skip: false }, (check) => { return check(stringify(-0.123)).mustBe('-0.123'); }); test('stringify(0.0000123) ', { skip: false }, (check) => { return check(stringify(0.0000123)).mustBe('0.0000123'); }); test('stringify("") ', { skip: false }, (check) => { return check(stringify("")).mustBe('""'); }); test('stringify("abc") ', { skip: false }, (check) => { return check(stringify("abc")).mustBe('"abc"'); }); test('stringify("\\uD800") ', { skip: false }, (check) => { return check(stringify("\uD800")).mustBe('"\uD800"'); }); test('stringify([]) ', { skip: false }, (check) => { return check(stringify([])).mustBe('[]'); }); test('stringify({}) ', { skip: false }, (check) => { return check(stringify({})).mustBe('{}'); }); test('stringify([1]) ', { skip: false }, (check) => { return check(stringify([1])).mustBe('[1]'); }); test('stringify({arr:[1]}) ', { skip: false }, (check) => { return check(stringify({ arr: [1] })).mustBe('{"arr":[1]}'); }); test('stringify([{nr:1}]) ', { skip: false }, (check) => { return check(stringify([{ nr: 1 }])).mustBe('[{"nr":1}]'); }); test('stringify(DC(undefined)) ', { skip: false }, (check) => { return check(stringify(DC(undefined))).mustBe(undefined); }); test('stringify(DC(null)) ', { skip: false }, (check) => { return check(stringify(DC(null))).mustBe('null'); }); test('stringify(DC(true)) ', { skip: false }, (check) => { return check(stringify(DC(true))).mustBe('true'); }); test('stringify(DC(false)) ', { skip: false }, (check) => { return check(stringify(DC(false))).mustBe('false'); }); test('stringify(DC(0)) ', { skip: false }, (check) => { return check(stringify(DC(0))).mustBe('0'); }); test('stringify(DC(-0)) ', { skip: false }, (check) => { return check(stringify(DC(-0))).mustBe('0'); }); test('stringify(DC(0.123)) ', { skip: false }, (check) => { return check(stringify(DC(0.123))).mustBe('0.123'); }); test('stringify(DC(-0.123)) ', { skip: false }, (check) => { return check(stringify(DC(-0.123))).mustBe('-0.123'); }); test('stringify(DC(0.0000123)) ', { skip: false }, (check) => { return check(stringify(DC(0.0000123))).mustBe('0.0000123'); }); test('stringify(DC("")) ', { skip: false }, (check) => { return check(stringify(DC(""))).mustBe('""'); }); test('stringify(DC("abc")) ', { skip: false }, (check) => { return check(stringify(DC("abc"))).mustBe('"abc"'); }); test('stringify(DC([])) ', { skip: false }, (check) => { return check(stringify(DC([]))).mustBe('[]'); }); test('stringify(DC({})) ', { skip: false }, (check) => { return check(stringify(DC({}))).mustBe('{}'); }); test('stringify(DC([{ nr: 1 }])) ', { skip: false }, (check) => { return check(stringify(DC([{ nr: 1 }]))).mustBe('[{"nr":1}]'); }); test('stringify(DC({n:1}),replacer) ', { skip: false }, (check) => { var keys = [], data = stringify(DC({ n: 1 }), function (k, v) { check(typeof this).mustBe("object"); check(typeof k).mustBe("string"); check(v).mustNotBe(undefined); keys.push(k); this; return v; }); check(keys.toString()) .mustBe(",n") .done(); }); test('stringify({s:"a",n:1},["n"]) ', { skip: false }, (check) => { check(stringify(DC({ n: 1 }), ["n"])).mustBe('{"n":1}').done(); }); test('stringify({n:1,o:{toJSON:...}})', { skip: false }, (check) => { var obj = { n: 1, o: { toJSON: function (k) { return {}; } } }; check(stringify(DC(obj))).mustBe('{"n":1,"o":{}}').done(); }); }); /***** Complex TESTS ******************************************************/ testRunner('Complex TESTS ', { skip: false }, (test) => { test('parse - stringify //metadata ', { skip: false }, (check) => { var json = `{ "test": { "arr": [ 123, "abc" ] }, "str": "abc" }`; var jsme = `/*obj*/ { /*obj*/ /*{"timestamp":126546199,"delete":true}*/ "test": { /*array*/ "arr": [ /*number*/ 123, /*string*/ "abc" ] }, /*abc*/ "str": "abc" }`; var obj = parse(jsme, DC); var jsme2 = stringify(obj, null, 4); check(jsme2).mustBe(jsme); var json2 = JSON.stringify(obj, null, 4); check(json2).mustBe(json) return true; }); test('overwriting data ', { skip: false }, (check) => { var json = `{ "test":{ "arr":[ 123, 456, "abc" ] }, "str":"abc" }`; var dc = DC({ test: { arr: [] }, str: "abc" }); var obj = parse(json, dc); obj.test.arr.pop(); var result = stringify(obj, null, 4, { modifiedData: true, setUnmodified: true }); check(result).mustBe(`{ "test": { "arr": [\r\r 0: 123, 1: 456 ] } }`); check(stringify(obj, null, 0, { modifiedData: true, setUnmodified: true })).mustBe('{}'); return true; }); test('overwriting data minify ', { skip: false }, (check) => { var json = '{"test":{"arr":[123,"abc"]},"str":"abc"}'; var dc = DC({ test: { arr: [] }, str: "abc" }); var obj = parse(json, dc); var result = stringify(obj, null, "", { modifiedData: true, setUnmodified: true }); check(result).mustBe('{"test":{"arr":[0:123,1:"abc"]}}'); check(stringify(obj, null, "", { modifiedData: true, setUnmodified: true })).mustBe('{}'); return true; }); test('overwriting array delete dc ', { skip: false }, (check) => { var json = `{ "arr": [ { "nr": 1 }, { "nr": 2 }, { "nr": 3 }, { "nr": 4 }, { "nr": 5 }, { "nr": 6 }, { "nr": 7 } ] }`; var obj = parse(json, DC); var arr = obj.arr.splice(0, 4); var result = stringify(obj, null, 4, { modifiedData: true, setUnmodified: true }); check(result).mustBe(`{ "arr": [\r\r 0: { "nr": 5 }, 1: { "nr": 6 }, 2: { "nr": 7 } ] }`); obj.arr.push(8); parse(result, obj); result = stringify(obj, null, 4, { modifiedData: true, setUnmodified: true }); check(result).mustBe(`{ "arr": [\r\r 0: { "nr": 5 }, 1: { "nr": 6 }, 2: { "nr": 7 } ] }`); return true; }); test('overwriting object delete key ', { skip: false }, (check) => { var json = ` { "dc": { "key0": 0, "key1": 1, "key2": 2 } } `; var obj = parse(json, DC); delete obj.dc.key0; var result = stringify(obj, null, 4, { modifiedData: true, setUnmodified: true }); check(result).mustBe(`{ "dc": { "key0": } }`); return true; }); test('overwriting array ', { skip: false }, (check) => { var json = ` [ { "key0": 0 } ]`; var obj = parse(json, DC); obj[0].key0 = 1; var result = obj.stringifyChanges(null, 4); check(result).mustBe(`[ 0: { "key0": 1 } ]`); return true; }); }); }); /** * Test runner. Function to run unit tests in the console. * @author Manuel Lõhmus 2024 (MIT License) * @version 1.1.2 * [2024-12-29] adde d functionality to select tests by ID in the command line arguments (e.g. --testIDs=1 2 3) * @example `npm test '--'` or `node index.test.js` * @example `npm test '--' --help` or `node index.test.js --help` * @example `npm test '--' --testIDs=1 2 3` or `node index.test.js --testIDs=1 2 3` * @param {string} runnerName Test runner name. * @param {{skip:boolean}} options Test runner options. * @param {(test:Test)=>void} cb Callback function to run the unit tests. * @returns {boolean} If the tests are OK * @example testRunner('Module name', { skip: false }, function (test) {...}); * * @callback Test Unit test callback function * @param {string} testName Test name. * @param {{skip:boolean,timeout:number}} options Test options. (default: {skip:false,timeout:3000}))) * @param {(check:Check,done:Done)=>void} fn Test function. Function parameters: check, done. `check` is used to check the test result. `done` is used to end the test. * @returns {void} * @example test("Test name", {skip:false,timeout:3000}, function(check,done){...}); * @example test("Test name", function(check,done){...}); * @example test("Test name", {skip:checkableObject === undefined}, function(check,done){...}); * * @callback Check Check function to check the test result. * @param {string} label Value name. * @param {any} value Value to check. * @returns {Validator} * @example check('name', value).mustBe(true); * @example check('name', value).mustNotBe(false); * @example check('name', value).mustBe(true).done(); * @example check('name', value).mustBe(true).mustNotBe(false).done(); * * @callback Done Callback function to end the test. * @param {Error} err Error message. If the error message is empty, the test is considered successful. * @returns {void} * * @typedef Validator * @property {(value:any)=>Validator} mustBe Check if the value is equal to the specified value. * @property {(value:any)=>Validator} mustNotBe Check if the value is not equal to the specified value. * @property {(value:any)=>Validator} mustInclude Check if the value is included to the specified value. * @property {Done} done Callback function to end the test. */ function testRunner(runnerName, options, cb) { this?.process?.on('uncaughtException', function noop() { }); testRunner.testRunnerOK = true; clearTimeout(testRunner.exitTimeoutID); var stdout = {}, timeouts = {}, countStarted = 0, countCompleted = 0, testsStarted = false, testRunnerOK = true, strSKIP = "\t\t[\x1b[100m\x1b[97m SKIP \x1b[0m]", strTestsERR = "[\x1b[41m\x1b[97m The tests failed! \x1b[0m]", strTestsDONE = "[\x1b[42m\x1b[97m The tests are done! \x1b[0m]", { help, testids } = _arg_options(); if (help || help === '') { console.log(` npm test '--' [OPTION1=VALUE1] [OPTION2=VALUE2] ... or node index.test.js [OPTION1=VALUE1] [OPTION2=VALUE2] ... The following options are supported: --help Display this help --testIDs Number of the test to run (e.g. node index.test.js --testIDs=1 2 3) `); if (this?.process?.argv[1].endsWith(".js")) { exitPressKey(); } return; } if (testids) { testids = testids.split(' ').filter((id) => id); } //skip all tests if (options?.skip) { testsStarted = "SKIP"; if (runnerName) { log(0, "SKIP > ", runnerName, strSKIP); } testCompleted(); return testRunnerOK; } if (runnerName) { log(0, "START > ", runnerName); } cb(test); testsStarted = true; testCompleted(); return testRunnerOK; function log() { var line = ""; for (let i = 1; i < arguments.length; i++) { line += arguments[i]; } if (stdout[arguments[0]]) { stdout[arguments[0]] += line + "\n"; } else { stdout[arguments[0]] = line + "\n"; } } function print_stdout() { console.log(); console.log( Object.keys(stdout).reduce((output, value, i) => output += stdout[i], '') ); } /** * Unit test function. * @type {Test} */ function test(testName, options, fn) { var startTime, endTime, id = ++countStarted, testOK = true, label = " " + id + ".\tTEST > " + testName + "\t", strOK = "\t[\x1b[42m\x1b[97m OK \x1b[0m]", strERR = "\t[\x1b[41m\x1b[97m FAILED \x1b[0m] -> "; //skip if (options?.skip || testids && !testids.includes(id + '')) { log(id, label, "\t", strSKIP); testCompleted(); return; } //timeout timeouts[id] = setTimeout(function () { done("timeout"); }, options?.timeout || 3000); startTime = performance.now(); try { if (fn(check, done)) { done(); } } catch (err) { done(err); } /** * Callback function to end the test. * @type {Done} */ function done(err = '') { endTime = performance.now(); if (err) { testRunnerOK = testOK = false; } if (err || testOK) log(id, label, ": ", (endTime - startTime).toFixed(2), "ms\t", err ? strERR : strOK, err || ""); if (timeouts[id]) { testCompleted(); } clearTimeout(timeouts[id]); delete timeouts[id]; } /** * Check function to check the test result. * @type {Check} */ function check(label, value) { if (arguments.length === 1) { value = label; label = 'returned'; } if (label === undefined) { label = 'returned'; } /** * Selection fuctions to check. * @type {Validator} */ return { mustBe: function mustBe(mustBe) { if (value !== mustBe) { done("\x1b[44m\x1b[97m " + label + " \x1b[0m '" + value + "' \x1b[44m\x1b[97m must be \x1b[0m '" + mustBe + "'"); } return this; }, mustNotBe: function mustNotBe(mustNotBe) { if (value === mustNotBe) { done("\x1b[44m\x1b[97m " + label + " \x1b[0m '" + value + "' \x1b[44m\x1b[97m must not be \x1b[0m '" + mustNotBe + "'"); } return this; }, mustInclude: function mustInclude(mustInclude) { if (!value?.includes || !value.includes(mustInclude)) { done("\x1b[44m\x1b[97m " + label + " \x1b[0m '" + value + "' \x1b[44m\x1b[97m must include \x1b[0m '" + mustInclude + "'"); } return this; }, done }; } } function testCompleted() { countCompleted++; if (!testsStarted || countStarted >= countCompleted) { return; } if (runnerName) { if (testsStarted === "SKIP") { print_stdout(); } else if (!testRunnerOK) { log(++countStarted, "END > " + runnerName + "\t" + strTestsERR); print_stdout(); } else { log(++countStarted, "END > ", runnerName, "\t", strTestsDONE); print_stdout(); } this?.process?.removeAllListeners('uncaughtException'); if (this?.process?.argv[1].endsWith(".js")) { exitPressKey(); } else if (this?.process) { if (!testRunnerOK) { testRunner.testRunnerOK = false; } testRunner.exitTimeoutID = setTimeout(function () { process.exit(testRunner.testRunnerOK ? 0 : 1); }, 100); } } } function exitPressKey() { process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.on('data', process.exit.bind(process, testRunnerOK ? 0 : 1)); console.log('Press any key to exit'); } function _arg_options() { if (!this?.process) { return {}; } var key; return process.argv.slice(2).reduce(function (opt, keyVal) { var [_key, _val] = keyVal.split('='); _key = (_key + '').trim(); if (_key[0] === '-') { if (_val === undefined) { key = _trimKey(_key); opt[key] = true; return opt; } if (_val[0] !== '-') { _val = (_val + '').trim(); key = _trimKey(_key); opt[key] = _val; } } else if (_key) { opt[key] = typeof opt[key] === 'string' ? opt[key] + ' ' + _key : _key; } return opt; function _trimKey(key) { if (key[0] === '-') { key = key.slice(1); } if (key[0] === '-') { key = key.slice(1); } return key.toLowerCase(); } }, {}); } } /** * Import modules. * @param {string[]} importIdentifierArray Modules to import. * @param {(...importModules:any[]) => void} callback Callback function. */ function importModules(importIdentifierArray, callback) { var thisScope = "undefined" != typeof globalThis ? globalThis : "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {}; if (!thisScope.modules) { thisScope.modules = {}; } if (typeof exports === 'object' && typeof module !== 'undefined') { // CommonJS if (importIdentifierArray.length) { importIdentifierArray = importIdentifierArray.map(function (id) { return require(id); }); } return module.exports = callback.call(thisScope, ...importIdentifierArray); } // Browser waitModules(); function waitModules() { if (importIdentifierArray.length) { for (let i = 0; i < importIdentifierArray.length; i++) { if (!thisScope.modules[importIdentifierArray[i]]) { return setTimeout(waitModules, 10); } } } callback.call(thisScope, ...importIdentifierArray.map(function (id) { return thisScope.modules[id]; })); } }