diff --git a/devNotes/tests/diffProxyTest.js b/devNotes/tests/diffProxyTest.js deleted file mode 100644 index 72ec17ec588e68cdeda1321db39cd22df152e01f..0000000000000000000000000000000000000000 --- a/devNotes/tests/diffProxyTest.js +++ /dev/null @@ -1,82 +0,0 @@ -function tests() { - const getProxy = App.Utils.Diff.getProxy; - - function orig() { - return { - a: 1, - b: [1, 2], - c: {a: 1} - }; - } - - function log(name, p) { - console.log(name); - const original = p.diffOriginal; - const diff = p.diffChange; - console.log("Original:", _.cloneDeep(original)); - console.log("Diff: ", diff); - App.Utils.Diff.applyDiff(original, diff); - console.log("Apply: ", original); - } - - log("Proxy", getProxy(orig())); - - let o = getProxy(orig()); - o.a = 2; - log(1, o); - - o = getProxy(orig()); - delete o.a; - log(2, o); - - o = getProxy(orig()); - o.c.a = 2; - log(3, o); - - o = getProxy(orig()); - delete o.c.a; - log(4, o); - - o = getProxy(orig()); - delete o.c; - log(5, o); - - o = getProxy(orig()); - o.b[1] = 5; - log(6, o); - - o = getProxy(orig()); - o.b.push(5); - log(7, o); - - o = getProxy(orig()); - o.b.push(5); - console.log("EXPECT: 5, IS: ", o.b[2]); - log(8, o); - - o = getProxy(orig()); - console.log("POP 1:", o.b.pop()); - log(9, o); - - o = getProxy(orig()); - o.d = 7; - log(10, o); - - o = getProxy(orig()); - o.d = {a: 5}; - console.log("Expect 5:", o.d.a); - log(11, o); - o = getProxy(orig()); - - o.d = {a: [5]}; - o.d.a.unshift(9); - log(12, o); - - let slaveDummy = getProxy({eye: new App.Entity.EyeState()}); - eyeSurgery(slaveDummy, "left", "remove"); - log(20, slaveDummy); - - slaveDummy = getProxy({eye: new App.Entity.EyeState()}); - eyeSurgery(slaveDummy, "both", "remove"); - log(20, slaveDummy); -} diff --git a/js/dynamicJSLoading.js b/js/dynamicJSLoading.js new file mode 100644 index 0000000000000000000000000000000000000000..aafbcf33d65380d6fd4c63cacc48ee5105f7725c --- /dev/null +++ b/js/dynamicJSLoading.js @@ -0,0 +1,111 @@ +App.Loader = (function() { + /** + * Remember the last loaded script. + * @type {string} + */ + let lastScript = ""; + + /** + * @param {string} name + * @param {string} path Relative to the HTML file + * @returns {HTMLScriptElement} + */ + function loadScript(name, path) { + lastScript = name; + + const script = document.createElement("script"); + script.setAttribute("src", `${path}`); + document.head.append(script); + return script; + } + + /** + * To make sure the scripts are loaded series, keep a queue of scripts to be loaded and only load the next once the + * previous one is finished. + * + * @see nextScript + * + * @type {Array<()=>void>} + */ + const scriptQueue = []; + + class Group { + /** + * @param {string} path + */ + constructor(path) { + this._path = path; + this._scripts = []; + + group.set(path, this); + scriptQueue.push(() => { + this._scripts.push(loadScript(path, path + "/index.js")); + }); + } + + /** + * Loads a script as part of this group + * @param {string} subPath relative to group path + */ + queueSubscript(subPath) { + scriptQueue.push(() => { + this._scripts.push(loadScript(subPath, this._path + "/" + subPath + ".js")); + }); + } + + /** + * Removes all script elements belonging to this Group. Does not undo any changes these scripts did. + */ + unload() { + for (const script of this._scripts) { + document.head.removeChild(script); + } + } + } + + /** + * @type {Map<string, Group>} + */ + const group = new Map(); + + /** + * Loads the group located at path + * + * @param {string} path + */ + function loadGroup(path) { + if (group.has(path)) { + group.get(path).unload(); + } + new Group(path); + } + + /** + * Gives the group located at path + * + * @param {string} path + * @returns {Group} + */ + function getGroup(path) { + return group.get(path); + } + + function nextScript() { + if (scriptQueue.length > 0) { + scriptQueue.shift()(); + } + } + + return { + loadGroup: loadGroup, + getGroup: getGroup, + nextScript: nextScript, + executeTests: () => { + loadGroup("../tests"); + nextScript(); + }, + get lastScript() { + return lastScript; + } + }; +})(); diff --git a/tests/diffProxy.js b/tests/diffProxy.js new file mode 100644 index 0000000000000000000000000000000000000000..c991c2756307c933f3f726b03c323c91d8791420 --- /dev/null +++ b/tests/diffProxy.js @@ -0,0 +1,223 @@ +{ + const getProxy = App.Utils.Diff.getProxy; + + App.Testing.executeTest("Overwrite first level", () => { + }, () => { + let proxy = getProxy({a: 1}); + + proxy.a = 2; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {a: 1}); + App.Testing.equals(proxy.a, 2); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {a: 2}); + }, () => { + }); + + App.Testing.executeTest("Overwrite second level", () => { + }, () => { + let proxy = getProxy({c: {a: 1}}); + + proxy.c.a = 2; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {c: {a: 1}}); + App.Testing.equals(proxy.c.a, 2); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {c: {a: 2}}); + }, () => { + }); + + App.Testing.executeTest("Delete first level", () => { + }, () => { + let proxy = getProxy({a: 1}); + + delete proxy.a; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {a: 1}); + App.Testing.hasNoProperty(proxy, "a"); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {}); + }, () => { + }); + + App.Testing.executeTest("Delete in second level", () => { + }, () => { + let proxy = getProxy({c: {a: 1}}); + + delete proxy.c.a; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {c: {a: 1}}); + App.Testing.hasProperty(proxy, "c"); + App.Testing.hasNoProperty(proxy.c, "a"); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {c: {}}); + }, () => { + }); + + App.Testing.executeTest("Delete with second level", () => { + }, () => { + let proxy = getProxy({c: {a: 1}}); + + delete proxy.c; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {c: {a: 1}}); + App.Testing.hasNoProperty(proxy, "c"); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {}); + }, () => { + }); + + App.Testing.executeTest("add value", () => { + }, () => { + let proxy = getProxy({}); + + proxy.d = 7; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {}); + App.Testing.hasProperty(proxy, "d"); + App.Testing.equals(proxy.d, 7); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {d: 7}); + }, () => { + }); + + App.Testing.executeTest("add object", () => { + }, () => { + let proxy = getProxy({}); + + proxy.d = {a: 5}; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {}); + App.Testing.hasProperty(proxy, "d"); + App.Testing.hasProperty(proxy.d, "a"); + App.Testing.equals(proxy.d.a, 5); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {d: {a: 5}}); + }, () => { + }); + + App.Testing.executeTest("Overwrite array entry", () => { + }, () => { + let proxy = getProxy({b: [1, 2]}); + + proxy.b[1] = 5; + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {b: [1, 2]}); + App.Testing.equals(proxy.b, [1, 5]); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {b: [1, 5]}); + }, () => { + }); + + App.Testing.executeTest("array push", () => { + }, () => { + let proxy = getProxy({b: [1, 2]}); + + proxy.b.push(5); + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {b: [1, 2]}); + App.Testing.equals(proxy.b, [1, 2, 5]); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {b: [1, 2, 5]}); + }, () => { + }); + + App.Testing.executeTest("array pop", () => { + }, () => { + let proxy = getProxy({b: [1, 2]}); + + proxy.b.pop(); + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {b: [1, 2]}); + App.Testing.equals(proxy.b, [1]); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {b: [1]}); + }, () => { + }); + + App.Testing.executeTest("new array unshift", () => { + }, () => { + let proxy = getProxy({}); + + proxy.d = {a: [5]}; + proxy.d.a.unshift(9); + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {}); + App.Testing.hasProperty(proxy.d, "a"); + App.Testing.equals(proxy.d.a, [9, 5]); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original, {d: {a: [9, 5]}}); + }, () => { + }); + + + App.Testing.executeTest("remove one eye", () => { + }, () => { + let proxy = getProxy({eye: new App.Entity.EyeState()}); + + eyeSurgery(proxy, "left", "remove"); + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {eye: new App.Entity.EyeState()}); + App.Testing.equals(proxy.eye.left, null); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original.eye.left, null); + }, () => { + }); + + App.Testing.executeTest("remove both eyes", () => { + }, () => { + let proxy = getProxy({eye: new App.Entity.EyeState()}); + + eyeSurgery(proxy, "both", "remove"); + + const original = proxy.diffOriginal; + + App.Testing.equals(original, {eye: new App.Entity.EyeState()}); + App.Testing.equals(proxy.eye.left, null); + App.Testing.equals(proxy.eye.right, null); + + App.Utils.Diff.applyDiff(original, proxy.diffChange); + App.Testing.equals(original.eye.left, null); + App.Testing.equals(original.eye.right, null); + }, () => { + }); + + // Do this last + App.Testing.unitDone(); +} diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 0000000000000000000000000000000000000000..873c8564cc64305b4dc5a873b2c085054a6705ea --- /dev/null +++ b/tests/index.js @@ -0,0 +1,118 @@ +// Define Test framework and register all tests + +// To run all tests, type 'App.Loader.executeTests()' in the browser console. Make sure the HTML file has not been +// moved from the 'bin/' directory + +// First, create the test framework +App.Testing = (function() { + // First, abstract away anything test unrelated + const group = App.Loader.getGroup("../tests"); + + function addTestUnit(path) { + group.queueSubscript(path); + } + + let unitTotal = 0; + let unitSuccesses = 0; + + function start() { + App.Loader.nextScript(); + } + + function unitDone() { + console.log("Group '" + App.Loader.lastScript + "' done. Successful tests:", unitSuccesses, "of", unitTotal); + unitTotal = 0; + unitSuccesses = 0; + App.Loader.nextScript(); + } + + // Actual Testing functionality + class TestError extends Error { + } + + /** + * @param {string} name Unique test name + * @param {null|(()=>void)} prepare Prepare the global state + * @param {!(()=>void)} test Do the actual testing + * @param {null|(()=>void)} cleanup Clean the global state up + */ + function executeTest(name, prepare, test, cleanup) { + unitTotal++; + try { + if (prepare != null) { + prepare(); + } + } catch (e) { + console.log("PREPARE_FAILED", name, e); + tryCleanup(name, cleanup); + return; + } + try { + test(); + } catch (e) { + console.log("TEST_FAILED", name, e); + } + if (tryCleanup(name, cleanup)) { + unitSuccesses++; + } + } + + /** + * @param {string} name Unique test name + * @param {null|(()=>void)} cleanup Clean the global state up + */ + function tryCleanup(name, cleanup) { + try { + if (cleanup != null) { + cleanup(); + } + } catch (e) { + console.log("CLEANUP_FAILED", name, e); + return false; + } + return true; + } + + function equals(actual, expected) { + if (!_.isEqual(actual, expected)) { + throw new TestError("Actual value does not match expected value"); + } + } + + /** + * @param {Object} obj + * @param {string} property + */ + function hasProperty(obj, property) { + if (obj[property] === undefined) { + throw new TestError("Expected property does not exist"); + } + } + + /** + * @param {Object} obj + * @param {string} property + */ + function hasNoProperty(obj, property) { + if (obj[property] !== undefined) { + throw new TestError("Unexpected property exists"); + } + } + + function isType(value, expected) { + if (!(typeof value === expected)) { + throw new TestError("Actual type does not match expected type"); + } + } + + return { + addTestUnit, start, unitDone, executeTest, + equals, hasProperty, hasNoProperty: hasNoProperty, isType + }; +})(); + +// Now load all tests +App.Testing.addTestUnit("diffProxy"); + +// Finally, execute the tests +App.Testing.start();