mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Refactor Subflow logic into own class
This commit is contained in:
		| @@ -35,6 +35,9 @@ function Node(n) { | ||||
|     if (n._alias) { | ||||
|         this._alias = n._alias; | ||||
|     } | ||||
|     if (n._flow) { | ||||
|         this._flow = n._flow; | ||||
|     } | ||||
|     this.updateWires(n.wires); | ||||
| } | ||||
|  | ||||
| @@ -131,10 +134,12 @@ Node.prototype.send = function(msg) { | ||||
|                 msg._msgid = redUtil.generateId(); | ||||
|             } | ||||
|             this.metric("send",msg); | ||||
|             node = flows.get(this._wire); | ||||
|             node = this._flow.getNode(this._wire); | ||||
|             /* istanbul ignore else */ | ||||
|             if (node) { | ||||
|                 node.receive(msg); | ||||
|             } else { | ||||
|                 console.log("trying to send to a node not on this flow",this._wire); | ||||
|             } | ||||
|             return; | ||||
|         } else { | ||||
| @@ -163,7 +168,7 @@ Node.prototype.send = function(msg) { | ||||
|                 var k = 0; | ||||
|                 // for each recipent node of that output | ||||
|                 for (var j = 0; j < wires.length; j++) { | ||||
|                     node = flows.get(wires[j]); // node at end of wire j | ||||
|                     node = this._flow.getNode(wires[j]); // node at end of wire j | ||||
|                     if (node) { | ||||
|                         // for each msg to send eg. [[m1, m2, ...], ...] | ||||
|                         for (k = 0; k < msgs.length; k++) { | ||||
| @@ -182,6 +187,8 @@ Node.prototype.send = function(msg) { | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|                         console.log("trying to send to a node not on this flow",this._wire); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -251,7 +258,7 @@ Node.prototype.error = function(logMessage,msg) { | ||||
|     } | ||||
|     var handled = false; | ||||
|     if (msg) { | ||||
|         handled = flows.handleError(this,logMessage,msg); | ||||
|         handled = this._flow.handleError(this,logMessage,msg); | ||||
|     } | ||||
|     if (!handled) { | ||||
|         log_helper(this, Log.ERROR, logMessage); | ||||
| @@ -286,6 +293,6 @@ Node.prototype.metric = function(eventname, msg, metricValue) { | ||||
|  * status: { fill:"red|green", shape:"dot|ring", text:"blah" } | ||||
|  */ | ||||
| Node.prototype.status = function(status) { | ||||
|     flows.handleStatus(this,status); | ||||
|     this._flow.handleStatus(this,status); | ||||
| }; | ||||
| module.exports = Node; | ||||
|   | ||||
| @@ -16,33 +16,68 @@ | ||||
|  | ||||
| var when = require("when"); | ||||
| var clone = require("clone"); | ||||
| var typeRegistry = require("@node-red/registry"); | ||||
| var Subflow; | ||||
| var Log; | ||||
| var redUtil = require("@node-red/util").util; | ||||
| var flowUtil = require("./util"); | ||||
| var events = require("../../events"); | ||||
|  | ||||
| var nodeCloseTimeout = 15000; | ||||
|  | ||||
| class Flow { | ||||
|     constructor(global,flow) { | ||||
|         this.global = global; | ||||
|     constructor(parent,globalFlow,flow) { | ||||
|         this.TYPE = 'flow'; | ||||
|         this.parent = parent; | ||||
|         this.global = globalFlow; | ||||
|         if (typeof flow === 'undefined') { | ||||
|             this.flow = global; | ||||
|             this.flow = globalFlow; | ||||
|             this.isGlobalFlow = true; | ||||
|         } else { | ||||
|             this.flow = flow; | ||||
|             this.isGlobalFlow = false; | ||||
|         } | ||||
|         this.id = this.flow.id || "global"; | ||||
|         this.activeNodes = {}; | ||||
|         this.subflowInstanceNodes = {}; | ||||
|         this.catchNodeMap = {}; | ||||
|         this.statusNodeMap = {}; | ||||
|         this.catchNodes = []; | ||||
|         this.statusNodes = []; | ||||
|     } | ||||
|  | ||||
|     debug(msg) { | ||||
|         Log.log({ | ||||
|             id: this.id||"global", | ||||
|             level: Log.DEBUG, | ||||
|             type:this.TYPE, | ||||
|             msg:msg | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     log(msg) { | ||||
|         Log.log({ | ||||
|             id: this.id||"global", | ||||
|             level: Log.INFO, | ||||
|             type:this.TYPE, | ||||
|             msg:msg | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     trace(msg) { | ||||
|         Log.log({ | ||||
|             id: this.id||"global", | ||||
|             level: Log.TRACE, | ||||
|             type:this.TYPE, | ||||
|             msg:msg | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     start(diff) { | ||||
|         this.trace("start "+this.TYPE); | ||||
|         var node; | ||||
|         var newNode; | ||||
|         var id; | ||||
|         this.catchNodeMap = {}; | ||||
|         this.statusNodeMap = {}; | ||||
|         this.catchNodes = []; | ||||
|         this.statusNodes = []; | ||||
|  | ||||
|         var configNodes = Object.keys(this.flow.configs); | ||||
|         var configNodeAttempts = {}; | ||||
| @@ -69,7 +104,7 @@ class Flow { | ||||
|                     } | ||||
|                 } | ||||
|                 if (readyToCreate) { | ||||
|                     newNode = createNode(node.type,node); | ||||
|                     newNode = flowUtil.createNode(this,node); | ||||
|                     if (newNode) { | ||||
|                         this.activeNodes[id] = newNode; | ||||
|                     } | ||||
| @@ -91,7 +126,7 @@ class Flow { | ||||
|                 node = this.flow.nodes[id]; | ||||
|                 if (!node.subflow) { | ||||
|                     if (!this.activeNodes[id]) { | ||||
|                         newNode = createNode(node.type,node); | ||||
|                         newNode = flowUtil.createNode(this,node); | ||||
|                         if (newNode) { | ||||
|                             this.activeNodes[id] = newNode; | ||||
|                         } | ||||
| @@ -100,13 +135,24 @@ class Flow { | ||||
|                     if (!this.subflowInstanceNodes[id]) { | ||||
|                         try { | ||||
|                             var subflowDefinition = this.flow.subflows[node.subflow]||this.global.subflows[node.subflow] | ||||
|                             var nodes = createSubflow(subflowDefinition,node,this.flow.subflows,this.global.subflows,this.activeNodes); | ||||
|                             this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id}); | ||||
|                             for (var i=0;i<nodes.length;i++) { | ||||
|                                 if (nodes[i]) { | ||||
|                                     this.activeNodes[nodes[i].id] = nodes[i]; | ||||
|                                 } | ||||
|                             } | ||||
|                             // console.log("NEED TO CREATE A SUBFLOW",id,node.subflow); | ||||
|                             this.subflowInstanceNodes[id] = true; | ||||
|                             var subflow = Subflow.create( | ||||
|                                 this, | ||||
|                                 this.global, | ||||
|                                 subflowDefinition, | ||||
|                                 node | ||||
|                             ); | ||||
|                             subflow.start(); | ||||
|                             this.activeNodes[id] = subflow.node; | ||||
|                             this.subflowInstanceNodes[id] = subflow; | ||||
|  | ||||
|                             // this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id}); | ||||
|                             // for (var i=0;i<nodes.length;i++) { | ||||
|                             //     if (nodes[i]) { | ||||
|                             //         this.activeNodes[nodes[i].id] = nodes[i]; | ||||
|                             //     } | ||||
|                             // } | ||||
|                         } catch(err) { | ||||
|                             console.log(err.stack) | ||||
|                         } | ||||
| @@ -115,31 +161,43 @@ class Flow { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var activeCount = Object.keys(this.activeNodes).length; | ||||
|         if (activeCount > 0) { | ||||
|             this.trace("------------------|--------------|-----------------"); | ||||
|             this.trace(" id               | type         | alias"); | ||||
|             this.trace("------------------|--------------|-----------------"); | ||||
|         } | ||||
|         // Build the map of catch/status nodes. | ||||
|         for (id in this.activeNodes) { | ||||
|             if (this.activeNodes.hasOwnProperty(id)) { | ||||
|                 node = this.activeNodes[id]; | ||||
|                 this.trace(" "+id.padEnd(16)+" | "+node.type.padEnd(12)+" | "+(node._alias||"")); | ||||
|                 if (node.type === "catch") { | ||||
|                     this.catchNodeMap[node.z] = this.catchNodeMap[node.z] || []; | ||||
|                     this.catchNodeMap[node.z].push(node); | ||||
|                     this.catchNodes.push(node); | ||||
|                 } else if (node.type === "status") { | ||||
|                     this.statusNodeMap[node.z] = this.statusNodeMap[node.z] || []; | ||||
|                     this.statusNodeMap[node.z].push(node); | ||||
|                     this.statusNodes.push(node); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (activeCount > 0) { | ||||
|             this.trace("------------------|--------------|-----------------"); | ||||
|         } | ||||
|         // this.dump(); | ||||
|     } | ||||
|  | ||||
|     stop(stopList, removedList) { | ||||
|         return new Promise((resolve,reject) => { | ||||
|             this.trace("stop "+this.TYPE); | ||||
|             var i; | ||||
|             if (stopList) { | ||||
|                 for (i=0;i<stopList.length;i++) { | ||||
|                     if (this.subflowInstanceNodes[stopList[i]]) { | ||||
|                         // The first in the list is the instance node we already | ||||
|                         // know about | ||||
|                         stopList = stopList.concat(this.subflowInstanceNodes[stopList[i]].slice(1)) | ||||
|                     } | ||||
|                 } | ||||
|                 // for (i=0;i<stopList.length;i++) { | ||||
|                 //     if (this.subflowInstanceNodes[stopList[i]]) { | ||||
|                 //         console.log("NEED TO STOP A SUBFLOW",stopList[i]); | ||||
|                 //         // The first in the list is the instance node we already | ||||
|                 //         // know about | ||||
|                 //         // stopList = stopList.concat(this.subflowInstanceNodes[stopList[i]].slice(1)) | ||||
|                 //     } | ||||
|                 // } | ||||
|             } else { | ||||
|                 stopList = Object.keys(this.activeNodes); | ||||
|             } | ||||
| @@ -156,34 +214,20 @@ class Flow { | ||||
|                 if (node) { | ||||
|                     delete this.activeNodes[stopList[i]]; | ||||
|                     if (this.subflowInstanceNodes[stopList[i]]) { | ||||
|                         try { | ||||
|                             var subflow = this.subflowInstanceNodes[stopList[i]]; | ||||
|                             promises.push(this.stopNode(node,false).then(() => { subflow.stop() })); | ||||
|                         } catch(err) { | ||||
|                             node.error(err); | ||||
|                         } | ||||
|                         delete this.subflowInstanceNodes[stopList[i]]; | ||||
|                     } | ||||
|                     try { | ||||
|                         var removed = removedMap[stopList[i]]; | ||||
|                         promises.push( | ||||
|                             when.promise(function(resolve, reject) { | ||||
|                                 var start; | ||||
|                                 var nt = node.type; | ||||
|                                 var nid = node.id; | ||||
|                                 var n = node; | ||||
|                                 when.promise(function(resolve) { | ||||
|                                     Log.trace("Stopping node "+nt+":"+nid+(removed?" removed":"")); | ||||
|                                     start = Date.now(); | ||||
|                                     resolve(n.close(removed)); | ||||
|                                 }).timeout(nodeCloseTimeout).then(function(){ | ||||
|                                     var delta = Date.now() - start; | ||||
|                                     Log.trace("Stopped node "+nt+":"+nid+" ("+delta+"ms)" ); | ||||
|                                     resolve(delta); | ||||
|                                 },function(err) { | ||||
|                                     var delta = Date.now() - start; | ||||
|                                     n.error(Log._("nodes.flows.stopping-error",{message:err})); | ||||
|                                     Log.debug(err.stack); | ||||
|                                     reject(err); | ||||
|                                 }); | ||||
|                             }) | ||||
|                         ); | ||||
|                     } catch(err) { | ||||
|                         node.error(err); | ||||
|                     } else { | ||||
|                         try { | ||||
|                             var removed = removedMap[stopList[i]]; | ||||
|                             promises.push(this.stopNode(node,removed)); | ||||
|                         } catch(err) { | ||||
|                             node.error(err); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -193,13 +237,46 @@ class Flow { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     stopNode(node,removed) { | ||||
|         return when.promise(function(resolve, reject) { | ||||
|             var start; | ||||
|             when.promise(function(resolve) { | ||||
|                 Log.trace("Stopping node "+node.type+":"+node.id+(removed?" removed":"")); | ||||
|                 start = Date.now(); | ||||
|                 resolve(node.close(removed)); | ||||
|             }).timeout(nodeCloseTimeout).then(function(){ | ||||
|                 var delta = Date.now() - start; | ||||
|                 Log.trace("Stopped node "+node.type+":"+node.id+" ("+delta+"ms)" ); | ||||
|                 resolve(delta); | ||||
|             },function(err) { | ||||
|                 var delta = Date.now() - start; | ||||
|                 node.error(Log._("nodes.flows.stopping-error",{message:err})); | ||||
|                 Log.debug(err.stack); | ||||
|                 reject(err); | ||||
|             }); | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     update(_global,_flow) { | ||||
|         this.global = _global; | ||||
|         this.flow = _flow; | ||||
|     } | ||||
|  | ||||
|     getNode(id) { | ||||
|         return this.activeNodes[id]; | ||||
|         // console.log('getNode',id,!!this.activeNodes[id]) | ||||
|         if (!id) { | ||||
|             return undefined; | ||||
|         } | ||||
|         // console.log((new Error().stack).toString().split("\n").slice(1,3).join("\n")) | ||||
|         if ((this.flow.configs && this.flow.configs[id]) || (this.flow.nodes && this.flow.nodes[id])) { | ||||
|             // This is a node owned by this flow, so return whatever we have got | ||||
|             // During a stop/restart, activeNodes could be null for this id | ||||
|             return this.activeNodes[id]; | ||||
|         } else if (this.activeNodes[id]) { | ||||
|             // TEMP: this is a subflow internal node within this flow | ||||
|             return this.activeNodes[id]; | ||||
|         } | ||||
|         return this.parent.getNode(id); | ||||
|     } | ||||
|  | ||||
|     getActiveNodes() { | ||||
| @@ -207,40 +284,40 @@ class Flow { | ||||
|     } | ||||
|  | ||||
|     handleStatus(node,statusMessage) { | ||||
|         var targetStatusNodes = null; | ||||
|         var reportingNode = node; | ||||
|         events.emit("node-status",{ | ||||
|             id: node.id, | ||||
|             status:statusMessage | ||||
|         }); | ||||
|  | ||||
|         var handled = false; | ||||
|         while (reportingNode && !handled) { | ||||
|             targetStatusNodes = this.statusNodeMap[reportingNode.z]; | ||||
|             if (targetStatusNodes) { | ||||
|                 targetStatusNodes.forEach(function(targetStatusNode) { | ||||
|                     if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) { | ||||
|                         return; | ||||
|                     } | ||||
|                     var message = { | ||||
|                         status: { | ||||
|                             text: "", | ||||
|                             source: { | ||||
|                                 id: node.id, | ||||
|                                 type: node.type, | ||||
|                                 name: node.name | ||||
|                             } | ||||
|                         } | ||||
|                     }; | ||||
|                     if (statusMessage.hasOwnProperty("text")) { | ||||
|                         message.status.text = statusMessage.text.toString(); | ||||
|                     } | ||||
|                     targetStatusNode.receive(message); | ||||
|                     handled = true; | ||||
|                 }); | ||||
|         this.statusNodes.forEach(function(targetStatusNode) { | ||||
|             if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) { | ||||
|                 return; | ||||
|             } | ||||
|             if (!handled) { | ||||
|                 reportingNode = this.activeNodes[reportingNode.z]; | ||||
|             var message = { | ||||
|                 status: { | ||||
|                     text: "", | ||||
|                     source: { | ||||
|                         id: node.id, | ||||
|                         type: node.type, | ||||
|                         name: node.name | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|             if (statusMessage.hasOwnProperty("text")) { | ||||
|                 message.status.text = statusMessage.text.toString(); | ||||
|             } | ||||
|             targetStatusNode.receive(message); | ||||
|             handled = true; | ||||
|         }); | ||||
|         if (!handled) { | ||||
|             // // Nothing in this flow handled the status - pass it to the parent | ||||
|             this.parent.handleStatus(node,statusMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     handleError(node,logMessage,msg) { | ||||
|         // console.log("HE",logMessage); | ||||
|         var count = 1; | ||||
|         if (msg && msg.hasOwnProperty("error") && msg.error !== null) { | ||||
|             if (msg.error.hasOwnProperty("source") && msg.error.source !== null) { | ||||
| @@ -253,268 +330,67 @@ class Flow { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         var targetCatchNodes = null; | ||||
|         var throwingNode = node; | ||||
|         var handled = false; | ||||
|         while (throwingNode && !handled) { | ||||
|             targetCatchNodes = this.catchNodeMap[throwingNode.z]; | ||||
|             if (targetCatchNodes) { | ||||
|                 targetCatchNodes.forEach(function(targetCatchNode) { | ||||
|                     if (targetCatchNode.scope && targetCatchNode.scope.indexOf(throwingNode.id) === -1) { | ||||
|                         return; | ||||
|                     } | ||||
|                     var errorMessage; | ||||
|                     if (msg) { | ||||
|                         errorMessage = redUtil.cloneMessage(msg); | ||||
|                     } else { | ||||
|                         errorMessage = {}; | ||||
|                     } | ||||
|                     if (errorMessage.hasOwnProperty("error")) { | ||||
|                         errorMessage._error = errorMessage.error; | ||||
|                     } | ||||
|                     errorMessage.error = { | ||||
|                         message: logMessage.toString(), | ||||
|                         source: { | ||||
|                             id: node.id, | ||||
|                             type: node.type, | ||||
|                             name: node.name, | ||||
|                             count: count | ||||
|                         } | ||||
|                     }; | ||||
|                     if (logMessage.hasOwnProperty('stack')) { | ||||
|                         errorMessage.error.stack = logMessage.stack; | ||||
|                     } | ||||
|                     targetCatchNode.receive(errorMessage); | ||||
|                     handled = true; | ||||
|                 }); | ||||
|         this.catchNodes.forEach(function(targetCatchNode) { | ||||
|             if (targetCatchNode.scope && targetCatchNode.scope.indexOf(node.id) === -1) { | ||||
|                 return; | ||||
|             } | ||||
|             if (!handled) { | ||||
|                 throwingNode = this.activeNodes[throwingNode.z]; | ||||
|             var errorMessage; | ||||
|             if (msg) { | ||||
|                 errorMessage = redUtil.cloneMessage(msg); | ||||
|             } else { | ||||
|                 errorMessage = {}; | ||||
|             } | ||||
|             if (errorMessage.hasOwnProperty("error")) { | ||||
|                 errorMessage._error = errorMessage.error; | ||||
|             } | ||||
|             errorMessage.error = { | ||||
|                 message: logMessage.toString(), | ||||
|                 source: { | ||||
|                     id: node.id, | ||||
|                     type: node.type, | ||||
|                     name: node.name, | ||||
|                     count: count | ||||
|                 } | ||||
|             }; | ||||
|             if (logMessage.hasOwnProperty('stack')) { | ||||
|                 errorMessage.error.stack = logMessage.stack; | ||||
|             } | ||||
|             targetCatchNode.receive(errorMessage); | ||||
|             handled = true; | ||||
|         }); | ||||
|         if (!handled) { | ||||
|             // Nothing in this flow handled the error - pass it to the parent | ||||
|             handled = this.parent.handleError(node,logMessage); | ||||
|         } | ||||
|         return handled; | ||||
|     } | ||||
|  | ||||
|     dump() { | ||||
|         console.log("==================") | ||||
|         console.log(this.TYPE, this.id); | ||||
|         for (var id in this.activeNodes) { | ||||
|             if (this.activeNodes.hasOwnProperty(id)) { | ||||
|                 var node = this.activeNodes[id]; | ||||
|                 console.log(" ",id.padEnd(16),node.type) | ||||
|                 if (node.wires) { | ||||
|                     console.log("   -> ",node.wires) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         console.log("==================") | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Create a new instance of a node | ||||
|  * @param  {string} type   The node type string | ||||
|  * @param  {object} config The node configuration object | ||||
|  * @return {Node}          The instance of the node | ||||
|  */ | ||||
| function createNode(type,config) { | ||||
|     var newNode = null; | ||||
|     try { | ||||
|         var nodeTypeConstructor = typeRegistry.get(type); | ||||
|         if (nodeTypeConstructor) { | ||||
|             var conf = clone(config); | ||||
|             delete conf.credentials; | ||||
|             for (var p in conf) { | ||||
|                 if (conf.hasOwnProperty(p)) { | ||||
|                     flowUtil.mapEnvVarProperties(conf,p); | ||||
|                 } | ||||
|             } | ||||
|             try { | ||||
|                 newNode = new nodeTypeConstructor(conf); | ||||
|             } catch (err) { | ||||
|                 Log.log({ | ||||
|                     level: Log.ERROR, | ||||
|                     id:conf.id, | ||||
|                     type: type, | ||||
|                     msg: err | ||||
|                 }); | ||||
|             } | ||||
|         } else { | ||||
|             Log.error(Log._("nodes.flow.unknown-type", {type:type})); | ||||
|         } | ||||
|     } catch(err) { | ||||
|         Log.error(err); | ||||
|     } | ||||
|     return newNode; | ||||
| } | ||||
|  | ||||
| function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) { | ||||
|     //console.log("CREATE SUBFLOW",sf.id,sfn.id); | ||||
|     var nodes = []; | ||||
|     var node_map = {}; | ||||
|     var newNodes = []; | ||||
|     var node; | ||||
|     var wires; | ||||
|     var i,j,k; | ||||
|  | ||||
|     var createNodeInSubflow = function(def) { | ||||
|         node = clone(def); | ||||
|         var nid = redUtil.generateId(); | ||||
|         node_map[node.id] = node; | ||||
|         node._alias = node.id; | ||||
|         node.id = nid; | ||||
|         node.z = sfn.id; | ||||
|         newNodes.push(node); | ||||
|     } | ||||
|  | ||||
|     // Clone all of the subflow config node definitions and give them new IDs | ||||
|     for (i in sf.configs) { | ||||
|         if (sf.configs.hasOwnProperty(i)) { | ||||
|             createNodeInSubflow(sf.configs[i]); | ||||
|         } | ||||
|     } | ||||
|     // Clone all of the subflow node definitions and give them new IDs | ||||
|     for (i in sf.nodes) { | ||||
|         if (sf.nodes.hasOwnProperty(i)) { | ||||
|             createNodeInSubflow(sf.nodes[i]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Look for any catch/status nodes and update their scope ids | ||||
|     // Update all subflow interior wiring to reflect new node IDs | ||||
|     for (i=0;i<newNodes.length;i++) { | ||||
|         node = newNodes[i]; | ||||
|         if (node.wires) { | ||||
|             var outputs = node.wires; | ||||
|             for (j=0;j<outputs.length;j++) { | ||||
|                 wires = outputs[j]; | ||||
|                 for (k=0;k<wires.length;k++) { | ||||
|                     outputs[j][k] = node_map[outputs[j][k]].id | ||||
|                 } | ||||
|             } | ||||
|             if ((node.type === 'catch' || node.type === 'status') && node.scope) { | ||||
|                 node.scope = node.scope.map(function(id) { | ||||
|                     return node_map[id]?node_map[id].id:"" | ||||
|                 }) | ||||
|             } else { | ||||
|                 for (var prop in node) { | ||||
|                     if (node.hasOwnProperty(prop) && prop !== '_alias') { | ||||
|                         if (node_map[node[prop]]) { | ||||
|                             //console.log("Mapped",node.type,node.id,prop,node_map[node[prop]].id); | ||||
|                             node[prop] = node_map[node[prop]].id; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Create a subflow node to accept inbound messages and route appropriately | ||||
|     var Node = require("../Node"); | ||||
|     var subflowInstance = { | ||||
|         id: sfn.id, | ||||
|         type: sfn.type, | ||||
|         z: sfn.z, | ||||
|         name: sfn.name, | ||||
|         wires: [] | ||||
|     } | ||||
|     if (sf.in) { | ||||
|         subflowInstance.wires = sf.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})}) | ||||
|         subflowInstance._originalWires = clone(subflowInstance.wires); | ||||
|     } | ||||
|     var subflowNode = new Node(subflowInstance); | ||||
|  | ||||
|     subflowNode.on("input", function(msg) { this.send(msg);}); | ||||
|  | ||||
|  | ||||
|     subflowNode._updateWires = subflowNode.updateWires; | ||||
|  | ||||
|     subflowNode.updateWires = function(newWires) { | ||||
|         // Wire the subflow outputs | ||||
|         if (sf.out) { | ||||
|             var node,wires,i,j; | ||||
|             // Restore the original wiring to the internal nodes | ||||
|             subflowInstance.wires = clone(subflowInstance._originalWires); | ||||
|             for (i=0;i<sf.out.length;i++) { | ||||
|                 wires = sf.out[i].wires; | ||||
|                 for (j=0;j<wires.length;j++) { | ||||
|                     if (wires[j].id != sf.id) { | ||||
|                         node = node_map[wires[j].id]; | ||||
|                         if (node._originalWires) { | ||||
|                             node.wires = clone(node._originalWires); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             var modifiedNodes = {}; | ||||
|             var subflowInstanceModified = false; | ||||
|  | ||||
|             for (i=0;i<sf.out.length;i++) { | ||||
|                 wires = sf.out[i].wires; | ||||
|                 for (j=0;j<wires.length;j++) { | ||||
|                     if (wires[j].id === sf.id) { | ||||
|                         subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(newWires[i]); | ||||
|                         subflowInstanceModified = true; | ||||
|                     } else { | ||||
|                         node = node_map[wires[j].id]; | ||||
|                         node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]); | ||||
|                         modifiedNodes[node.id] = node; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Object.keys(modifiedNodes).forEach(function(id) { | ||||
|                 var node = modifiedNodes[id]; | ||||
|                 subflowNode.instanceNodes[id].updateWires(node.wires); | ||||
|             }); | ||||
|             if (subflowInstanceModified) { | ||||
|                 subflowNode._updateWires(subflowInstance.wires); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     nodes.push(subflowNode); | ||||
|  | ||||
|     // Wire the subflow outputs | ||||
|     if (sf.out) { | ||||
|         var modifiedNodes = {}; | ||||
|         for (i=0;i<sf.out.length;i++) { | ||||
|             wires = sf.out[i].wires; | ||||
|             for (j=0;j<wires.length;j++) { | ||||
|                 if (wires[j].id === sf.id) { | ||||
|                     // A subflow input wired straight to a subflow output | ||||
|                     subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(sfn.wires[i]) | ||||
|                     subflowNode._updateWires(subflowInstance.wires); | ||||
|                 } else { | ||||
|                     node = node_map[wires[j].id]; | ||||
|                     modifiedNodes[node.id] = node; | ||||
|                     if (!node._originalWires) { | ||||
|                         node._originalWires = clone(node.wires); | ||||
|                     } | ||||
|                     node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(sfn.wires[i]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Instantiate the nodes | ||||
|     for (i=0;i<newNodes.length;i++) { | ||||
|         node = newNodes[i]; | ||||
|         var type = node.type; | ||||
|  | ||||
|         var m = /^subflow:(.+)$/.exec(type); | ||||
|         if (!m) { | ||||
|             var newNode = createNode(type,node); | ||||
|             if (newNode) { | ||||
|                 activeNodes[node.id] = newNode; | ||||
|                 nodes.push(newNode); | ||||
|             } | ||||
|         } else { | ||||
|             var subflowId = m[1]; | ||||
|             nodes = nodes.concat(createSubflow(subflows[subflowId]||globalSubflows[subflowId],node,subflows,globalSubflows,activeNodes)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     subflowNode.instanceNodes = {}; | ||||
|  | ||||
|     nodes.forEach(function(node) { | ||||
|         subflowNode.instanceNodes[node.id] = node; | ||||
|     }); | ||||
|     return nodes; | ||||
| } | ||||
|  | ||||
|  | ||||
| module.exports = { | ||||
|     init: function(runtime) { | ||||
|         nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000; | ||||
|         Log = runtime.log; | ||||
|         Subflow = require("./Subflow"); | ||||
|         Subflow.init(runtime); | ||||
|     }, | ||||
|     create: function(global,conf) { | ||||
|         return new Flow(global,conf); | ||||
|     } | ||||
|     create: function(parent,global,conf) { | ||||
|         return new Flow(parent,global,conf); | ||||
|     }, | ||||
|     Flow: Flow | ||||
| } | ||||
|   | ||||
							
								
								
									
										246
									
								
								packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,246 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| const clone = require("clone"); | ||||
| const Flow = require('./Flow').Flow; | ||||
|  | ||||
| const redUtil = require("@node-red/util").util; | ||||
| const flowUtil = require("./util"); | ||||
|  | ||||
| var Log; | ||||
|  | ||||
|  | ||||
| class Subflow extends Flow { | ||||
|     constructor(parent,globalFlow,subflowDef,subflowInstance) { | ||||
|         // console.log(subflowDef); | ||||
|         // console.log("CREATE SUBFLOW",subflowDef.id,subflowInstance.id); | ||||
|         // console.log("SubflowInstance\n"+JSON.stringify(subflowInstance," ",2)); | ||||
|         // console.log("SubflowDef\n"+JSON.stringify(subflowDef," ",2)); | ||||
|         var subflows = parent.flow.subflows; | ||||
|         var globalSubflows = parent.global.subflows; | ||||
|  | ||||
|         var node_map = {}; | ||||
|         var node; | ||||
|         var wires; | ||||
|         var i; | ||||
|  | ||||
|         var subflowInternalFlowConfig = { | ||||
|             id: subflowInstance.id, | ||||
|             configs: {}, | ||||
|             nodes: {}, | ||||
|             subflows: {} | ||||
|         } | ||||
|  | ||||
|         if (subflowDef.configs) { | ||||
|             // Clone all of the subflow config node definitions and give them new IDs | ||||
|             for (i in subflowDef.configs) { | ||||
|                 if (subflowDef.configs.hasOwnProperty(i)) { | ||||
|                     node = createNodeInSubflow(subflowInstance.id,subflowDef.configs[i]); | ||||
|                     node_map[node._alias] = node; | ||||
|                     subflowInternalFlowConfig.configs[node.id] = node; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (subflowDef.nodes) { | ||||
|             // Clone all of the subflow node definitions and give them new IDs | ||||
|             for (i in subflowDef.nodes) { | ||||
|                 if (subflowDef.nodes.hasOwnProperty(i)) { | ||||
|                     node = createNodeInSubflow(subflowInstance.id,subflowDef.nodes[i]); | ||||
|                     node_map[node._alias] = node; | ||||
|                     subflowInternalFlowConfig.nodes[node.id] = node; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         subflowInternalFlowConfig.subflows = clone(subflowDef.subflows || {}); | ||||
|  | ||||
|         remapSubflowNodes(subflowInternalFlowConfig.configs,node_map); | ||||
|         remapSubflowNodes(subflowInternalFlowConfig.nodes,node_map); | ||||
|  | ||||
|         // console.log("Instance config\n",JSON.stringify(subflowInternalFlowConfig,"",2)); | ||||
|  | ||||
|         super(parent,globalFlow,subflowInternalFlowConfig); | ||||
|  | ||||
|         this.TYPE = 'subflow'; | ||||
|         this.subflowDef = subflowDef; | ||||
|         this.subflowInstance = subflowInstance; | ||||
|         this.node_map = node_map; | ||||
|     } | ||||
|  | ||||
|     start(diff) { | ||||
|         var self = this; | ||||
|         // Create a subflow node to accept inbound messages and route appropriately | ||||
|         var Node = require("../Node"); | ||||
|         var subflowInstanceConfig = { | ||||
|             id: this.subflowInstance.id, | ||||
|             type: this.subflowInstance.type, | ||||
|             z: this.subflowInstance.z, | ||||
|             name: this.subflowInstance.name, | ||||
|             wires: [], | ||||
|             _flow: this | ||||
|         } | ||||
|         if (this.subflowDef.in) { | ||||
|             subflowInstanceConfig.wires = this.subflowDef.in.map(function(n) { return n.wires.map(function(w) { return self.node_map[w.id].id;})}) | ||||
|             subflowInstanceConfig._originalWires = clone(subflowInstanceConfig.wires); | ||||
|         } | ||||
|  | ||||
|         this.node = new Node(subflowInstanceConfig); | ||||
|         this.node.on("input", function(msg) { this.send(msg);}); | ||||
|  | ||||
|         this.node._updateWires = this.node.updateWires; | ||||
|  | ||||
|         this.node.updateWires = function(newWires) { | ||||
|             // Wire the subflow outputs | ||||
|             if (self.subflowDef.out) { | ||||
|                 var node,wires,i,j; | ||||
|                 // Restore the original wiring to the internal nodes | ||||
|                 subflowInstanceConfig.wires = clone(subflowInstanceConfig._originalWires); | ||||
|                 for (i=0;i<self.subflowDef.out.length;i++) { | ||||
|                     wires = self.subflowDef.out[i].wires; | ||||
|                     for (j=0;j<wires.length;j++) { | ||||
|                         if (wires[j].id != self.subflowDef.id) { | ||||
|                             node = self.node_map[wires[j].id]; | ||||
|                             if (node._originalWires) { | ||||
|                                 node.wires = clone(node._originalWires); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 var modifiedNodes = {}; | ||||
|                 var subflowInstanceModified = false; | ||||
|                 for (i=0;i<self.subflowDef.out.length;i++) { | ||||
|                     wires = self.subflowDef.out[i].wires; | ||||
|                     for (j=0;j<wires.length;j++) { | ||||
|                         if (wires[j].id === self.subflowDef.id) { | ||||
|                             subflowInstanceConfig.wires[wires[j].port] = subflowInstanceConfig.wires[wires[j].port].concat(newWires[i]); | ||||
|                             subflowInstanceModified = true; | ||||
|                         } else { | ||||
|                             node = self.node_map[wires[j].id]; | ||||
|                             node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]); | ||||
|                             modifiedNodes[node.id] = node; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 Object.keys(modifiedNodes).forEach(function(id) { | ||||
|                     var node = modifiedNodes[id]; | ||||
|                     self.activeNodes[id].updateWires(node.wires); | ||||
|                 }); | ||||
|  | ||||
|                 if (subflowInstanceModified) { | ||||
|                     self.node._updateWires(subflowInstanceConfig.wires); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Wire the subflow outputs | ||||
|         if (this.subflowDef.out) { | ||||
|             var modifiedNodes = {}; | ||||
|             for (var i=0;i<this.subflowDef.out.length;i++) { | ||||
|                 // i: the output index | ||||
|                 // This is what this Output is wired to | ||||
|                 wires = this.subflowDef.out[i].wires; | ||||
|                 for (var j=0;j<wires.length;j++) { | ||||
|                     if (wires[j].id === this.subflowDef.id) { | ||||
|                         // A subflow input wired straight to a subflow output | ||||
|                         subflowInstanceConfig.wires[wires[j].port] = subflowInstanceConfig.wires[wires[j].port].concat(this.subflowInstance.wires[i]) | ||||
|                         this.node._updateWires(subflowInstanceConfig.wires); | ||||
|                     } else { | ||||
|                         var node = self.node_map[wires[j].id]; | ||||
|                         modifiedNodes[node.id] = node; | ||||
|                         if (!node._originalWires) { | ||||
|                             node._originalWires = clone(node.wires); | ||||
|                         } | ||||
|                         node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         super.start(diff); | ||||
|     } | ||||
|  | ||||
|     stop(stopList,removedList) { | ||||
|         return super.stop(stopList,removedList); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| function createNodeInSubflow(subflowInstanceId, def) { | ||||
|     let node = clone(def); | ||||
|     let nid = redUtil.generateId(); | ||||
|     // console.log("Create Node In subflow",node.id, "--->",nid, "(",node.type,")") | ||||
|     // node_map[node.id] = node; | ||||
|     node._alias = node.id; | ||||
|     node.id = nid; | ||||
|     node.z = subflowInstanceId; | ||||
|     return node; | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Given an object of {id:nodes} and a map of {old-id:node}, modifiy all | ||||
|  * properties in the nodes object to reference the new node ids. | ||||
|  * This handles: | ||||
|  *  - node.wires, | ||||
|  *  - node.scope of Catch and Status nodes, | ||||
|  *  - node.XYZ for any property where XYZ is recognised as an old property | ||||
|  * @param  {[type]} nodes   [description] | ||||
|  * @param  {[type]} nodeMap [description] | ||||
|  * @return {[type]}         [description] | ||||
|  */ | ||||
| function remapSubflowNodes(nodes,nodeMap) { | ||||
|     for (var id in nodes) { | ||||
|         if (nodes.hasOwnProperty(id)) { | ||||
|             var node = nodes[id]; | ||||
|             if (node.wires) { | ||||
|                 var outputs = node.wires; | ||||
|                 for (j=0;j<outputs.length;j++) { | ||||
|                     wires = outputs[j]; | ||||
|                     for (k=0;k<wires.length;k++) { | ||||
|                         outputs[j][k] = nodeMap[outputs[j][k]].id | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if ((node.type === 'catch' || node.type === 'status') && node.scope) { | ||||
|                 node.scope = node.scope.map(function(id) { | ||||
|                     return nodeMap[id]?nodeMap[id].id:"" | ||||
|                 }) | ||||
|             } else { | ||||
|                 for (var prop in node) { | ||||
|                     if (node.hasOwnProperty(prop) && prop !== '_alias') { | ||||
|                         if (nodeMap[node[prop]]) { | ||||
|                             //console.log("Mapped",node.type,node.id,prop,nodeMap[node[prop]].id); | ||||
|                             node[prop] = nodeMap[node[prop]].id; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| function createSubflow(parent,globalFlow,subflowDef,subflowInstance) { | ||||
|     return new Subflow(parent,globalFlow,subflowDef,subflowInstance) | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     init: function(runtime) { | ||||
|         Log = runtime.log; | ||||
|     }, | ||||
|     create: createSubflow | ||||
| } | ||||
| @@ -42,7 +42,6 @@ var started = false; | ||||
| var credentialsPendingReset = false; | ||||
|  | ||||
| var activeNodesToFlow = {}; | ||||
| var subflowInstanceNodeMap = {}; | ||||
|  | ||||
| var typeEventRegistered = false; | ||||
|  | ||||
| @@ -233,59 +232,6 @@ function getFlows() { | ||||
|     return activeConfig; | ||||
| } | ||||
|  | ||||
| function delegateError(node,logMessage,msg) { | ||||
|     var handled = false; | ||||
|     if (activeFlows[node.z]) { | ||||
|         handled = activeFlows[node.z].handleError(node,logMessage,msg); | ||||
|     } else if (activeNodesToFlow[node.z] && activeFlows[activeNodesToFlow[node.z]]) { | ||||
|         handled = activeFlows[activeNodesToFlow[node.z]].handleError(node,logMessage,msg); | ||||
|     } else if (activeFlowConfig.subflows[node.z] && subflowInstanceNodeMap[node.id]) { | ||||
|         subflowInstanceNodeMap[node.id].forEach(function(n) { | ||||
|             handled = handled || delegateError(getNode(n),logMessage,msg); | ||||
|         }); | ||||
|     } | ||||
|     return handled; | ||||
| } | ||||
| function handleError(node,logMessage,msg) { | ||||
|     var handled = false; | ||||
|     if (node.z) { | ||||
|         handled = delegateError(node,logMessage,msg); | ||||
|     } else { | ||||
|         if (activeFlowConfig.configs[node.id]) { | ||||
|             activeFlowConfig.configs[node.id]._users.forEach(function(id) { | ||||
|                 var userNode = activeFlowConfig.allNodes[id]; | ||||
|                 handled = handled || delegateError(userNode,logMessage,msg); | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|     return handled; | ||||
| } | ||||
|  | ||||
| function delegateStatus(node,statusMessage) { | ||||
|     if (activeFlows[node.z]) { | ||||
|         activeFlows[node.z].handleStatus(node,statusMessage); | ||||
|     } else if (activeNodesToFlow[node.z] && activeFlows[activeNodesToFlow[node.z]]) { | ||||
|         activeFlows[activeNodesToFlow[node.z]].handleStatus(node,statusMessage); | ||||
|     } | ||||
| } | ||||
| function handleStatus(node,statusMessage) { | ||||
|     events.emit("node-status",{ | ||||
|         id: node.id, | ||||
|         status:statusMessage | ||||
|     }); | ||||
|     if (node.z) { | ||||
|         delegateStatus(node,statusMessage); | ||||
|     } else { | ||||
|         if (activeFlowConfig.configs[node.id]) { | ||||
|             activeFlowConfig.configs[node.id]._users.forEach(function(id) { | ||||
|                 var userNode = activeFlowConfig.allNodes[id]; | ||||
|                 delegateStatus(userNode,statusMessage); | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| function start(type,diff,muteLog) { | ||||
|     type = type||"full"; | ||||
|     started = true; | ||||
| @@ -338,7 +284,7 @@ function start(type,diff,muteLog) { | ||||
|         // Check the 'global' flow is running | ||||
|         if (!activeFlows['global']) { | ||||
|             log.debug("red/nodes/flows.start : starting flow : global"); | ||||
|             activeFlows['global'] = Flow.create(activeFlowConfig); | ||||
|             activeFlows['global'] = Flow.create(flowAPI,activeFlowConfig); | ||||
|         } | ||||
|  | ||||
|         // Check each flow in the active configuration | ||||
| @@ -346,7 +292,7 @@ function start(type,diff,muteLog) { | ||||
|             if (activeFlowConfig.flows.hasOwnProperty(id)) { | ||||
|                 if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) { | ||||
|                     // This flow is not disabled, nor is it currently active, so create it | ||||
|                     activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                     activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                     log.debug("red/nodes/flows.start : starting flow : "+id); | ||||
|                 } else { | ||||
|                     log.debug("red/nodes/flows.start : not starting disabled flow : "+id); | ||||
| @@ -366,7 +312,7 @@ function start(type,diff,muteLog) { | ||||
|                         activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                     } else { | ||||
|                         // This flow didn't previously exist, so create it | ||||
|                         activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                         activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                         log.debug("red/nodes/flows.start : starting flow : "+id); | ||||
|                     } | ||||
|                 } else { | ||||
| @@ -375,22 +321,20 @@ function start(type,diff,muteLog) { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Having created or updated all flows, now start them. | ||||
|     for (id in activeFlows) { | ||||
|         if (activeFlows.hasOwnProperty(id)) { | ||||
|             activeFlows[id].start(diff); | ||||
|  | ||||
|             // Create a map of node id to flow id and also a subflowInstance lookup map | ||||
|             var activeNodes = activeFlows[id].getActiveNodes(); | ||||
|             Object.keys(activeNodes).forEach(function(nid) { | ||||
|                 activeNodesToFlow[nid] = id; | ||||
|                 if (activeNodes[nid]._alias) { | ||||
|                     subflowInstanceNodeMap[activeNodes[nid]._alias] = subflowInstanceNodeMap[activeNodes[nid]._alias] || []; | ||||
|                     subflowInstanceNodeMap[activeNodes[nid]._alias].push(nid); | ||||
|                 } | ||||
|             }); | ||||
|             try { | ||||
|                 activeFlows[id].start(diff); | ||||
|  | ||||
|                 // Create a map of node id to flow id and also a subflowInstance lookup map | ||||
|                 var activeNodes = activeFlows[id].getActiveNodes(); | ||||
|                 Object.keys(activeNodes).forEach(function(nid) { | ||||
|                     activeNodesToFlow[nid] = id; | ||||
|                 }); | ||||
|             } catch(err) { | ||||
|                 console.log(err.stack); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     events.emit("nodes-started"); | ||||
| @@ -465,11 +409,6 @@ function stop(type,diff,muteLog) { | ||||
|                     delete activeNodesToFlow[id]; | ||||
|                 }); | ||||
|             } | ||||
|             // Ideally we'd prune just what got stopped - but mapping stopList | ||||
|             // id to the list of subflow instance nodes is something only Flow | ||||
|             // can do... so cheat by wiping the map knowing it'll be rebuilt | ||||
|             // in start() | ||||
|             subflowInstanceNodeMap = {}; | ||||
|             if (!muteLog) { | ||||
|                 if (type !== "full") { | ||||
|                     log.info(log._("nodes.flows.stopped-modified-"+type)); | ||||
| @@ -735,6 +674,13 @@ function removeFlow(id) { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| const flowAPI = { | ||||
|     getNode: getNode, | ||||
|     handleError: () => false, | ||||
|     handleStatus: () => false | ||||
| } | ||||
|  | ||||
|  | ||||
| module.exports = { | ||||
|     init: init, | ||||
|  | ||||
| @@ -773,8 +719,8 @@ module.exports = { | ||||
|  | ||||
|     get started() { return started }, | ||||
|  | ||||
|     handleError: handleError, | ||||
|     handleStatus: handleStatus, | ||||
|     // handleError: handleError, | ||||
|     // handleStatus: handleStatus, | ||||
|  | ||||
|     checkTypeInUse: checkTypeInUse, | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
|  **/ | ||||
| var clone = require("clone"); | ||||
| var redUtil = require("@node-red/util").util; | ||||
| var Log = require("@node-red/util").log; | ||||
| var subflowInstanceRE = /^subflow:(.+)$/; | ||||
| var typeRegistry = require("@node-red/registry"); | ||||
|  | ||||
| @@ -427,9 +428,10 @@ module.exports = { | ||||
|         // console.log(diff); | ||||
|         // for (id in newConfig.allNodes) { | ||||
|         //     console.log( | ||||
|         //         (added[id]?"+":(changed[id]?"!":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"~":" "), | ||||
|         //         id, | ||||
|         //         newConfig.allNodes[id].type, | ||||
|         //         (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "), | ||||
|         //         newConfig.allNodes[id].type.padEnd(10), | ||||
|         //         id.padEnd(16), | ||||
|         //         (newConfig.allNodes[id].z||"").padEnd(16), | ||||
|         //         newConfig.allNodes[id].name||newConfig.allNodes[id].label||"" | ||||
|         //     ); | ||||
|         // } | ||||
| @@ -443,5 +445,44 @@ module.exports = { | ||||
|         // } | ||||
|  | ||||
|         return diff; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Create a new instance of a node | ||||
|      * @param  {Flow} flow     The containing flow | ||||
|      * @param  {object} config The node configuration object | ||||
|      * @return {Node}          The instance of the node | ||||
|      */ | ||||
|     createNode: function(flow,config) { | ||||
|         var newNode = null; | ||||
|         var type = config.type; | ||||
|         try { | ||||
|             var nodeTypeConstructor = typeRegistry.get(type); | ||||
|             if (nodeTypeConstructor) { | ||||
|                 var conf = clone(config); | ||||
|                 delete conf.credentials; | ||||
|                 for (var p in conf) { | ||||
|                     if (conf.hasOwnProperty(p)) { | ||||
|                         mapEnvVarProperties(conf,p); | ||||
|                     } | ||||
|                 } | ||||
|                 try { | ||||
|                     conf._flow = flow; | ||||
|                     newNode = new nodeTypeConstructor(conf); | ||||
|                 } catch (err) { | ||||
|                     Log.log({ | ||||
|                         level: Log.ERROR, | ||||
|                         id:conf.id, | ||||
|                         type: type, | ||||
|                         msg: err | ||||
|                     }); | ||||
|                 } | ||||
|             } else { | ||||
|                 Log.error(Log._("nodes.flow.unknown-type", {type:type})); | ||||
|             } | ||||
|         } catch(err) { | ||||
|             Log.error(err); | ||||
|         } | ||||
|         return newNode; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -167,18 +167,17 @@ describe('Node', function() { | ||||
|     describe('#send', function() { | ||||
|  | ||||
|         it('emits a single message', function(done) { | ||||
|             var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); | ||||
|             var n2 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':n1,'n2':n2}[id]; | ||||
|             }); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':n1,'n2':n2}[id]}, | ||||
|             }; | ||||
|             var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]}); | ||||
|             var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); | ||||
|             var message = {payload:"hello world"}; | ||||
|  | ||||
|             n2.on('input',function(msg) { | ||||
|                 // msg equals message, and is not a new copy | ||||
|                 should.deepEqual(msg,message); | ||||
|                 should.strictEqual(msg,message); | ||||
|                 flowGet.restore(); | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
| @@ -186,11 +185,11 @@ describe('Node', function() { | ||||
|         }); | ||||
|  | ||||
|         it('emits multiple messages on a single output', function(done) { | ||||
|             var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); | ||||
|             var n2 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':n1,'n2':n2}[id]; | ||||
|             }); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':n1,'n2':n2}[id]}, | ||||
|             }; | ||||
|             var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]}); | ||||
|             var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); | ||||
|  | ||||
|             var messages = [ | ||||
|                 {payload:"hello world"}, | ||||
| @@ -209,7 +208,6 @@ describe('Node', function() { | ||||
|                     // second msg sent, clone | ||||
|                     msg.payload.should.equal(messages[rcvdCount].payload); | ||||
|                     should.notStrictEqual(msg,messages[rcvdCount]); | ||||
|                     flowGet.restore(); | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
| @@ -217,14 +215,14 @@ describe('Node', function() { | ||||
|         }); | ||||
|  | ||||
|         it('emits messages to multiple outputs', function(done) { | ||||
|             var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]}); | ||||
|             var n2 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var n3 = new RedNode({id:'n3',type:'abc'}); | ||||
|             var n4 = new RedNode({id:'n4',type:'abc'}); | ||||
|             var n5 = new RedNode({id:'n5',type:'abc'}); | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id]; | ||||
|             }); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id]}, | ||||
|             }; | ||||
|             var n1 = new RedNode({_flow:flow, id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]}); | ||||
|             var n2 = new RedNode({_flow:flow, id:'n2',type:'abc'}); | ||||
|             var n3 = new RedNode({_flow:flow, id:'n3',type:'abc'}); | ||||
|             var n4 = new RedNode({_flow:flow, id:'n4',type:'abc'}); | ||||
|             var n5 = new RedNode({_flow:flow, id:'n5',type:'abc'}); | ||||
|  | ||||
|             var messages = [ | ||||
|                 {payload:"hello world"}, | ||||
| @@ -241,7 +239,6 @@ describe('Node', function() { | ||||
|                 should.strictEqual(msg,messages[0]); | ||||
|                 rcvdCount += 1; | ||||
|                 if (rcvdCount == 3) { | ||||
|                     flowGet.restore(); | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
| @@ -257,7 +254,6 @@ describe('Node', function() { | ||||
|                 should.notStrictEqual(msg,messages[2]); | ||||
|                 rcvdCount += 1; | ||||
|                 if (rcvdCount == 3) { | ||||
|                     flowGet.restore(); | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
| @@ -269,7 +265,6 @@ describe('Node', function() { | ||||
|                 should.notStrictEqual(msg,messages[2]); | ||||
|                 rcvdCount += 1; | ||||
|                 if (rcvdCount == 3) { | ||||
|                     flowGet.restore(); | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
| @@ -278,18 +273,17 @@ describe('Node', function() { | ||||
|         }); | ||||
|  | ||||
|         it('emits no messages', function(done) { | ||||
|             var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); | ||||
|             var n2 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':n1,'n2':n2}[id]; | ||||
|             }); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':n1,'n2':n2}[id]}, | ||||
|             }; | ||||
|             var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]}); | ||||
|             var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); | ||||
|  | ||||
|             n2.on('input',function(msg) { | ||||
|                 should.fail(null,null,"unexpected message"); | ||||
|             }); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 flowGet.restore(); | ||||
|                 done(); | ||||
|             }, 200); | ||||
|  | ||||
| @@ -297,11 +291,11 @@ describe('Node', function() { | ||||
|         }); | ||||
|  | ||||
|         it('emits messages ignoring non-existent nodes', function(done) { | ||||
|             var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]}); | ||||
|             var n2 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':n1,'n2':n2}[id]; | ||||
|             }); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':n1,'n2':n2}[id]}, | ||||
|             }; | ||||
|             var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n9'],['n2']]}); | ||||
|             var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); | ||||
|  | ||||
|             var messages = [ | ||||
|                 {payload:"hello world"}, | ||||
| @@ -312,7 +306,6 @@ describe('Node', function() { | ||||
|             n2.on('input',function(msg) { | ||||
|                 should.deepEqual(msg,messages[1]); | ||||
|                 should.strictEqual(msg,messages[1]); | ||||
|                 flowGet.restore(); | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
| @@ -320,12 +313,12 @@ describe('Node', function() { | ||||
|         }); | ||||
|  | ||||
|         it('emits messages without cloning req or res', function(done) { | ||||
|             var n1 = new RedNode({id:'n1',type:'abc',wires:[[['n2'],['n3']]]}); | ||||
|             var n2 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var n3 = new RedNode({id:'n3',type:'abc'}); | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':n1,'n2':n2,'n3':n3}[id]; | ||||
|             }); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':n1,'n2':n2,'n3':n3}[id]}, | ||||
|             }; | ||||
|             var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[[['n2'],['n3']]]}); | ||||
|             var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); | ||||
|             var n3 = new RedNode({_flow:flow,id:'n3',type:'abc'}); | ||||
|  | ||||
|             var req = {}; | ||||
|             var res = {}; | ||||
| @@ -342,7 +335,6 @@ describe('Node', function() { | ||||
|                 msg.res.should.be.exactly(message.res); | ||||
|                 rcvdCount += 1; | ||||
|                 if (rcvdCount == 2) { | ||||
|                     flowGet.restore(); | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
| @@ -356,7 +348,6 @@ describe('Node', function() { | ||||
|                 msg.res.should.be.exactly(message.res); | ||||
|                 rcvdCount += 1; | ||||
|                 if (rcvdCount == 2) { | ||||
|                     flowGet.restore(); | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
| @@ -365,26 +356,25 @@ describe('Node', function() { | ||||
|         }); | ||||
|  | ||||
|          it("logs the uuid for all messages sent", function(done) { | ||||
|             var flowGet = sinon.stub(flows,"get",function(id) { | ||||
|                 return {'n1':sender,'n2':receiver1,'n3':receiver2}[id]; | ||||
|             }); | ||||
|             var logHandler = { | ||||
|                 messagesSent: 0, | ||||
|                 emit: function(event, msg) { | ||||
|                     if (msg.event == "node.abc.send" && msg.level == Log.METRIC) { | ||||
|                         this.messagesSent++; | ||||
|                         (typeof msg.msgid).should.not.be.equal("undefined"); | ||||
|                         flowGet.restore(); | ||||
|                         done(); | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             Log.addHandler(logHandler); | ||||
|             var flow = { | ||||
|                 getNode: (id) => { return {'n1':sender,'n2':receiver1,'n3':receiver2}[id]}, | ||||
|             }; | ||||
|  | ||||
|             var sender = new RedNode({id:'n1',type:'abc', wires:[['n2', 'n3']]}); | ||||
|             var receiver1 = new RedNode({id:'n2',type:'abc'}); | ||||
|             var receiver2 = new RedNode({id:'n3',type:'abc'}); | ||||
|             var sender = new RedNode({_flow:flow,id:'n1',type:'abc', wires:[['n2', 'n3']]}); | ||||
|             var receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'}); | ||||
|             var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'}); | ||||
|             sender.send({"some": "message"}); | ||||
|         }) | ||||
|     }); | ||||
| @@ -434,50 +424,35 @@ describe('Node', function() { | ||||
|  | ||||
|     describe('#error', function() { | ||||
|         it('handles a null error message', function(done) { | ||||
|             var n = new RedNode({id:'123',type:'abc',z:'789'}); | ||||
|             var loginfo = {}; | ||||
|             sinon.stub(Log, 'log', function(msg) { | ||||
|                 loginfo = msg; | ||||
|             }); | ||||
|             sinon.stub(flows,"handleError", function(node,message,msg) { | ||||
|             }); | ||||
|  | ||||
|             var flow = { | ||||
|                 handleError: sinon.stub() | ||||
|             } | ||||
|             var n = new RedNode({_flow:flow, id:'123',type:'abc',z:'789'}); | ||||
|             var message = {a:1}; | ||||
|  | ||||
|             n.error(null,message); | ||||
|             should.deepEqual({level:Log.ERROR, id:n.id, type:n.type, msg:"",z:'789'}, loginfo); | ||||
|  | ||||
|             flows.handleError.called.should.be.true(); | ||||
|             flows.handleError.args[0][0].should.eql(n); | ||||
|             flows.handleError.args[0][1].should.eql(""); | ||||
|             flows.handleError.args[0][2].should.eql(message); | ||||
|             flow.handleError.called.should.be.true(); | ||||
|             flow.handleError.args[0][0].should.eql(n); | ||||
|             flow.handleError.args[0][1].should.eql(""); | ||||
|             flow.handleError.args[0][2].should.eql(message); | ||||
|  | ||||
|             Log.log.restore(); | ||||
|             flows.handleError.restore(); | ||||
|             done(); | ||||
|         }); | ||||
|  | ||||
|         it('produces an error message', function(done) { | ||||
|             var n = new RedNode({id:'123',type:'abc',z:'789'}); | ||||
|             var loginfo = {}; | ||||
|             sinon.stub(Log, 'log', function(msg) { | ||||
|                 loginfo = msg; | ||||
|             }); | ||||
|             sinon.stub(flows,"handleError", function(node,message,msg) { | ||||
|             }); | ||||
|  | ||||
|             var flow = { | ||||
|                 handleError: sinon.stub() | ||||
|             } | ||||
|             var n = new RedNode({_flow:flow, id:'123',type:'abc',z:'789'}); | ||||
|             var message = {a:2}; | ||||
|  | ||||
|             n.error("This is an error",message); | ||||
|             should.deepEqual({level:Log.ERROR, id:n.id, type:n.type, msg:"This is an error",z:'789'}, loginfo); | ||||
|  | ||||
|             flows.handleError.called.should.be.true(); | ||||
|             flows.handleError.args[0][0].should.eql(n); | ||||
|             flows.handleError.args[0][1].should.eql("This is an error"); | ||||
|             flows.handleError.args[0][2].should.eql(message); | ||||
|             flow.handleError.called.should.be.true(); | ||||
|             flow.handleError.args[0][0].should.eql(n); | ||||
|             flow.handleError.args[0][1].should.eql("This is an error"); | ||||
|             flow.handleError.args[0][2].should.eql(message); | ||||
|  | ||||
|             Log.log.restore(); | ||||
|             flows.handleError.restore(); | ||||
|             done(); | ||||
|         }); | ||||
|  | ||||
| @@ -528,8 +503,10 @@ describe('Node', function() { | ||||
|  | ||||
|     describe('#status', function() { | ||||
|         it('publishes status', function(done) { | ||||
|             sinon.stub(flows,"handleStatus", function(node,message,msg) {}); | ||||
|             var n = new RedNode({id:'123',type:'abc'}); | ||||
|             var flow = { | ||||
|                 handleStatus: sinon.stub() | ||||
|             } | ||||
|             var n = new RedNode({_flow:flow,id:'123',type:'abc'}); | ||||
|             var status = {fill:"green",shape:"dot",text:"connected"}; | ||||
|             var topic; | ||||
|             var message; | ||||
| @@ -537,10 +514,9 @@ describe('Node', function() { | ||||
|  | ||||
|             n.status(status); | ||||
|  | ||||
|             flows.handleStatus.called.should.be.true(); | ||||
|             flows.handleStatus.args[0][0].should.eql(n); | ||||
|             flows.handleStatus.args[0][1].should.eql(status); | ||||
|             flows.handleStatus.restore(); | ||||
|             flow.handleStatus.called.should.be.true(); | ||||
|             flow.handleStatus.args[0][0].should.eql(n); | ||||
|             flow.handleStatus.args[0][1].should.eql(status); | ||||
|             done(); | ||||
|         }); | ||||
|     }); | ||||
|   | ||||
| @@ -31,7 +31,6 @@ var typeRegistry = NR_TEST_UTILS.require("@node-red/registry"); | ||||
|  | ||||
| describe('Flow', function() { | ||||
|     var getType; | ||||
|     var getNode; | ||||
|  | ||||
|     var stoppedNodes = {}; | ||||
|     var currentNodes = {}; | ||||
| @@ -44,11 +43,11 @@ describe('Flow', function() { | ||||
|         rewiredNodes = {}; | ||||
|         createCount = 0; | ||||
|         Flow.init({settings:{},log:{ | ||||
|             log: sinon.stub(), | ||||
|             debug: sinon.stub(), | ||||
|             trace: sinon.stub(), | ||||
|             warn: sinon.stub(), | ||||
|             info: sinon.stub(), | ||||
|             log: sinon.stub(), // function() { console.log("l",[...arguments].map(a => JSON.stringify(a)).join(" ")) },// | ||||
|             debug: sinon.stub(), // function() { console.log("d",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|             trace: sinon.stub(), // function() { console.log("t",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|             warn: sinon.stub(), // function() { console.log("w",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|             info: sinon.stub(), // function() { console.log("i",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|             metric: sinon.stub(), | ||||
|             _: function() { return "abc"} | ||||
|         }}); | ||||
| @@ -64,6 +63,7 @@ describe('Flow', function() { | ||||
|         this.stopped = false; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             // console.log(this.id,msg.payload); | ||||
|             node.handled++; | ||||
|             node.send(msg); | ||||
|         }); | ||||
| @@ -81,6 +81,34 @@ describe('Flow', function() { | ||||
|     } | ||||
|     util.inherits(TestNode,Node); | ||||
|  | ||||
|     var TestErrorNode = function(n) { | ||||
|         Node.call(this,n); | ||||
|         this._index = createCount++; | ||||
|         this.scope = n.scope; | ||||
|         this.name = n.name; | ||||
|         var node = this; | ||||
|         this.foo = n.foo; | ||||
|         this.handled = 0; | ||||
|         this.stopped = false; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             node.handled++; | ||||
|             node.error("test error",msg); | ||||
|         }); | ||||
|         this.on('close',function() { | ||||
|             node.stopped = true; | ||||
|             stoppedNodes[node.id] = node; | ||||
|             delete currentNodes[node.id]; | ||||
|         }); | ||||
|         this.__updateWires = this.updateWires; | ||||
|         this.updateWires = function(newWires) { | ||||
|             rewiredNodes[node.id] = node; | ||||
|             node.newWires = newWires; | ||||
|             node.__updateWires(newWires); | ||||
|         }; | ||||
|     } | ||||
|     util.inherits(TestErrorNode,Node); | ||||
|  | ||||
|     var TestAsyncNode = function(n) { | ||||
|         Node.call(this,n); | ||||
|         var node = this; | ||||
| @@ -111,26 +139,21 @@ describe('Flow', function() { | ||||
|         getType = sinon.stub(typeRegistry,"get",function(type) { | ||||
|             if (type=="test") { | ||||
|                 return TestNode; | ||||
|             } else if (type=="testError"){ | ||||
|                 return TestErrorNode; | ||||
|             } else { | ||||
|                 return TestAsyncNode; | ||||
|             } | ||||
|         }); | ||||
|         getNode = sinon.stub(flows,"get",function(id) { | ||||
|             return currentNodes[id]; | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
|     after(function() { | ||||
|         getType.restore(); | ||||
|         getNode.restore(); | ||||
|     }); | ||||
|  | ||||
|  | ||||
|  | ||||
|     describe('#constructor',function() { | ||||
|         it('called with an empty flow',function() { | ||||
|             var config = flowUtils.parseConfig([]); | ||||
|             var flow = Flow.create(config); | ||||
|             var flow = Flow.create({},config); | ||||
|  | ||||
|             var nodeCount = 0; | ||||
|             Object.keys(flow.getActiveNodes()).length.should.equal(0); | ||||
| @@ -145,7 +168,7 @@ describe('Flow', function() { | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",z:"t1",type:"test",foo:"a"} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             flow.start(); | ||||
|  | ||||
|             Object.keys(flow.getActiveNodes()).should.have.length(4); | ||||
| @@ -188,7 +211,6 @@ describe('Flow', function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         it("instantiates config nodes in the right order",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
| @@ -198,7 +220,7 @@ describe('Flow', function() { | ||||
|                 {id:"4",z:"t1",type:"test",foo:"5"}, // This node depends on #5 | ||||
|                 {id:"5",z:"t1",type:"test"} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             flow.start(); | ||||
|  | ||||
|             Object.keys(flow.getActiveNodes()).should.have.length(5); | ||||
| @@ -238,130 +260,12 @@ describe('Flow', function() { | ||||
|                 {id:"node1",z:"t1",type:"test",foo:"node2"}, // This node depends on #5 | ||||
|                 {id:"node2",z:"t1",type:"test",foo:"node1"} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             /*jshint immed: false */ | ||||
|             (function(){ | ||||
|                 flow.start(); | ||||
|             }).should.throw("Circular config node dependency detected: node1"); | ||||
|         }); | ||||
|         it("instantiates a subflow and stops it",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]},{"wires":[{"id":"sf1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"test","z":"sf1",foo:"sf1-cn",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sf1-cn",type:"test","z":"sf1"} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(7); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[5]; | ||||
|             var sfInstanceId2 = Object.keys(activeNodes)[6]; | ||||
|             var sfConfigId = Object.keys(activeNodes)[4]; | ||||
|  | ||||
|             flow.getNode('1').should.have.a.property('id','1'); | ||||
|             flow.getNode('2').should.have.a.property('id','2'); | ||||
|             flow.getNode('3').should.have.a.property('id','3'); | ||||
|             flow.getNode('4').should.have.a.property('id','4'); | ||||
|             flow.getNode(sfInstanceId).should.have.a.property('id',sfInstanceId); | ||||
|             flow.getNode(sfInstanceId2).should.have.a.property('id',sfInstanceId2); | ||||
|             flow.getNode(sfConfigId).should.have.a.property('id',sfConfigId); | ||||
|  | ||||
|             flow.getNode(sfInstanceId2).should.have.a.property('foo',sfConfigId); | ||||
|  | ||||
|             currentNodes.should.have.a.property("1"); | ||||
|             currentNodes.should.not.have.a.property("2"); | ||||
|             currentNodes.should.have.a.property("3"); | ||||
|             currentNodes.should.have.a.property("4"); | ||||
|             currentNodes.should.have.a.property(sfInstanceId); | ||||
|             currentNodes.should.have.a.property(sfInstanceId2); | ||||
|             currentNodes.should.have.a.property(sfConfigId); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|             currentNodes[sfInstanceId].should.have.a.property("handled",0); | ||||
|             currentNodes[sfInstanceId2].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|             currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 currentNodes.should.not.have.a.property("1"); | ||||
|                 currentNodes.should.not.have.a.property("3"); | ||||
|                 currentNodes.should.not.have.a.property("4"); | ||||
|                 currentNodes.should.not.have.a.property(sfInstanceId); | ||||
|                 currentNodes.should.not.have.a.property(sfInstanceId2); | ||||
|                 currentNodes.should.not.have.a.property(sfConfigId); | ||||
|                 stoppedNodes.should.have.a.property("1"); | ||||
|                 stoppedNodes.should.have.a.property("3"); | ||||
|                 stoppedNodes.should.have.a.property("4"); | ||||
|                 stoppedNodes.should.have.a.property(sfInstanceId); | ||||
|                 stoppedNodes.should.have.a.property(sfInstanceId2); | ||||
|                 stoppedNodes.should.have.a.property(sfConfigId); | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("instantiates a subflow inside a subflow and stops it",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 1","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]}, | ||||
|                 {id:"sf2",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{wires:[]}],"out":[{"wires":[{"id":"sf2","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"subflow:sf2","z":"sf1",x:166,y:99,"wires":[[]]} | ||||
|  | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 Object.keys(currentNodes).should.have.length(0); | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("rewires nodes specified by diff",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
| @@ -371,7 +275,7 @@ describe('Flow', function() { | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]} | ||||
|             ]); | ||||
|  | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             createCount.should.equal(0); | ||||
|             flow.start(); | ||||
|             //TODO: use update to pass in new wiring and verify the change | ||||
| @@ -382,72 +286,6 @@ describe('Flow', function() { | ||||
|             done(); | ||||
|         }); | ||||
|  | ||||
|         it("rewires a subflow node on update/start",function(done){ | ||||
|  | ||||
|             var rawConfig = [ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test1","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]} | ||||
|             ]; | ||||
|  | ||||
|             var config = flowUtils.parseConfig(clone(rawConfig)); | ||||
|  | ||||
|             rawConfig[2].wires = [["4"]]; | ||||
|  | ||||
|             var newConfig = flowUtils.parseConfig(rawConfig); | ||||
|             var diff = flowUtils.diffConfigs(config,newConfig); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(6); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[4]; | ||||
|             var sfInstanceId2 = Object.keys(activeNodes)[5]; | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|             currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             flow.update(newConfig,newConfig.flows["t1"]); | ||||
|             flow.start(diff) | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test2"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",2); | ||||
|             currentNodes[sfInstanceId].should.have.a.property("handled",2); | ||||
|             currentNodes[sfInstanceId2].should.have.a.property("handled",2); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         it("instantiates a node with environment variable property values",function(done) { | ||||
|             after(function() { | ||||
|                 delete process.env.NODE_RED_TEST_VALUE; | ||||
| @@ -462,7 +300,7 @@ describe('Flow', function() { | ||||
|                 {id:"5",x:10,y:10,z:"t1",type:"test",foo:"$(NODE_RED_TEST_VALUE_NONE)",wires:[]}, | ||||
|                 {id:"6",x:10,y:10,z:"t1",type:"test",foo:["$(NODE_RED_TEST_VALUE)"],wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
| @@ -492,7 +330,7 @@ describe('Flow', function() { | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"asyncTest",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             flow.start(); | ||||
|  | ||||
|  | ||||
| @@ -518,7 +356,7 @@ describe('Flow', function() { | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             flow.start(); | ||||
|  | ||||
|             currentNodes.should.have.a.property("1"); | ||||
| @@ -536,35 +374,6 @@ describe('Flow', function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("stops subflow instance nodes",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[[]]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(4); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[3]; | ||||
|             flow.stop(["2"]).then(function() { | ||||
|                 currentNodes.should.not.have.a.property(sfInstanceId); | ||||
|                 stoppedNodes.should.have.a.property(sfInstanceId); | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("Times out a node that fails to close", function(done) { | ||||
|             Flow.init({settings:{nodeCloseTimeout:50},log:{ | ||||
|                 log: sinon.stub(), | ||||
| @@ -581,7 +390,7 @@ describe('Flow', function() { | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|             flow.start(); | ||||
|  | ||||
|             currentNodes.should.have.a.property("1"); | ||||
| @@ -615,12 +424,7 @@ describe('Flow', function() { | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}, | ||||
|                 {id:"sn2",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
| @@ -664,12 +468,7 @@ describe('Flow', function() { | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",scope:["2"],foo:"a",wires:[]}, | ||||
|                 {id:"sn2",x:10,y:10,z:"t1",type:"status",scope:["1"],foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
| @@ -696,147 +495,6 @@ describe('Flow', function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("passes a status event to the adjacent status node in subflow",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sf1-sn",x:10,y:10,z:"sf1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[3]; | ||||
|             var statusInstanceId = Object.keys(activeNodes)[4]; | ||||
|  | ||||
|             flow.handleStatus(activeNodes[sfInstanceId],{text:"my-status"}); | ||||
|  | ||||
|             currentNodes[statusInstanceId].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes[statusInstanceId].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","my-status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.status.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.status.source.should.have.a.property("name",undefined); | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("passes a status event to the multiple adjacent status nodes in subflow",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sf1-sn",x:10,y:10,z:"sf1",type:"status",foo:"a",wires:[]}, | ||||
|                 {id:"sf1-sn2",x:10,y:10,z:"sf1",type:"status",scope:["none"],wires:[]}, | ||||
|                 {id:"sf1-sn3",x:10,y:10,z:"sf1",type:"status",scope:["sf1-1"],wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             var sfInstanceId = Object.keys(activeNodes)[4]; | ||||
|             var statusInstanceId = Object.keys(activeNodes)[5]; | ||||
|             var statusInstanceId2 = Object.keys(activeNodes)[6]; | ||||
|             var statusInstanceId3 = Object.keys(activeNodes)[7]; | ||||
|  | ||||
|             flow.handleStatus(activeNodes[sfInstanceId],{text:"my-status"}); | ||||
|  | ||||
|             currentNodes[statusInstanceId].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes[statusInstanceId].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","my-status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.status.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.status.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             activeNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes[statusInstanceId2].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes[statusInstanceId3].should.have.a.property("handled",1); | ||||
|             statusMessage = currentNodes[statusInstanceId3].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","my-status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.status.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.status.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("passes a status event to the subflow's parent tab status node",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[3]; | ||||
|  | ||||
|             flow.handleStatus(activeNodes[sfInstanceId],{text:"my-status"}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","my-status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.status.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.status.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|  | ||||
| @@ -850,12 +508,7 @@ describe('Flow', function() { | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}, | ||||
|                 {id:"sn2",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
| @@ -899,12 +552,7 @@ describe('Flow', function() { | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"catch",scope:["2"],foo:"a",wires:[]}, | ||||
|                 {id:"sn2",x:10,y:10,z:"t1",type:"catch",scope:["1"],foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
| @@ -930,143 +578,7 @@ describe('Flow', function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("passes an error event to the adjacent catch node in subflow",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sf1-sn",x:10,y:10,z:"sf1",type:"catch",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[3]; | ||||
|             var catchInstanceId = Object.keys(activeNodes)[4]; | ||||
|  | ||||
|             flow.handleError(activeNodes[sfInstanceId],"my-error",{a:"foo"}); | ||||
|  | ||||
|             currentNodes[catchInstanceId].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes[catchInstanceId].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","my-error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.error.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.error.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("passes an error event to the multiple adjacent catch nodes in subflow",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sf1-sn",x:10,y:10,z:"sf1",type:"catch",foo:"a",wires:[]}, | ||||
|                 {id:"sf1-sn2",x:10,y:10,z:"sf1",type:"catch",scope:["none"],wires:[]}, | ||||
|                 {id:"sf1-sn3",x:10,y:10,z:"sf1",type:"catch",scope:["sf1-1"],wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[3]; | ||||
|             var catchInstanceId = Object.keys(activeNodes)[4]; | ||||
|             var catchInstanceId2 = Object.keys(activeNodes)[5]; | ||||
|             var catchInstanceId3 = Object.keys(activeNodes)[6]; | ||||
|  | ||||
|             flow.handleError(activeNodes[sfInstanceId],"my-error",{a:"foo"}); | ||||
|  | ||||
|             currentNodes[catchInstanceId].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes[catchInstanceId].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","my-error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.error.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.error.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             currentNodes[catchInstanceId2].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes[catchInstanceId3].should.have.a.property("handled",1); | ||||
|             statusMessage = currentNodes[catchInstanceId3].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","my-error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.error.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.error.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("passes an error event to the subflow's parent tab catch node",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             var sfInstanceId = Object.keys(activeNodes)[3]; | ||||
|  | ||||
|             flow.handleError(activeNodes[sfInstanceId],"my-error",{a:"foo"}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","my-error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("id",sfInstanceId); | ||||
|             statusMessage.error.source.should.have.a.property("type","test2"); | ||||
|             statusMessage.error.source.should.have.a.property("name",undefined); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("moves any existing error object sideways",function(done){ | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
| @@ -1075,12 +587,7 @@ describe('Flow', function() { | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create(config,config.flows["t1"]); | ||||
|  | ||||
|             getNode.restore(); | ||||
|             getNode = sinon.stub(flows,"get",function(id) { | ||||
|                 return flow.getNode(id); | ||||
|             }); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|   | ||||
							
								
								
									
										460
									
								
								test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,460 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
|  | ||||
| var should = require("should"); | ||||
| var sinon = require('sinon'); | ||||
| var clone = require('clone'); | ||||
| var util = require("util"); | ||||
|  | ||||
| var NR_TEST_UTILS = require("nr-test-utils"); | ||||
|  | ||||
| var Subflow = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows/Subflow"); | ||||
| var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows/Flow"); | ||||
|  | ||||
| var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows/util"); | ||||
| var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows"); | ||||
| var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node"); | ||||
| var typeRegistry = NR_TEST_UTILS.require("@node-red/registry"); | ||||
|  | ||||
| describe('Subflow', function() { | ||||
|     var getType; | ||||
|  | ||||
|     var stoppedNodes = {}; | ||||
|     var currentNodes = {}; | ||||
|     var rewiredNodes = {}; | ||||
|     var createCount = 0; | ||||
|  | ||||
|     beforeEach(function() { | ||||
|         currentNodes = {}; | ||||
|         stoppedNodes = {}; | ||||
|         rewiredNodes = {}; | ||||
|         createCount = 0; | ||||
|         var runtime = { | ||||
|             settings:{}, | ||||
|             log:{ | ||||
|                 log: sinon.stub(), // function() { console.log("l",[...arguments].map(a => JSON.stringify(a)).join(" ")) },// | ||||
|                 debug: sinon.stub(), // function() { console.log("d",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|                 trace: sinon.stub(), // function() { console.log("t",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|                 warn: sinon.stub(), // function() { console.log("w",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|                 info: sinon.stub(), // function() { console.log("i",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(), | ||||
|                 metric: sinon.stub(), | ||||
|                 _: function() { return "abc"} | ||||
|             } | ||||
|         } | ||||
|         Flow.init(runtime); | ||||
|         Subflow.init(runtime); | ||||
|     }); | ||||
|  | ||||
|     var TestNode = function(n) { | ||||
|         Node.call(this,n); | ||||
|         this._index = createCount++; | ||||
|         this.scope = n.scope; | ||||
|         var node = this; | ||||
|         this.foo = n.foo; | ||||
|         this.handled = 0; | ||||
|         this.stopped = false; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             // console.log(this.id,msg.payload); | ||||
|             node.handled++; | ||||
|             node.send(msg); | ||||
|         }); | ||||
|         this.on('close',function() { | ||||
|             node.stopped = true; | ||||
|             stoppedNodes[node.id] = node; | ||||
|             delete currentNodes[node.id]; | ||||
|         }); | ||||
|         this.__updateWires = this.updateWires; | ||||
|         this.updateWires = function(newWires) { | ||||
|             rewiredNodes[node.id] = node; | ||||
|             node.newWires = newWires; | ||||
|             node.__updateWires(newWires); | ||||
|         }; | ||||
|     } | ||||
|     util.inherits(TestNode,Node); | ||||
|  | ||||
|     var TestErrorNode = function(n) { | ||||
|         Node.call(this,n); | ||||
|         this._index = createCount++; | ||||
|         this.scope = n.scope; | ||||
|         this.name = n.name; | ||||
|         var node = this; | ||||
|         this.foo = n.foo; | ||||
|         this.handled = 0; | ||||
|         this.stopped = false; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             node.handled++; | ||||
|             node.error("test error",msg); | ||||
|         }); | ||||
|         this.on('close',function() { | ||||
|             node.stopped = true; | ||||
|             stoppedNodes[node.id] = node; | ||||
|             delete currentNodes[node.id]; | ||||
|         }); | ||||
|         this.__updateWires = this.updateWires; | ||||
|         this.updateWires = function(newWires) { | ||||
|             rewiredNodes[node.id] = node; | ||||
|             node.newWires = newWires; | ||||
|             node.__updateWires(newWires); | ||||
|         }; | ||||
|     } | ||||
|     util.inherits(TestErrorNode,Node); | ||||
|  | ||||
|  | ||||
|     var TestStatusNode = function(n) { | ||||
|         Node.call(this,n); | ||||
|         this._index = createCount++; | ||||
|         this.scope = n.scope; | ||||
|         this.name = n.name; | ||||
|         var node = this; | ||||
|         this.foo = n.foo; | ||||
|         this.handled = 0; | ||||
|         this.stopped = false; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             node.handled++; | ||||
|             node.status({text:"test status"}); | ||||
|         }); | ||||
|         this.on('close',function() { | ||||
|             node.stopped = true; | ||||
|             stoppedNodes[node.id] = node; | ||||
|             delete currentNodes[node.id]; | ||||
|         }); | ||||
|         this.__updateWires = this.updateWires; | ||||
|         this.updateWires = function(newWires) { | ||||
|             rewiredNodes[node.id] = node; | ||||
|             node.newWires = newWires; | ||||
|             node.__updateWires(newWires); | ||||
|         }; | ||||
|     } | ||||
|     util.inherits(TestStatusNode,Node); | ||||
|  | ||||
|     var TestAsyncNode = function(n) { | ||||
|         Node.call(this,n); | ||||
|         var node = this; | ||||
|         this.scope = n.scope; | ||||
|         this.foo = n.foo; | ||||
|         this.handled = 0; | ||||
|         this.messages = []; | ||||
|         this.stopped = false; | ||||
|         this.closeDelay = n.closeDelay || 50; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             node.handled++; | ||||
|             node.messages.push(msg); | ||||
|             node.send(msg); | ||||
|         }); | ||||
|         this.on('close',function(done) { | ||||
|             setTimeout(function() { | ||||
|                 node.stopped = true; | ||||
|                 stoppedNodes[node.id] = node; | ||||
|                 delete currentNodes[node.id]; | ||||
|                 done(); | ||||
|             },node.closeDelay); | ||||
|         }); | ||||
|     } | ||||
|     util.inherits(TestAsyncNode,Node); | ||||
|  | ||||
|     before(function() { | ||||
|         getType = sinon.stub(typeRegistry,"get",function(type) { | ||||
|             if (type=="test") { | ||||
|                 return TestNode; | ||||
|             } else if (type=="testError"){ | ||||
|                 return TestErrorNode; | ||||
|             } else if (type=="testStatus"){ | ||||
|                 return TestStatusNode; | ||||
|             } else { | ||||
|                 return TestAsyncNode; | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
|     after(function() { | ||||
|         getType.restore(); | ||||
|     }); | ||||
|     describe('#start',function() { | ||||
|         it("instantiates a subflow and stops it",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]},{"wires":[{"id":"sf1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"test","z":"sf1",foo:"sf1-cn",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sf1-cn",type:"test","z":"sf1"} | ||||
|             ]); | ||||
|             var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(4); | ||||
|             // var sfInstanceId = Object.keys(activeNodes)[5]; | ||||
|             // var sfInstanceId2 = Object.keys(activeNodes)[6]; | ||||
|             var sfConfigId = Object.keys(activeNodes)[4]; | ||||
|  | ||||
|             flow.getNode('1').should.have.a.property('id','1'); | ||||
|             flow.getNode('2').should.have.a.property('id','2'); | ||||
|             flow.getNode('3').should.have.a.property('id','3'); | ||||
|             flow.getNode('4').should.have.a.property('id','4'); | ||||
|             // flow.getNode(sfInstanceId).should.have.a.property('id',sfInstanceId); | ||||
|             // flow.getNode(sfInstanceId2).should.have.a.property('id',sfInstanceId2); | ||||
|             // flow.getNode(sfConfigId).should.have.a.property('id',sfConfigId); | ||||
|  | ||||
|             // flow.getNode(sfInstanceId2).should.have.a.property('foo',sfConfigId); | ||||
|  | ||||
|             Object.keys(currentNodes).should.have.length(6); | ||||
|  | ||||
|             currentNodes.should.have.a.property("1"); | ||||
|             currentNodes.should.not.have.a.property("2"); | ||||
|             currentNodes.should.have.a.property("3"); | ||||
|             currentNodes.should.have.a.property("4"); | ||||
|             // currentNodes.should.have.a.property(sfInstanceId); | ||||
|             // currentNodes.should.have.a.property(sfInstanceId2); | ||||
|             // currentNodes.should.have.a.property(sfConfigId); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",0); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 Object.keys(currentNodes).should.have.length(0); | ||||
|                 Object.keys(stoppedNodes).should.have.length(6); | ||||
|  | ||||
|                 // currentNodes.should.not.have.a.property("1"); | ||||
|                 // currentNodes.should.not.have.a.property("3"); | ||||
|                 // currentNodes.should.not.have.a.property("4"); | ||||
|                 // // currentNodes.should.not.have.a.property(sfInstanceId); | ||||
|                 // // currentNodes.should.not.have.a.property(sfInstanceId2); | ||||
|                 // // currentNodes.should.not.have.a.property(sfConfigId); | ||||
|                 // stoppedNodes.should.have.a.property("1"); | ||||
|                 // stoppedNodes.should.have.a.property("3"); | ||||
|                 // stoppedNodes.should.have.a.property("4"); | ||||
|                 // // stoppedNodes.should.have.a.property(sfInstanceId); | ||||
|                 // // stoppedNodes.should.have.a.property(sfInstanceId2); | ||||
|                 // // stoppedNodes.should.have.a.property(sfConfigId); | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("instantiates a subflow inside a subflow and stops it",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 1","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]}, | ||||
|                 {id:"sf2",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{wires:[]}],"out":[{"wires":[{"id":"sf2","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"subflow:sf2","z":"sf1",x:166,y:99,"wires":[[]]} | ||||
|  | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 Object.keys(currentNodes).should.have.length(0); | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|         it("rewires a subflow node on update/start",function(done){ | ||||
|  | ||||
|             var rawConfig = [ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test1","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]} | ||||
|             ]; | ||||
|  | ||||
|             var config = flowUtils.parseConfig(clone(rawConfig)); | ||||
|  | ||||
|             rawConfig[2].wires = [["4"]]; | ||||
|  | ||||
|             var newConfig = flowUtils.parseConfig(rawConfig); | ||||
|             var diff = flowUtils.diffConfigs(config,newConfig); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(4); | ||||
|             // var sfInstanceId = Object.keys(activeNodes)[4]; | ||||
|             // var sfInstanceId2 = Object.keys(activeNodes)[5]; | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             flow.update(newConfig,newConfig.flows["t1"]); | ||||
|             flow.start(diff) | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test2"}); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",2); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",2); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",2); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     describe('#stop', function() { | ||||
|         it("stops subflow instance nodes",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[[]]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(3); | ||||
|             Object.keys(stoppedNodes).should.have.length(0); | ||||
|             flow.stop(["2"]).then(function() { | ||||
|                 Object.keys(currentNodes).should.have.length(2); | ||||
|                 Object.keys(stoppedNodes).should.have.length(1); | ||||
|                 done(); | ||||
|             }).catch(done); | ||||
|         }); | ||||
|     }); | ||||
|     describe("#handleStatus",function() { | ||||
|         it("passes a status event to the subflow's parent tab status node",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",type:"testStatus",name:"test-status-node","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","test status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("type","testStatus"); | ||||
|             statusMessage.status.source.should.have.a.property("name","test-status-node"); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("#handleError",function() { | ||||
|         it("passes an error event to the subflow's parent tab catch node",function(done) { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow","name":"Subflow 2","info":"", | ||||
|                     "in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]}, | ||||
|                 {id:"sf1-1",name:"test-error-node",type:"testError","z":"sf1",x:166,y:99,"wires":[[]]}, | ||||
|                 {id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]} | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","test error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("type","testError"); | ||||
|             statusMessage.error.source.should.have.a.property("name","test-error-node"); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
| @@ -70,7 +70,7 @@ describe('flows/index', function() { | ||||
|         credentialsLoad = sinon.stub(credentials,"load",function() { | ||||
|             return when.resolve(); | ||||
|         }); | ||||
|         flowCreate = sinon.stub(Flow,"create",function(global, flow) { | ||||
|         flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) { | ||||
|             var id; | ||||
|             if (typeof flow === 'undefined') { | ||||
|                 flow = global; | ||||
| @@ -354,113 +354,113 @@ describe('flows/index', function() { | ||||
|     describe('#stopFlows', function() { | ||||
|  | ||||
|     }); | ||||
|     describe('#handleError', function() { | ||||
|         it('passes error to correct flow', function(done) { | ||||
|             var originalConfig = [ | ||||
|                 {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, | ||||
|                 {id:"t1",type:"tab"} | ||||
|             ]; | ||||
|             storage.getFlows = function() { | ||||
|                 return when.resolve({flows:originalConfig}); | ||||
|             } | ||||
|  | ||||
|             events.once('nodes-started',function() { | ||||
|                 flows.handleError(originalConfig[0],"message",{}); | ||||
|                 flowCreate.flows['t1'].handleError.called.should.be.true(); | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
|             flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|             flows.load().then(function() { | ||||
|                 flows.startFlows(); | ||||
|             }); | ||||
|         }); | ||||
|         it('passes error to flows that use the originating global config', function(done) { | ||||
|             var originalConfig = [ | ||||
|                 {id:"configNode",type:"test"}, | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]}, | ||||
|                 {id:"t2",type:"tab"}, | ||||
|                 {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}, | ||||
|                 {id:"t3",type:"tab"}, | ||||
|                 {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]} | ||||
|             ]; | ||||
|             storage.getFlows = function() { | ||||
|                 return when.resolve({flows:originalConfig}); | ||||
|             } | ||||
|  | ||||
|             events.once('nodes-started',function() { | ||||
|                 flows.handleError(originalConfig[0],"message",{}); | ||||
|                 try { | ||||
|                     flowCreate.flows['t1'].handleError.called.should.be.true(); | ||||
|                     flowCreate.flows['t2'].handleError.called.should.be.false(); | ||||
|                     flowCreate.flows['t3'].handleError.called.should.be.true(); | ||||
|                     done(); | ||||
|                 } catch(err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|             flows.load().then(function() { | ||||
|                 flows.startFlows(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     describe('#handleStatus', function() { | ||||
|         it('passes status to correct flow', function(done) { | ||||
|             var originalConfig = [ | ||||
|                 {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, | ||||
|                 {id:"t1",type:"tab"} | ||||
|             ]; | ||||
|             storage.getFlows = function() { | ||||
|                 return when.resolve({flows:originalConfig}); | ||||
|             } | ||||
|  | ||||
|             events.once('nodes-started',function() { | ||||
|                 flows.handleStatus(originalConfig[0],"message"); | ||||
|                 flowCreate.flows['t1'].handleStatus.called.should.be.true(); | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
|             flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|             flows.load().then(function() { | ||||
|                 flows.startFlows(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('passes status to flows that use the originating global config', function(done) { | ||||
|             var originalConfig = [ | ||||
|                 {id:"configNode",type:"test"}, | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]}, | ||||
|                 {id:"t2",type:"tab"}, | ||||
|                 {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}, | ||||
|                 {id:"t3",type:"tab"}, | ||||
|                 {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]} | ||||
|             ]; | ||||
|             storage.getFlows = function() { | ||||
|                 return when.resolve({flows:originalConfig}); | ||||
|             } | ||||
|  | ||||
|             events.once('nodes-started',function() { | ||||
|                 flows.handleStatus(originalConfig[0],"message"); | ||||
|                 try { | ||||
|                     flowCreate.flows['t1'].handleStatus.called.should.be.true(); | ||||
|                     flowCreate.flows['t2'].handleStatus.called.should.be.false(); | ||||
|                     flowCreate.flows['t3'].handleStatus.called.should.be.true(); | ||||
|                     done(); | ||||
|                 } catch(err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|             flows.load().then(function() { | ||||
|                 flows.startFlows(); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     // describe('#handleError', function() { | ||||
|     //     it('passes error to correct flow', function(done) { | ||||
|     //         var originalConfig = [ | ||||
|     //             {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, | ||||
|     //             {id:"t1",type:"tab"} | ||||
|     //         ]; | ||||
|     //         storage.getFlows = function() { | ||||
|     //             return when.resolve({flows:originalConfig}); | ||||
|     //         } | ||||
|     // | ||||
|     //         events.once('nodes-started',function() { | ||||
|     //             flows.handleError(originalConfig[0],"message",{}); | ||||
|     //             flowCreate.flows['t1'].handleError.called.should.be.true(); | ||||
|     //             done(); | ||||
|     //         }); | ||||
|     // | ||||
|     //         flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|     //         flows.load().then(function() { | ||||
|     //             flows.startFlows(); | ||||
|     //         }); | ||||
|     //     }); | ||||
|     //     it('passes error to flows that use the originating global config', function(done) { | ||||
|     //         var originalConfig = [ | ||||
|     //             {id:"configNode",type:"test"}, | ||||
|     //             {id:"t1",type:"tab"}, | ||||
|     //             {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]}, | ||||
|     //             {id:"t2",type:"tab"}, | ||||
|     //             {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}, | ||||
|     //             {id:"t3",type:"tab"}, | ||||
|     //             {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]} | ||||
|     //         ]; | ||||
|     //         storage.getFlows = function() { | ||||
|     //             return when.resolve({flows:originalConfig}); | ||||
|     //         } | ||||
|     // | ||||
|     //         events.once('nodes-started',function() { | ||||
|     //             flows.handleError(originalConfig[0],"message",{}); | ||||
|     //             try { | ||||
|     //                 flowCreate.flows['t1'].handleError.called.should.be.true(); | ||||
|     //                 flowCreate.flows['t2'].handleError.called.should.be.false(); | ||||
|     //                 flowCreate.flows['t3'].handleError.called.should.be.true(); | ||||
|     //                 done(); | ||||
|     //             } catch(err) { | ||||
|     //                 done(err); | ||||
|     //             } | ||||
|     //         }); | ||||
|     // | ||||
|     //         flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|     //         flows.load().then(function() { | ||||
|     //             flows.startFlows(); | ||||
|     //         }); | ||||
|     //     }); | ||||
|     // }); | ||||
|     // describe('#handleStatus', function() { | ||||
|     //     it('passes status to correct flow', function(done) { | ||||
|     //         var originalConfig = [ | ||||
|     //             {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, | ||||
|     //             {id:"t1",type:"tab"} | ||||
|     //         ]; | ||||
|     //         storage.getFlows = function() { | ||||
|     //             return when.resolve({flows:originalConfig}); | ||||
|     //         } | ||||
|     // | ||||
|     //         events.once('nodes-started',function() { | ||||
|     //             flows.handleStatus(originalConfig[0],"message"); | ||||
|     //             flowCreate.flows['t1'].handleStatus.called.should.be.true(); | ||||
|     //             done(); | ||||
|     //         }); | ||||
|     // | ||||
|     //         flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|     //         flows.load().then(function() { | ||||
|     //             flows.startFlows(); | ||||
|     //         }); | ||||
|     //     }); | ||||
|     // | ||||
|     //     it('passes status to flows that use the originating global config', function(done) { | ||||
|     //         var originalConfig = [ | ||||
|     //             {id:"configNode",type:"test"}, | ||||
|     //             {id:"t1",type:"tab"}, | ||||
|     //             {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]}, | ||||
|     //             {id:"t2",type:"tab"}, | ||||
|     //             {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}, | ||||
|     //             {id:"t3",type:"tab"}, | ||||
|     //             {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]} | ||||
|     //         ]; | ||||
|     //         storage.getFlows = function() { | ||||
|     //             return when.resolve({flows:originalConfig}); | ||||
|     //         } | ||||
|     // | ||||
|     //         events.once('nodes-started',function() { | ||||
|     //             flows.handleStatus(originalConfig[0],"message"); | ||||
|     //             try { | ||||
|     //                 flowCreate.flows['t1'].handleStatus.called.should.be.true(); | ||||
|     //                 flowCreate.flows['t2'].handleStatus.called.should.be.false(); | ||||
|     //                 flowCreate.flows['t3'].handleStatus.called.should.be.true(); | ||||
|     //                 done(); | ||||
|     //             } catch(err) { | ||||
|     //                 done(err); | ||||
|     //             } | ||||
|     //         }); | ||||
|     // | ||||
|     //         flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|     //         flows.load().then(function() { | ||||
|     //             flows.startFlows(); | ||||
|     //         }); | ||||
|     //     }); | ||||
|     // }); | ||||
|  | ||||
|     describe('#checkTypeInUse', function() { | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user