From 76b43266fa0f43f8a661da884f0a764c6afc602f Mon Sep 17 00:00:00 2001 From: "Alexander J. Vincent" Date: Thu, 30 Nov 2017 23:36:06 -0800 Subject: [PATCH] #137, #139, GUI: Update configuration file format to spec. --- docs/gui/DistortionsGUI.js | 21 +- docs/gui/DistortionsRules.js | 5 +- docs/gui/HandlerNames.js | 6 +- docs/gui/OuterGridManager.js | 13 +- docs/gui/mainPanels/graphs.js | 88 +++++++- docs/gui/mainPanels/load.js | 88 ++------ docs/gui/mainPanels/membrane.js | 10 +- docs/gui/mainPanels/output.js | 99 ++++---- docs/gui/other.css | 5 + docs/gui/specification.html | 39 ++-- docs/gui/tests/loadPanel.js | 388 +++++++++++--------------------- docs/gui/tests/membranePanel.js | 12 +- docs/gui/tests/outputPanel.js | 47 ++-- docs/gui/tests/rules.js | 18 +- 14 files changed, 398 insertions(+), 441 deletions(-) diff --git a/docs/gui/DistortionsGUI.js b/docs/gui/DistortionsGUI.js index 523aa687..5283fa2b 100644 --- a/docs/gui/DistortionsGUI.js +++ b/docs/gui/DistortionsGUI.js @@ -114,7 +114,7 @@ const DistortionsGUI = window.DistortionsGUI = { for (let prop in rulesMap) { if (rulesMap[prop] instanceof DistortionsRules) data.rules[prop] = rulesMap[prop].configurationAsJSON(); - else if (prop in ["source", "sourceGraphIndex"]) + else if (prop === "source") data[prop] = rulesMap[prop]; else data.rules[prop] = rulesMap[prop]; @@ -135,7 +135,8 @@ const DistortionsGUI = window.DistortionsGUI = { // XXX ajvincent Need to let the GUI know this value name is taken return; - const valueFromSource = graph.valueGetterEditor.getValue(); + let valueFromSource = graph.valueGetterEditor.getValue(); + await DistortionsManager.BlobLoader.addNamedValue(valueName, valueFromSource); const panel = document.createElement("section"); @@ -152,12 +153,16 @@ const DistortionsGUI = window.DistortionsGUI = { const value = DistortionsManager.BlobLoader.valuesByName.get(panel.dataset.valueName); const rules = this.buildDistortions(panel, value); - DistortionsManager.valueNameToRulesMap.set( - panel.dataset.hash, { - "value": rules, - "source": valueFromSource, - } - ); + const distortionsSet = { + "about": { + "valueName": valueName, + "isFunction": (typeof value === "function"), + "getExample": valueFromSource.split("\n").slice(1, -2).join("\n"), + }, + "value": rules, + }; + DistortionsManager.valueNameToRulesMap.set(panel.dataset.hash, distortionsSet); + graph.distortionMaps.push(distortionsSet); OuterGridManager.panels.appendChild(panel); diff --git a/docs/gui/DistortionsRules.js b/docs/gui/DistortionsRules.js index 6da6b55c..d527fbe5 100644 --- a/docs/gui/DistortionsRules.js +++ b/docs/gui/DistortionsRules.js @@ -222,11 +222,8 @@ DistortionsRules.prototype = { throw new Error("Not implemented!"); }, - configurationAsJSON: function() { + exportJSON: function() { const rv = { - formatVersion: "0.8.2", - dataVersion: "0.1", - /* filterOwnKeys: [] || null, inheritOwnKeys: false, diff --git a/docs/gui/HandlerNames.js b/docs/gui/HandlerNames.js index e78813b9..2f625cde 100644 --- a/docs/gui/HandlerNames.js +++ b/docs/gui/HandlerNames.js @@ -85,13 +85,13 @@ const HandlerNames = window.HandlerNames = { * @param config {JSONObject} The configuration. */ importConfig: function(config) { - for (let i = 0; i < config.graphNames.length; i++) { - this.setRow(i, config.graphNames[i], config.graphSymbolLists.includes(i)); + for (let i = 0; i < config.graphs.length; i++) { + this.setRow(i, config.graphs[i].name, config.graphs[i].isSymbol); } const range = document.createRange(); const delButton = this.grid.getElementsByTagName("button")[ - config.graphNames.length + config.graphs.length ]; range.setEndBefore(this.grid.lastChild); range.setStartAfter(delButton); diff --git a/docs/gui/OuterGridManager.js b/docs/gui/OuterGridManager.js index 07c7a887..8059835b 100644 --- a/docs/gui/OuterGridManager.js +++ b/docs/gui/OuterGridManager.js @@ -113,8 +113,15 @@ const OuterGridManager = window.OuterGridManager = { // Update the cached configuration { const [graphNames, graphSymbolLists] = HandlerNames.serializableNames(); - config.graphNames = graphNames; - config.graphSymbolLists = graphSymbolLists; + if (!Array.isArray(config.graphs)) { + config.graphs = []; + } + while (config.graphs.length < graphNames.length) + config.graphs.push({}); + graphNames.forEach(function(name, index) { + config.graphs[index].name = name; + config.graphs[index].isSymbol = graphSymbolLists.includes(index); + }); } // Define our object graph managers @@ -126,8 +133,8 @@ const OuterGridManager = window.OuterGridManager = { this.graphNamesCache.controllers.push(new ObjectGraphManager()); } const controller = this.graphNamesCache.controllers[i]; + controller.importJSON(config.graphs[i]); controller.setGraphName(name); - controller.readDistortionsData(config.distortionsByGraph[i]); } const deadControllers = this.graphNamesCache.controllers.slice(names.length); diff --git a/docs/gui/mainPanels/graphs.js b/docs/gui/mainPanels/graphs.js index 34a417bd..efd5fd8f 100644 --- a/docs/gui/mainPanels/graphs.js +++ b/docs/gui/mainPanels/graphs.js @@ -1,10 +1,13 @@ function ObjectGraphManager() { this.radioClass = `graphpanel-${ObjectGraphManager.instanceCount}`; ObjectGraphManager.instanceCount++; - this.distortions = []; + + this.distortionMaps = []; this.passThroughEditor = null; this.valueGetterEditor = null; + this.jsonBase = null; + this.buildUI(); } ObjectGraphManager.instanceCount = 0; @@ -32,7 +35,58 @@ ObjectGraphManager.prototype.buildUI = function() { this.radio.addEventListener("change", this, true); } -ObjectGraphManager.prototype.setGraphName = function(name) { +ObjectGraphManager.prototype.importJSON = function(data) { + this.jsonBase = { + "name": data.name, + "isSymbol": data.isSymbol + }; +}; + +ObjectGraphManager.prototype.exportJSON = function(graphIndex) { + const rv = { + "name": this.jsonBase.name, + "isSymbol": this.jsonBase.isSymbol, + "passThroughSource": null, + "passThroughEnabled": null, + "primordialsPass": false, + "distortions": [], + }; + + if (this.passThroughEditor) { + let lines = this.passThroughEditor.getValue().split("\n"); + lines = lines.slice(2, -6); + rv.passThroughSource = lines.join("\n"); + + rv.passThroughEnabled = this.passThroughCheckbox.checked; + rv.primordialsPass = this.primordialsCheckbox.checked; + } + else { + const config = MembranePanel.cachedConfig; + const lastGraph = Array.isArray(config.graphs) ? config.graphs[graphIndex] : null; + if (lastGraph) { + rv.passThroughSource = lastGraph.passThroughSource; + rv.passThroughEnabled = lastGraph.passThroughEnabled; + rv.primordialsPass = lastGraph.primordialsPass; + } + } + + this.distortionMaps.forEach(function(dm) { + let d = {}, keys = Reflect.ownKeys(dm); + keys.forEach(function(key) { + if (key === "about") { + d[key] = dm[key]; + return; + } + d[key] = dm[key].exportJSON(); + }); + + rv.distortions.push(d); + }); + + return rv; +}; + +ObjectGraphManager.prototype.setGraphName = function(name, jsonName, isSymbol) { this.groupLabel.firstChild.nodeValue = name; if (!this.groupLabel.parentNode) { OuterGridManager.addGraphUI( @@ -41,10 +95,6 @@ ObjectGraphManager.prototype.setGraphName = function(name) { } }; -ObjectGraphManager.prototype.readDistortionsData = function(data) { - -}; - ObjectGraphManager.prototype.remove = function() { }; @@ -88,6 +138,32 @@ ObjectGraphManager.prototype.handlePassThrough = function(event) { CodeMirrorManager.setEditorEnabled(this.passThroughEditor, checked); }; +ObjectGraphManager.prototype.getPassThrough = function(fullSource = false) { + if (!this.passThroughCheckbox.checked) + return null; + + if (!this.passThroughEditor) { + const config = MembranePanel.cachedConfig; + if (Array.isArray(config.graphs)) { + let index = OuterGridManager.graphNamesCache.controllers.indexOf(this); + if (config.graphs[index]) + return config.graphs[index].passThrough; + } + return null; + } + + if (!fullSource) { + let lines = this.passThroughEditor.getValue().split("\n"); + lines = lines.slice(2, -6); + return lines.join("\n"); + } + + let value = this.passThroughEditor.getValue(); + value = value.substr(value.indexOf("(")); + value = value.replace(/;\n$/, ",\n"); + return value; +}; + ObjectGraphManager.prototype.handleEvent = function(event) { if (event.target === this.primordialsCheckbox) return this.handlePrimordials(event); diff --git a/docs/gui/mainPanels/load.js b/docs/gui/mainPanels/load.js index 08a8971f..0d69ef63 100644 --- a/docs/gui/mainPanels/load.js +++ b/docs/gui/mainPanels/load.js @@ -32,9 +32,8 @@ window.LoadPanel = { } }, - validateDistortions: - function(instructions, index, graphIndex, graphsTotal) { - const errorPrefix = `config.distortionsByGraph[${graphIndex}][${index}]`; + validateDistortions: function(instructions, index, graphIndex) { + const errorPrefix = `config.graphs[${graphIndex}].distortions[${index}]`; function requireType(field, type) { if (typeof instructions[field] !== type) throw new Error(`${errorPrefix}.${field} must be of type ${type}`); @@ -49,22 +48,6 @@ window.LoadPanel = { requireType("rules", "object"); // XXX ajvincent We're not going to attempt parsing instructions.source now. - - { - const max = graphsTotal - 1; - if (!Number.isInteger(instructions.sourceGraphIndex) || - (instructions.sourceGraphIndex < 0) || - (instructions.sourceGraphIndex >= graphsTotal)) - throw new Error( - `${errorPrefix}.sourceGraphIndex must be an integer from 0 to ${max}` - ); - } - - if (instructions.sourceGraphIndex === graphIndex) - throw new Error( - `${errorPrefix}.sourceGraphIndex cannot be the target graph index ${graphIndex}` - ); - const rulesMembers = ["value"]; if (instructions.isFunction) { /* @@ -135,15 +118,14 @@ window.LoadPanel = { } var config = { - commonFiles: [], - passThrough: null, - graphNames: [], - graphSymbolLists: [], - distortionsByGraph: [], + "configurationSetup": {}, + "membrane": {}, + "graphs": [] }; if (!this.configFileInput.files.length && ( !this.testMode || !this.testMode.configSource)) return config; + try { { let p, jsonAsText; @@ -161,55 +143,33 @@ window.LoadPanel = { // Validate the configuration. { - if (!Array.isArray(config.graphNames)) - throw new Error("config.graphNames must be an array of strings"); - - if (!Array.isArray(config.graphSymbolLists) || - !config.graphSymbolLists.every(function(key, index) { - let rv = (Number.isInteger(key) && (0 <= key) && - (key < config.graphNames.length)); - if (rv && (index > 0)) - rv = key > config.graphSymbolLists[index - 1]; - return rv; - })) - throw new Error( - "config.graphSymbolLists must be an ordered array of unique " + - "non-negative integers, each member of which is less than " + - "config.graphNames.length" - ); + if (!Array.isArray(config.graphs)) + throw new Error("config.graphs must be an array of objects"); let stringKeys = new Set(); - config.graphNames.forEach((key, index) => { - if (typeof key !== "string") - throw new Error("config.graphNames must be an array of strings"); - if (!(config.graphSymbolLists.includes(index))) { - if (stringKeys.has(key)) { + config.graphs.forEach((graph, graphIndex) => { + if (typeof graph.name !== "string") + throw new Error(`config.graphs[${graphIndex}].name must be a string`); + if (typeof graph.isSymbol !== "boolean") + throw new Error(`config.graphs[${graphIndex}].isSymbol must be a boolean`); + if (!graph.isSymbol) { + if (stringKeys.has(graph.name)) { throw new Error( - `config.graphNames[${index}] = "${key}", ` + - "but this string name appears earlier in config.graphNames" + `config.graphs[${graphIndex}].name = "${graph.name}", ` + + "but this name appears earlier in config.graphs, and neither name is a symbol" ); } - stringKeys.add(key); + stringKeys.add(graph.name); } - }); - if (!Array.isArray(config.distortionsByGraph) || - (config.distortionsByGraph.length != config.graphNames.length) || - !config.distortionsByGraph.every(Array.isArray)) - { - throw new Error( - `config.distortionsByGraph must be an array with length ` + - config.graphNames.length + ` of arrays` - ); - } + if (!Array.isArray(graph.distortions)) { + throw new Error(`config.graphs[${graphIndex}].distortions must be an array`); + } - config.distortionsByGraph.forEach(function(items, graphIndex) { - items.forEach(function(item, index) { - this.validateDistortions( - item, index, graphIndex, config.graphNames.length - ); + graph.distortions.forEach(function(item, index) { + this.validateDistortions(item, index, graphIndex); }, this); - }, this); + }); } HandlerNames.importConfig(config); diff --git a/docs/gui/mainPanels/membrane.js b/docs/gui/mainPanels/membrane.js index fa711bde..2d79f62f 100644 --- a/docs/gui/mainPanels/membrane.js +++ b/docs/gui/mainPanels/membrane.js @@ -45,9 +45,13 @@ window.MembranePanel = { return this.handlePassThrough(event); }, - getPassThrough: function() { - if (!this.passThroughCheckbox.checked) - return null; + getPassThrough: function(fullSource = false) { + if (!fullSource) { + let lines = this.passThroughEditor.getValue().split("\n"); + lines = lines.slice(2, -6); + return lines.join("\n"); + } + let value = this.passThroughEditor.getValue(); value = value.substr(value.indexOf("(")); value = value.replace(/;\n$/, ",\n"); diff --git a/docs/gui/mainPanels/output.js b/docs/gui/mainPanels/output.js index 039e62cf..78e0b4db 100644 --- a/docs/gui/mainPanels/output.js +++ b/docs/gui/mainPanels/output.js @@ -68,25 +68,32 @@ const OutputPanel = window.OutputPanel = { for (let i = 0; i < fileList.length; i++) commonFiles.push(fileList[i].name); } - const PassThroughSource = MembranePanel.getPassThrough(); - - var [graphNames, graphSymbolLists] = HandlerNames.serializableNames(); /************************************************************************** - * Step 2: Get the DistortionsRules' configurations. * + * Step 2: Generate Distortions GUI JSON file. * **************************************************************************/ - const distortionsData = DistortionsGUI.metadataInGraphOrder(); + const guiConfigAsJSON = { + "configurationSetup": { + "commonFiles": commonFiles, + "formatVersion": 1.0, + "lastUpdated": (new Date()).toUTCString(), + }, + "membrane": { + "passThroughSource": MembranePanel.getPassThrough(), + "passThroughEnabled": MembranePanel.passThroughCheckbox.checked, + "primordialsPass": MembranePanel.primordialsCheckbox.checked, + }, + + "graphs": [] + }; - /************************************************************************** - * Step 3: Generate Distortions GUI JSON file. * - **************************************************************************/ - const guiConfig = JSON.stringify({ - "commonFiles": commonFiles, - "passThrough": PassThroughSource, - "graphNames": graphNames, - "graphSymbolLists": graphSymbolLists, - "distortionsByGraph": distortionsData, - }, null, 2) + "\n"; + { + const controllers = OuterGridManager.graphNamesCache.controllers; + controllers.forEach(function(c, i) { + guiConfigAsJSON.graphs.push(c.exportJSON(i)); + }); + } + const guiConfig = JSON.stringify(guiConfigAsJSON, null, 2) + "\n"; this.configEditor.setValue(guiConfig); { @@ -96,40 +103,56 @@ const OutputPanel = window.OutputPanel = { } /************************************************************************** - * Step 4: Generate Membrane crafting JavaScript file. * + * Step 3: Generate Membrane crafting JavaScript file. * **************************************************************************/ - var script = `function buildMembrane(utilities) { + const PassThroughSource = MembranePanel.getPassThrough(true); + const formattedNames = HandlerNames.getFormattedNames(); + var script = `function buildMembrane(___utilities___) { "use strict"; - const devMembrane = new Membrane({ - logger: (utilities.logger || null)`; + const rvMembrane = new Membrane({ + logger: (___utilities___.logger || null)`; if (PassThroughSource) { script += `, passThrough: ${PassThroughSource.replace(/\n/gm, "\n ")}`; } script += ` }); - const graphNames = [\n ${HandlerNames.getFormattedNames().join(",\n ")}\n ]; - const graphs = graphNames.map(function(name) { - return devMembrane.getHandlerByName(name, true); - });\n\n`; - distortionsData.forEach(function(dataArray, graphIndex) { - if (dataArray.length === 0) - return; - const nl = "\n "; - script += ` {\n const rules = devMembrane.modifyRules.createDistortionsListener();\n`; - dataArray.forEach(function(data) { - script += `${nl}rules.addListener(${data.name}, "value", `; - script += JSON.stringify(data.rules.value, null, 2).replace(/\n/gm, nl) + `);\n`; - if (!("proto" in data.rules)) - return; - script += `${nl}rules.addListener(${data.name}, "proto", `; - script += JSON.stringify(data.rules.proto, null, 2).replace(/\n/gm, nl) + `);\n`; + +`; + + { + const controllers = OuterGridManager.graphNamesCache.controllers; + controllers.forEach(function(c, index) { + const graph = guiConfigAsJSON.graphs[index]; + + script += ` {\n `; + if (graph.distortions.length || c.passThroughCheckbox.checked) + script += `const ___graph___ = `; + script += `rvMembrane.getHandlerByName(${formattedNames[index]}, { mustCreate: true });\n`; + + if (c.passThroughCheckbox.checked) { + script += ` ___graph___.passThroughFilter = ${c.getPassThrough(true)};\n`; + } + + if (graph.distortions.length) { + script += ` const ___listener___ = rvMembrane.modifyRules.createDistortionsListener();\n`; + graph.distortions.forEach(function(d) { + const keys = Reflect.ownKeys(d); + keys.splice(keys.indexOf("about"), 1); + keys.forEach(function(k) { + const distortionAsJSON = JSON.stringify(d[k], null, 2).replace(/\n/gm, "\n "); + script += ` ___listener___.addListener(${d.about.valueName}, "${k}", ${distortionAsJSON});\n\n`; + }); + }); + script += ` ___listener___.bindToHandler(___graph___);\n`; + } + + script += ` }\n\n`; }); - script += `${nl}rules.bindToHandler(graphs[${graphIndex}]);\n }\n\n`; - }); + } - script += " return devMembrane;\n}\n"; + script += " return rvMembrane;\n}\n"; this.jsEditor.setValue(script); { diff --git a/docs/gui/other.css b/docs/gui/other.css index 93b8026b..b3ce68b6 100644 --- a/docs/gui/other.css +++ b/docs/gui/other.css @@ -15,3 +15,8 @@ *.CodeMirror.disabled { opacity: 0.3; } + +h2 { + margin-top: 0; + margin-bottom: 0; +} diff --git a/docs/gui/specification.html b/docs/gui/specification.html index 7c0f8035..d45a78c2 100644 --- a/docs/gui/specification.html +++ b/docs/gui/specification.html @@ -48,7 +48,7 @@

Root structure

/* a floating point number representing this format version */ "formatVersion": 1.0, - /* UTC datetime stamp for when this was last generated. + /* UTC datetime stamp for when this was last generated. */ "lastUpdated": "", /* String[] of user comments. Optional. */ @@ -57,7 +57,9 @@

Root structure

"membrane": { /* source code of membrane's passthrough function, or null */ - "passThrough": "", + "passThroughSource": "", + "passThroughEnabled": false, + "primordialsPass": false, /* String[] of user comments. Optional. */ "comments": [], @@ -73,7 +75,9 @@

Root structure

"isSymbol": false, /* true if the graph name is a Symbol */ /* source code of object graph's passthrough function, or null */ - "passThrough": "", + "passThroughSource": "", + "passThroughEnabled": false, + "primordialsPass": false, "distortions": [ /* Distortions configuration objects */ @@ -102,15 +106,22 @@

Root structure

Distortions configuration objects

diff --git a/docs/gui/tests/loadPanel.js b/docs/gui/tests/loadPanel.js index 114c63a5..6cf98f84 100644 --- a/docs/gui/tests/loadPanel.js +++ b/docs/gui/tests/loadPanel.js @@ -5,7 +5,7 @@ describe("Load Panel Operations:", function() { beforeEach(async function() { await getDocumentLoadPromise("base/gui/index.html"); window = testFrame.contentWindow; - window.LoadPanel.testMode = {}; + window.LoadPanel.testMode = {fakeFiles: true}; OGM = window.OuterGridManager; }); @@ -32,15 +32,29 @@ describe("Load Panel Operations:", function() { it("can import a simple configuration with test mode", async function() { window.LoadPanel.testMode.configSource = `{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [2], - "distortionsByGraph": [ - [], - [], - [] + "configurationSetup": {}, + "graphs": [ + { + "name": "wet", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "dry", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "damp", + "isSymbol": true, + "distortions": [] + } ] }`; await chooseMembranePanel(); + let temp = await window.LoadPanel.getConfiguration(); let [graphNames, graphSymbolLists] = window.HandlerNames.serializableNames(); expect(graphNames).toEqual(["wet", "dry", "damp"]); @@ -50,21 +64,6 @@ describe("Load Panel Operations:", function() { const valid = window.MembranePanel.form.checkValidity(); expect(valid).toBe(true); - - if (!valid) - return; - - let p1 = MessageEventPromise( - window, "OuterGridManager: object graphs defined" - ); - let p2 = MessageEventPromise( - window, "Graph panel shown: graphpanel-0" - ); - window.MembranePanel.form.submit(); - await Promise.all([p1, p2]); - - expect(OGM.graphNamesCache.lastVisibleGraph).not.toBe(null); - expect(OGM.selectedTabs.file).toBe(OGM.graphNamesCache.lastVisibleGraph.radio); }); describe("tests for configuration file errors", function() { @@ -79,232 +78,157 @@ describe("Load Panel Operations:", function() { it("with a not-well-formed JSON file", async function() { await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [2] + "configurationSetup": {}, + "graphs": [ + { + "name": "wet", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "dry", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "damp", + "isSymbol": true, + "distortions": [] + } + ] `); // missing closing brace expect(getErrorMessage()).not.toBe(null); }); - it("with graphNames not being an array", async function() { - await expectError(`{ - "graphNames": 4, - "graphSymbolLists": [2] - }`); - - expect(getErrorMessage()).toBe( - "config.graphNames must be an array of strings" - ); - }); - - it("with graphSymbolLists not being an array", async function() { - await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": 2 - }`); - - expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" - ); - }); - - it("with graphSymbolLists holding a number too big", async function() { - await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [3] - }`); - - expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" - ); - }); - - it("with graphSymbolLists holding a number too small", async function() { + it("with graphs not being an array", async function() { await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [-1] + "graphs": 4 }`); expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" - ); - }); - - it("with graphSymbolLists holding a number not an integer", async function() { - await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [0.5] - }`); - - expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" - ); - }); - - it("with graphSymbolLists holding a string value", async function() { - await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": ["wet"] - }`); - - expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" - ); - }); - - it("with graphSymbolLists holding a duplicate value", async function() { - await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [2, 2] - }`); - - expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" - ); - }); - - it("with graphSymbolLists holding a value out of order", async function() { - await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [2, 1] - }`); - - expect(getErrorMessage()).toBe( - "config.graphSymbolLists must be an ordered array of unique non-negative integers, each member of which is less than config.graphNames.length" + "config.graphs must be an array of objects" ); }); it("with duplicate non-symbol graphNames", async function() { await expectError(`{ - "graphNames": ["wet", "dry", "wet"], - "graphSymbolLists": [] + "configurationSetup": {}, + "graphs": [ + { + "name": "wet", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "dry", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "wet", + "isSymbol": false, + "distortions": [] + } + ] }`); expect(getErrorMessage()).toBe( - `config.graphNames[2] = "wet", but this string name appears earlier in config.graphNames` + `config.graphs[2].name = "wet", but this name appears earlier in config.graphs, and neither name is a symbol` ); }); - it("with a missing graphDistortions property", async function() { + it("with a missing distortions property", async function() { await expectError(`{ - "graphNames": ["wet", "dry"], - "graphSymbolLists": [] + "configurationSetup": {}, + "graphs": [ + { + "name": "wet", + "isSymbol": false + }, + + { + "name": "dry", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "damp", + "isSymbol": true, + "distortions": [] + } + ] }`); expect(getErrorMessage()).toBe( - `config.distortionsByGraph must be an array with length 2 of arrays` + `config.graphs[0].distortions must be an array` ); }); - it("with a graphDistortions property not an array", async function() { + it("with a distortions property not an array", async function() { await expectError(`{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [], - "graphDistortions": false + "configurationSetup": {}, + "graphs": [ + { + "name": "wet", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "dry", + "isSymbol": false, + "distortions": false + }, + + { + "name": "damp", + "isSymbol": true, + "distortions": [] + } + ] }`); expect(getErrorMessage()).toBe( - `config.distortionsByGraph must be an array with length 3 of arrays` + `config.graphs[1].distortions must be an array` ); }); - it( - "with a graphDistortions array containing a non-array", - async function() { - await expectError(`{ - "graphNames": ["wet", "dry"], - "graphSymbolLists": [], - "graphDistortions": [[], false] - }`); - expect(getErrorMessage()).toBe( - `config.distortionsByGraph must be an array with length 2 of arrays` - ); - } - ); - - it( - "with a graphDistortions array containing the wrong number of arrays", - async function() { - await expectError(`{ - "graphNames": ["wet", "dry"], - "graphSymbolLists": [], - "graphDistortions": [[]] - }`); - expect(getErrorMessage()).toBe( - `config.distortionsByGraph must be an array with length 2 of arrays` - ); - } - ); - - it( - "can progress after fixing the error", - async function() { - await expectError(`{ - "graphNames": ["wet", "dry"], - "graphSymbolLists": [], - "graphDistortions": [[]] - }`); - OGM.loadPanelRadio.click(); - - window.LoadPanel.testMode.configSource = `{ - "graphNames": ["wet", "dry", "damp"], - "graphSymbolLists": [2], - "distortionsByGraph": [ - [], - [], - [] - ] - }`; - - let p = MessageEventPromise( - window, "MembranePanel cached configuration reset" - ); - OGM.membranePanelRadio.click(); - await p; - - let [graphNames, graphSymbolLists] = window.HandlerNames.serializableNames(); - expect(graphNames).toEqual(["wet", "dry", "damp"]); - expect(graphSymbolLists).toEqual([2]); - - expect(getErrorMessage()).toBe(null); - - const valid = window.MembranePanel.form.checkValidity(); - expect(valid).toBe(true); - - if (!valid) - return; - - let p1 = MessageEventPromise( - window, "OuterGridManager: object graphs defined" - ); - let p2 = MessageEventPromise( - window, "Graph panel shown: graphpanel-0" - ); - window.MembranePanel.form.submit(); - await Promise.all([p1, p2]); - - expect(OGM.graphNamesCache.lastVisibleGraph).not.toBe(null); - expect(OGM.selectedTabs.file).toBe(OGM.graphNamesCache.lastVisibleGraph.radio); - } - ); - describe("with a set of distortions rules: ", function() { function getFullConfig(middle) { return `{ - "graphNames": ["wet", "dry"], - "graphSymbolLists": [], - "distortionsByGraph": [ - [], - [${middle}] + "configurationSetup": {}, + "graphs": [ + { + "name": "wet", + "isSymbol": false, + "distortions": [${middle}] + }, + + { + "name": "dry", + "isSymbol": false, + "distortions": [] + }, + + { + "name": "damp", + "isSymbol": true, + "distortions": [] + } ] }`; } - const errPrefix = 'config.distortionsByGraph[1][0]'; + const errPrefix = 'config.graphs[0].distortions[0]'; let valid; beforeEach(function() { valid = { "name": "ObjectGraphHandler", "source": "function() {\n return ObjectGraphHandler;\n};\n", - "sourceGraphIndex": 0, "rules": { "value": { "formatVersion": "0.8.2", @@ -389,62 +313,6 @@ describe("Load Panel Operations:", function() { await runTestForFailure(".rules must be of type object"); }); - it("missing sourceGraphIndex", async function() { - delete valid.sourceGraphIndex; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("string sourceGraphIndex", async function() { - valid.sourceGraphIndex = ""; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("NaN sourceGraphIndex", async function() { - valid.sourceGraphIndex = NaN; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("negative sourceGraphIndex", async function() { - valid.sourceGraphIndex = -1; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("Infinity sourceGraphIndex", async function() { - valid.sourceGraphIndex = Infinity; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("non-integer sourceGraphIndex", async function() { - valid.sourceGraphIndex = 0.5; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("sourceGraphIndex too large", async function() { - valid.sourceGraphIndex = 2; - await runTestForFailure( - ".sourceGraphIndex must be an integer from 0 to 1" - ); - }); - - it("sourceGraphIndex matches graphIndex", async function() { - valid.sourceGraphIndex = 1; - await runTestForFailure( - ".sourceGraphIndex cannot be the target graph index 1" - ); - }); - it("rules.value is missing", async function() { delete valid.rules.value; await runTestForFailure(".rules.value must be an object"); diff --git a/docs/gui/tests/membranePanel.js b/docs/gui/tests/membranePanel.js index ca7f3032..c97573fe 100644 --- a/docs/gui/tests/membranePanel.js +++ b/docs/gui/tests/membranePanel.js @@ -49,7 +49,6 @@ describe("Membrane Panel Operations:", function() { expect(window.MembranePanel.primordialsCheckbox.checked).toBe(false); expect(window.MembranePanel.primordialsCheckbox.disabled).toBe(true); expect(window.CodeMirrorManager.getEditorEnabled(editor)).toBe(false); - expect(window.MembranePanel.getPassThrough()).toBe(null); } { @@ -57,10 +56,6 @@ describe("Membrane Panel Operations:", function() { expect(window.MembranePanel.passThroughCheckbox.checked).toBe(true); expect(window.MembranePanel.primordialsCheckbox.disabled).toBe(false); expect(window.CodeMirrorManager.getEditorEnabled(editor)).toBe(true); - - const prelim = "(function() {\n const items = [];\n\n"; - const value = window.MembranePanel.getPassThrough(); - expect(value.startsWith(prelim)).toBe(true); } { @@ -71,7 +66,7 @@ describe("Membrane Panel Operations:", function() { expect(window.CodeMirrorManager.getEditorEnabled(editor)).toBe(true); const prelim = "(function() {\n const items = Membrane.Primordials.slice(0);\n\n"; - const value = window.MembranePanel.getPassThrough(); + const value = window.MembranePanel.getPassThrough(true); expect(value.startsWith(prelim)).toBe(true); } @@ -81,10 +76,6 @@ describe("Membrane Panel Operations:", function() { expect(window.MembranePanel.primordialsCheckbox.checked).toBe(false); expect(window.MembranePanel.primordialsCheckbox.disabled).toBe(false); expect(window.CodeMirrorManager.getEditorEnabled(editor)).toBe(true); - - const prelim = "(function() {\n const items = [];\n\n"; - const value = window.MembranePanel.getPassThrough(); - expect(value.startsWith(prelim)).toBe(true); } { @@ -93,7 +84,6 @@ describe("Membrane Panel Operations:", function() { expect(window.MembranePanel.primordialsCheckbox.checked).toBe(false); expect(window.MembranePanel.primordialsCheckbox.disabled).toBe(true); expect(window.CodeMirrorManager.getEditorEnabled(editor)).toBe(false); - expect(window.MembranePanel.getPassThrough()).toBe(null); } }); }); diff --git a/docs/gui/tests/outputPanel.js b/docs/gui/tests/outputPanel.js index 85b1692b..38a2d95c 100644 --- a/docs/gui/tests/outputPanel.js +++ b/docs/gui/tests/outputPanel.js @@ -38,17 +38,30 @@ describe("Output panel", function() { { let actualJSON = await getJSON(); - expect(actualJSON.graphNames).toEqual(["wet", "dry"]); - expect(actualJSON.graphSymbolLists).toEqual([]); + expect(Array.isArray(actualJSON.graphs)).toBe(true); + expect(actualJSON.graphs[0].name).toBe("wet"); + expect(actualJSON.graphs[0].isSymbol).toBe(false); + expect(actualJSON.graphs[1].name).toBe("dry"); + expect(actualJSON.graphs[1].isSymbol).toBe(false); } + await membranePanelSelect(); window.HandlerNames.setRow(2, "damp", true); - window.OutputPanel.update(); + OGM.defineGraphs(); + + await linkUpdatePromise(); { let actualJSON = await getJSON(); - expect(actualJSON.graphNames).toEqual(["wet", "dry", "damp"]); - expect(actualJSON.graphSymbolLists).toEqual([2]); + + expect(Array.isArray(actualJSON.graphs)).toBe(true); + expect(actualJSON.graphs[0].name).toBe("wet"); + expect(actualJSON.graphs[0].isSymbol).toBe(false); + expect(actualJSON.graphs[1].name).toBe("dry"); + expect(actualJSON.graphs[1].isSymbol).toBe(false); + + expect(actualJSON.graphs[2].name).toBe("damp"); + expect(actualJSON.graphs[2].isSymbol).toBe(true); } // XXX ajvincent Checking for files depends on issues #121, 122. @@ -58,7 +71,9 @@ describe("Output panel", function() { await getGUIMocksPromise([]); { let actualJSON = await getJSON(); - expect(actualJSON.passThrough).toBe(null); + expect(actualJSON.membrane.passThroughSource).toBe(""); + expect(actualJSON.membrane.passThroughEnabled).toBe(false); + expect(actualJSON.membrane.primordialsPass).toBe(false); } { @@ -67,9 +82,9 @@ describe("Output panel", function() { await linkUpdatePromise(); let actualJSON = await getJSON(); - expect(typeof actualJSON.passThrough).toBe("string"); - const prelim = "(function() {\n const items = [];\n\n"; - expect(actualJSON.passThrough.startsWith(prelim)).toBe(true); + expect(actualJSON.membrane.passThroughSource).toBe(""); + expect(actualJSON.membrane.passThroughEnabled).toBe(true); + expect(actualJSON.membrane.primordialsPass).toBe(false); } { @@ -79,9 +94,9 @@ describe("Output panel", function() { await linkUpdatePromise(); let actualJSON = await getJSON(); - expect(typeof actualJSON.passThrough).toBe("string"); - const prelim = "(function() {\n const items = Membrane.Primordials.slice(0);\n\n"; - expect(actualJSON.passThrough.startsWith(prelim)).toBe(true); + expect(actualJSON.membrane.passThroughSource).toBe(""); + expect(actualJSON.membrane.passThroughEnabled).toBe(true); + expect(actualJSON.membrane.primordialsPass).toBe(true); } { @@ -91,7 +106,9 @@ describe("Output panel", function() { await linkUpdatePromise(); let actualJSON = await getJSON(); - expect(actualJSON.passThrough).toBe(null); + expect(actualJSON.membrane.passThroughSource).toBe(""); + expect(actualJSON.membrane.passThroughEnabled).toBe(false); + expect(actualJSON.membrane.primordialsPass).toBe(true); } }); }); @@ -106,9 +123,7 @@ describe("Output panel", function() { async function testScriptForSyntax() { let url = window.OutputPanel.jsLink.getAttribute("href"); let source = await XHRPromise(url); - expect( - source.startsWith("function buildMembrane(utilities) {\n") - ).toBe(true); + expect(source.startsWith("function buildMembrane(")).toBe(true); source = `function() {\n${source}\nreturn true;\n}`; const BlobLoader = window.DistortionsManager.BlobLoader; diff --git a/docs/gui/tests/rules.js b/docs/gui/tests/rules.js index 680db456..5a5fdb28 100644 --- a/docs/gui/tests/rules.js +++ b/docs/gui/tests/rules.js @@ -120,7 +120,7 @@ describe("DistortionsRules", function() { // Configuration dynamic properties. { - let config = rules.configurationAsJSON(); + let config = rules.exportJSON(); delete config.formatVersion; delete config.dataVersion; @@ -138,7 +138,7 @@ describe("DistortionsRules", function() { // Configuration update tests testCheckboxState( filterKeysCheckbox, - () => Array.isArray(rules.configurationAsJSON().filterOwnKeys) + () => Array.isArray(rules.exportJSON().filterOwnKeys) ); expectedKeys.forEach(function(key) { @@ -147,7 +147,7 @@ describe("DistortionsRules", function() { if (!checkbox) return; testCheckboxState( - checkbox, () => rules.configurationAsJSON().filterOwnKeys.includes(key) + checkbox, () => rules.exportJSON().filterOwnKeys.includes(key) ); }); @@ -168,7 +168,7 @@ describe("DistortionsRules", function() { ].forEach(function(key) { const checkbox = getCheckbox("traps", key); testCheckboxState( - checkbox, () => rules.configurationAsJSON().proxyTraps.includes(key) + checkbox, () => rules.exportJSON().proxyTraps.includes(key) ); }); @@ -182,18 +182,18 @@ describe("DistortionsRules", function() { expect(checkbox).not.toBe(undefined); if (!checkbox) return; - testCheckboxState(checkbox, () => rules.configurationAsJSON()[key]); + testCheckboxState(checkbox, () => rules.exportJSON()[key]); }); if (isFunction) { for (let i = 0; i < 2; i++) { - expect(rules.configurationAsJSON().truncateArgList).toBe(false); + expect(rules.exportJSON().truncateArgList).toBe(false); truncateArgButton.click(); - expect(rules.configurationAsJSON().truncateArgList).toBe(true); + expect(rules.exportJSON().truncateArgList).toBe(true); truncateArgButton.click(); - expect(rules.configurationAsJSON().truncateArgList).toBe(i); + expect(rules.exportJSON().truncateArgList).toBe(i); truncateArgMax.stepUp(); - expect(rules.configurationAsJSON().truncateArgList).toBe(i + 1); + expect(rules.exportJSON().truncateArgList).toBe(i + 1); truncateArgButton.click(); } }