diff --git a/packages/node_modules/@node-red/nodes/core/core/25-complete.html b/packages/node_modules/@node-red/nodes/core/core/25-complete.html new file mode 100644 index 000000000..f82ed02f8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/core/25-complete.html @@ -0,0 +1,136 @@ + + diff --git a/packages/node_modules/@node-red/nodes/core/core/25-complete.js b/packages/node_modules/@node-red/nodes/core/core/25-complete.js new file mode 100644 index 000000000..78008f81d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/core/25-complete.js @@ -0,0 +1,30 @@ +/** + * 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. + **/ + +module.exports = function(RED) { + "use strict"; + + function CompleteNode(n) { + RED.nodes.createNode(this,n); + var node = this; + this.scope = n.scope; + this.on("input",function(msg) { + this.send(msg); + }); + } + + RED.nodes.registerType("complete",CompleteNode); +} diff --git a/packages/node_modules/@node-red/nodes/core/core/58-debug.js b/packages/node_modules/@node-red/nodes/core/core/58-debug.js index 1571605da..badbad25e 100644 --- a/packages/node_modules/@node-red/nodes/core/core/58-debug.js +++ b/packages/node_modules/@node-red/nodes/core/core/58-debug.js @@ -81,7 +81,7 @@ module.exports = function(RED) { } } - this.on("input", function(msg) { + this.on("input", function(msg, done) { if (this.complete === "true") { // debug complete msg object if (this.console === "true") { @@ -90,13 +90,14 @@ module.exports = function(RED) { if (this.active && this.tosidebar) { sendDebug({id:node.id, name:node.name, topic:msg.topic, msg:msg, _path:msg._path}); } + done(); } else { - prepareValue(msg,function(err,msg) { + prepareValue(msg,function(err,debugMsg) { if (err) { node.error(err); return; } - var output = msg.msg; + var output = debugMsg.msg; if (node.console === "true") { if (typeof output === "string") { node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output); @@ -114,9 +115,10 @@ module.exports = function(RED) { } if (node.active) { if (node.tosidebar == true) { - sendDebug(msg); + sendDebug(debugMsg); } } + done(); }); } }) diff --git a/packages/node_modules/@node-red/nodes/core/logic/15-change.js b/packages/node_modules/@node-red/nodes/core/logic/15-change.js index 8d100233e..fb25939c0 100644 --- a/packages/node_modules/@node-red/nodes/core/logic/15-change.js +++ b/packages/node_modules/@node-red/nodes/core/logic/15-change.js @@ -328,13 +328,14 @@ module.exports = function(RED) { } if (valid) { - this.on('input', function(msg) { + this.on('input', function(msg, done) { applyRules(msg, 0, (err,msg) => { if (err) { node.error(err,msg); } else if (msg) { node.send(msg); } + done(); }) }); } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/core/25-complete.html b/packages/node_modules/@node-red/nodes/locales/en-US/core/25-complete.html new file mode 100644 index 000000000..d73f8e13d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/en-US/core/25-complete.html @@ -0,0 +1,29 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 76e1cf2f8..6e8794eb7 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -112,6 +112,9 @@ "selected": "selected nodes" } }, + "complete": { + "completeNodes": "complete: __number__" + }, "debug": { "output": "Output", "none": "None", diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index b1003d717..f4ecabc86 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -27,6 +27,8 @@ function Node(n) { this.type = n.type; this.z = n.z; this._closeCallbacks = []; + this._inputCallback = null; + this._inputCallbacks = null; if (n.name) { this.name = n.name; @@ -43,6 +45,10 @@ function Node(n) { // as part of its constructure - config._flow will overwrite this._flow // which we can tolerate as they are the same object. Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true }) + this._asyncDelivery = n._flow.asyncMessageDelivery; + } + if (this._asyncDelivery === undefined) { + this._asyncDelivery = true; } this.updateWires(n.wires); } @@ -79,17 +85,95 @@ Node.prototype.context = function() { return this._context; } -Node.prototype._on = Node.prototype.on; +Node.prototype._complete = function(msg,error) { + if (error) { + // For now, delegate this to this.error + // But at some point, the timeout handling will need to know about + // this as well. + this.error(error,msg); + } else { + this._flow.handleComplete(this,msg); + } +} +Node.prototype._on = Node.prototype.on; Node.prototype.on = function(event, callback) { var node = this; if (event == "close") { this._closeCallbacks.push(callback); + } else if (event === "input") { + if (this._inputCallback) { + this._inputCallbacks = [this._inputCallback, callback]; + this._inputCallback = null; + } else if (this._inputCallbacks) { + this._inputCallbacks.push(callback); + } else { + this._inputCallback = callback; + } } else { this._on(event, callback); } }; +Node.prototype._emit = Node.prototype.emit; +Node.prototype.emit = function(event,arg) { + var node = this; + if (event === "input") { + // When Pluggable Message Routing arrives, this will be called from + // that and will already be sync/async depending on the router. + if (this._asyncDelivery) { + setImmediate(function() { + node._emitInput(arg); + }); + } else { + this._emitInput(arg); + } + } else { + this._emit(event,arg); + } +} + +Node.prototype._emitInput = function(arg) { + var node = this; + if (node._inputCallback) { + try { + node._inputCallback(arg,function(err) { node._complete(arg,err); }); + } catch(err) { + node.error(err,arg); + } + } else if (node._inputCallbacks) { + var c = node._inputCallbacks.length; + for (var i=0;i { + this.completeNodeMap[id] = this.completeNodeMap[id] || []; + this.completeNodeMap[id].push(node); + }) + } } } } @@ -516,6 +525,20 @@ class Flow { return handled; } + handleComplete(node,msg) { + if (this.completeNodeMap[node.id]) { + let toSend = msg; + this.completeNodeMap[node.id].forEach((completeNode,index) => { + toSend = redUtil.cloneMessage(msg); + completeNode.receive(toSend); + }) + } + } + + get asyncMessageDelivery() { + return asyncMessageDelivery + } + dump() { console.log("==================") console.log(this.TYPE, this.id); @@ -562,6 +585,7 @@ function stopNode(node,removed) { module.exports = { init: function(runtime) { nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000; + asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery Log = runtime.log; Subflow = require("./Subflow"); Subflow.init(runtime); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js index ef9fbdab2..981ed7173 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js @@ -729,6 +729,10 @@ module.exports = { updateFlow: updateFlow, removeFlow: removeFlow, disableFlow:null, - enableFlow:null + enableFlow:null, + isDeliveryModeAsync: function() { + // If settings is null, this is likely being run by unit tests + return !settings || !settings.runtimeSyncDelivery + } }; diff --git a/test/nodes/core/core/80-function_spec.js b/test/nodes/core/core/80-function_spec.js index 01e40c464..295c45ee2 100644 --- a/test/nodes/core/core/80-function_spec.js +++ b/test/nodes/core/core/80-function_spec.js @@ -268,45 +268,50 @@ describe('function node', function() { helper.load(functionNode, flow, function() { var n1 = helper.getNode("n1"); n1.receive({payload:"foo",topic: "bar"}); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().ERROR); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)'); - done(); - } catch(err) { - done(err); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().ERROR); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)'); + done(); + } catch(err) { + done(err); + } + },50); }); }); it('should handle node.on()', function(done) { - var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"node.on('close',function(){node.log('closed')});"}]; + var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"node.on('close',function(){ node.log('closed')});"}]; helper.load(functionNode, flow, function() { var n1 = helper.getNode("n1"); n1.receive({payload:"foo",topic: "bar"}); - helper.getNode("n1").close(); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "function"; + setTimeout(function() { + n1.close().then(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().INFO); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'closed'); + done(); + } catch(err) { + done(err); + } }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().INFO); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'closed'); - done(); - } catch(err) { - done(err); - } + },1500); }); }); @@ -532,22 +537,24 @@ describe('function node', function() { }); function checkCallbackError(name, done) { - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().ERROR); - msg.should.have.property('id', name); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'Error: Callback must be a function'); - done(); - } - catch (e) { - done(e); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().ERROR); + msg.should.have.property('id', name); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'Error: Callback must be a function'); + done(); + } + catch (e) { + done(e); + } + },50); } it('should get persistable node context (w/o callback)', function(done) { @@ -1267,12 +1274,12 @@ describe('function node', function() { n1.receive({payload:"foo",topic: "bar"}); } else { msg.should.have.property('payload', "hello"); + delete process.env._TEST_FOO_; done(); } } catch(err) { - done(err); - } finally { delete process.env._TEST_FOO_; + done(err); } }); n1.receive({payload:"foo",topic: "bar"}); @@ -1285,21 +1292,23 @@ describe('function node', function() { helper.load(functionNode, flow, function () { var n1 = helper.getNode("n1"); n1.receive({payload: "foo", topic: "bar"}); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().INFO); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'test'); - done(); - } catch (err) { - done(err); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().INFO); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'test'); + done(); + } catch (err) { + done(err); + } + },50); }); }); it('should log a Debug Message', function (done) { @@ -1307,21 +1316,23 @@ describe('function node', function() { helper.load(functionNode, flow, function () { var n1 = helper.getNode("n1"); n1.receive({payload: "foo", topic: "bar"}); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().DEBUG); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'test'); - done(); - } catch (err) { - done(err); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().DEBUG); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'test'); + done(); + } catch (err) { + done(err); + } + },50); }); }); it('should log a Trace Message', function (done) { @@ -1329,21 +1340,23 @@ describe('function node', function() { helper.load(functionNode, flow, function () { var n1 = helper.getNode("n1"); n1.receive({payload: "foo", topic: "bar"}); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().TRACE); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'test'); - done(); - } catch (err) { - done(err); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().TRACE); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'test'); + done(); + } catch (err) { + done(err); + } + },50); }); }); it('should log a Warning Message', function (done) { @@ -1351,21 +1364,23 @@ describe('function node', function() { helper.load(functionNode, flow, function () { var n1 = helper.getNode("n1"); n1.receive({payload: "foo", topic: "bar"}); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().WARN); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'test'); - done(); - } catch (err) { - done(err); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().WARN); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'test'); + done(); + } catch (err) { + done(err); + } + },50); }); }); it('should log an Error Message', function (done) { @@ -1373,21 +1388,23 @@ describe('function node', function() { helper.load(functionNode, flow, function () { var n1 = helper.getNode("n1"); n1.receive({payload: "foo", topic: "bar"}); - try { - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "function"; - }); - logEvents.should.have.length(1); - var msg = logEvents[0][0]; - msg.should.have.property('level', helper.log().ERROR); - msg.should.have.property('id', 'n1'); - msg.should.have.property('type', 'function'); - msg.should.have.property('msg', 'test'); - done(); - } catch (err) { - done(err); - } + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "function"; + }); + logEvents.should.have.length(1); + var msg = logEvents[0][0]; + msg.should.have.property('level', helper.log().ERROR); + msg.should.have.property('id', 'n1'); + msg.should.have.property('type', 'function'); + msg.should.have.property('msg', 'test'); + done(); + } catch (err) { + done(err); + } + },50); }); }); }); diff --git a/test/nodes/core/logic/18-sort_spec.js b/test/nodes/core/logic/18-sort_spec.js index ac8d7d11c..960a6d666 100644 --- a/test/nodes/core/logic/18-sort_spec.js +++ b/test/nodes/core/logic/18-sort_spec.js @@ -475,20 +475,21 @@ describe('SORT node', function() { {id:"n2", type:"helper"}]; helper.load(sortNode, flow, function() { var n1 = helper.getNode("n1"); - setTimeout(function() { - var logEvents = helper.log().args.filter(function (evt) { - return evt[0].type == "sort"; - }); - var evt = logEvents[0][0]; - evt.should.have.property('id', "n1"); - evt.should.have.property('type', "sort"); - evt.should.have.property('msg', "sort.clear"); - done(); - }, 150); var msg = { payload: 0, parts: { id: "X", index: 0, count: 2} }; n1.receive(msg); - n1.close(); + setTimeout(function() { + n1.close().then(function() { + var logEvents = helper.log().args.filter(function (evt) { + return evt[0].type == "sort"; + }); + var evt = logEvents[0][0]; + evt.should.have.property('id', "n1"); + evt.should.have.property('type', "sort"); + evt.should.have.property('msg', "sort.clear"); + done(); + }); + }, 150); }); }); diff --git a/test/nodes/core/parsers/70-HTML_spec.js b/test/nodes/core/parsers/70-HTML_spec.js index e7e7ffa8b..3183a0539 100644 --- a/test/nodes/core/parsers/70-HTML_spec.js +++ b/test/nodes/core/parsers/70-HTML_spec.js @@ -21,7 +21,7 @@ var fs = require('fs-extra'); var htmlNode = require("nr-test-utils").require("@node-red/nodes/core/parsers/70-HTML.js"); var helper = require("node-red-node-test-helper"); -describe('html node', function() { +describe('HTML node', function() { var resourcesDir = __dirname+ path.sep + ".." + path.sep + ".." + path.sep + ".." + path.sep + "resources" + path.sep; var file = path.join(resourcesDir, "70-HTML-test-file.html"); @@ -228,16 +228,20 @@ describe('html node', function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n1.receive({payload:null,topic: "bar"}); - helper.log().called.should.be.true(); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "html"; - }); - logEvents.should.have.length(1); - // Each logEvent is the array of args passed to the function. - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + setTimeout(function() { + try { + helper.log().called.should.be.true(); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "html"; + }); + logEvents.should.have.length(1); + // Each logEvent is the array of args passed to the function. + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } diff --git a/test/nodes/core/parsers/70-JSON_spec.js b/test/nodes/core/parsers/70-JSON_spec.js index 7effdfab7..2ec3304bb 100644 --- a/test/nodes/core/parsers/70-JSON_spec.js +++ b/test/nodes/core/parsers/70-JSON_spec.js @@ -148,14 +148,18 @@ describe('JSON node', function() { var jn1 = helper.getNode("jn1"); var jn2 = helper.getNode("jn2"); jn1.receive({payload:'foo',topic: "bar"}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "json"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.startWith("Unexpected token o"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.startWith("Unexpected token o"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },20); } catch(err) { done(err); } @@ -378,14 +382,18 @@ describe('JSON node', function() { var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var obj = {"number": "foo", "string": 3}; jn1.receive({payload:obj, schema:schema}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "json"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } @@ -402,14 +410,18 @@ describe('JSON node', function() { var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var obj = {"number": "foo", "string": 3}; jn1.receive({payload:obj, schema:schema}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "json"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } @@ -426,14 +438,18 @@ describe('JSON node', function() { var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var jsonString = '{"number":"Hello","string":3}'; jn1.receive({payload:jsonString, schema:schema}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "json"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } @@ -450,14 +466,18 @@ describe('JSON node', function() { var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var jsonString = '{"number":"Hello","string":3}'; jn1.receive({payload:jsonString, schema:schema}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "json"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } @@ -474,14 +494,18 @@ describe('JSON node', function() { var schema = "garbage"; var obj = {"number": "foo", "string": 3}; jn1.receive({payload:obj, schema:schema}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "json"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.equal("json.errors.schema-error-compile"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error-compile"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } diff --git a/test/nodes/core/parsers/70-YAML_spec.js b/test/nodes/core/parsers/70-YAML_spec.js index 925a52922..3441e0946 100644 --- a/test/nodes/core/parsers/70-YAML_spec.js +++ b/test/nodes/core/parsers/70-YAML_spec.js @@ -130,14 +130,18 @@ describe('YAML node', function() { var yn1 = helper.getNode("yn1"); var yn2 = helper.getNode("yn2"); yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"}); - var logEvents = helper.log().args.filter(function(evt) { - return evt[0].type == "yaml"; - }); - logEvents.should.have.length(1); - logEvents[0][0].should.have.a.property('msg'); - logEvents[0][0].msg.should.startWith("end of the stream"); - logEvents[0][0].should.have.a.property('level',helper.log().ERROR); - done(); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "yaml"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.startWith("end of the stream"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { done(err) } + },50); } catch(err) { done(err); } diff --git a/test/unit/@node-red/runtime/lib/nodes/Node_spec.js b/test/unit/@node-red/runtime/lib/nodes/Node_spec.js index 7e5aed1b0..8267411fb 100644 --- a/test/unit/@node-red/runtime/lib/nodes/Node_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/Node_spec.js @@ -156,10 +156,50 @@ describe('Node', function() { throw new Error("test error"); }); n.receive(message); - n.error.called.should.be.true(); - n.error.firstCall.args[1].should.equal(message); - done(); + setTimeout(function() { + n.error.called.should.be.true(); + n.error.firstCall.args[1].should.equal(message); + done(); + },50); + }); + it('calls parent flow handleComplete when callback provided', function(done) { + var n = new RedNode({id:'123',type:'abc', _flow: { + handleComplete: function(node,msg) { + try { + msg.should.deepEqual(message); + done(); + } catch(err) { + done(err); + } + } + }}); + + var message = {payload:"hello world"}; + n.on('input',function(msg, nodeDone) { + nodeDone(); + }); + n.receive(message); + }); + it('logs error if callback provides error', function(done) { + var n = new RedNode({id:'123',type:'abc'}); + sinon.stub(n,"error",function(err,msg) {}); + + var message = {payload:"hello world"}; + n.on('input',function(msg, nodeDone) { + nodeDone(new Error("test error")); + setTimeout(function() { + try { + n.error.called.should.be.true(); + n.error.firstCall.args[0].toString().should.equal("Error: test error"); + n.error.firstCall.args[1].should.equal(message); + done(); + } catch(err) { + done(err); + } + },50); + }); + n.receive(message); }); }); @@ -172,15 +212,45 @@ describe('Node', function() { 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"}; - + var messageReceived = false; n2.on('input',function(msg) { // msg equals message, and is not a new copy + messageReceived = true; should.deepEqual(msg,message); should.strictEqual(msg,message); done(); }); - n1.send(message); + messageReceived.should.be.false(); + }); + + it('emits a single message - synchronous mode', function(done) { + var flow = { + getNode: (id) => { return {'n1':n1,'n2':n2}[id]}, + asyncMessageDelivery: false + }; + 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"}; + var messageReceived = false; + var notSyncErr; + n2.on('input',function(msg) { + try { + // msg equals message, and is not a new copy + messageReceived = true; + should.deepEqual(msg,message); + should.strictEqual(msg,message); + done(notSyncErr); + } catch(err) { + done(err); + } + }); + n1.send(message); + try { + messageReceived.should.be.true(); + } catch(err) { + notSyncErr = err; + } }); it('emits multiple messages on a single output', function(done) { @@ -356,12 +426,13 @@ describe('Node', function() { it("logs the uuid for all messages sent", function(done) { var logHandler = { + msgIds:[], messagesSent: 0, emit: function(event, msg) { if (msg.event == "node.abc.send" && msg.level == Log.METRIC) { this.messagesSent++; + this.msgIds.push(msg.msgid); (typeof msg.msgid).should.not.be.equal("undefined"); - done(); } } }; @@ -375,6 +446,17 @@ describe('Node', function() { var receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'}); var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'}); sender.send({"some": "message"}); + setTimeout(function() { + try { + logHandler.messagesSent.should.equal(1); + should.exist(logHandler.msgIds[0]) + Log.removeHandler(logHandler); + done(); + } catch(err) { + Log.removeHandler(logHandler); + done(err); + } + },50) }) }); diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js index e7430a6c2..c1f7b0f3e 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/Flow_spec.js @@ -122,6 +122,7 @@ describe('Flow', function() { currentNodes[node.id] = node; this.on('input',function(msg) { node.handled++; + msg.handled = node.handled; node.messages.push(msg); node.send(msg); }); @@ -136,12 +137,42 @@ describe('Flow', function() { } util.inherits(TestAsyncNode,Node); + var TestDoneNode = function(n) { + Node.call(this,n); + var node = this; + this.scope = n.scope; + this.uncaught = n.uncaught; + 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, done) { + node.handled++; + node.messages.push(msg); + node.send(msg); + done(); + }); + this.on('close',function(done) { + setTimeout(function() { + node.stopped = true; + stoppedNodes[node.id] = node; + delete currentNodes[node.id]; + done(); + },node.closeDelay); + }); + } + util.inherits(TestDoneNode,Node); + before(function() { getType = sinon.stub(typeRegistry,"get",function(type) { if (type=="test") { return TestNode; } else if (type=="testError"){ return TestErrorNode; + } else if (type=="testDone"){ + return TestDoneNode; } else { return TestAsyncNode; } @@ -189,28 +220,28 @@ describe('Flow', function() { currentNodes["2"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",0); + currentNodes["3"].on("input", function() { + currentNodes["1"].should.have.a.property("handled",1); + currentNodes["2"].should.have.a.property("handled",1); + currentNodes["3"].should.have.a.property("handled",1); - currentNodes["1"].receive({payload:"test"}); - - currentNodes["1"].should.have.a.property("handled",1); - currentNodes["2"].should.have.a.property("handled",1); - currentNodes["3"].should.have.a.property("handled",1); - - flow.stop().then(function() { - try { - currentNodes.should.not.have.a.property("1"); - currentNodes.should.not.have.a.property("2"); - currentNodes.should.not.have.a.property("3"); - currentNodes.should.not.have.a.property("4"); - stoppedNodes.should.have.a.property("1"); - stoppedNodes.should.have.a.property("2"); - stoppedNodes.should.have.a.property("3"); - stoppedNodes.should.have.a.property("4"); - done(); - } catch(err) { - done(err); - } + flow.stop().then(function() { + try { + currentNodes.should.not.have.a.property("1"); + currentNodes.should.not.have.a.property("2"); + currentNodes.should.not.have.a.property("3"); + currentNodes.should.not.have.a.property("4"); + stoppedNodes.should.have.a.property("1"); + stoppedNodes.should.have.a.property("2"); + stoppedNodes.should.have.a.property("3"); + stoppedNodes.should.have.a.property("4"); + done(); + } catch(err) { + done(err); + } + }); }); + currentNodes["1"].receive({payload:"test"}); }); it("instantiates config nodes in the right order",function(done) { @@ -350,25 +381,27 @@ describe('Flow', function() { currentNodes["1"].receive({payload:"test"}); - currentNodes["1"].should.have.a.property("handled",1); - // Message doesn't reach 3 as 2 is disabled - currentNodes["3"].should.have.a.property("handled",0); + setTimeout(function() { + currentNodes["1"].should.have.a.property("handled",1); + // Message doesn't reach 3 as 2 is disabled + currentNodes["3"].should.have.a.property("handled",0); - flow.stop().then(function() { - try { - currentNodes.should.not.have.a.property("1"); - currentNodes.should.not.have.a.property("2"); - currentNodes.should.not.have.a.property("3"); - currentNodes.should.not.have.a.property("4"); - stoppedNodes.should.have.a.property("1"); - stoppedNodes.should.not.have.a.property("2"); - stoppedNodes.should.have.a.property("3"); - stoppedNodes.should.have.a.property("4"); - done(); - } catch(err) { - done(err); - } - }); + flow.stop().then(function() { + try { + currentNodes.should.not.have.a.property("1"); + currentNodes.should.not.have.a.property("2"); + currentNodes.should.not.have.a.property("3"); + currentNodes.should.not.have.a.property("4"); + stoppedNodes.should.have.a.property("1"); + stoppedNodes.should.not.have.a.property("2"); + stoppedNodes.should.have.a.property("3"); + stoppedNodes.should.have.a.property("4"); + done(); + } catch(err) { + done(err); + } + }); + },50); }); }); @@ -551,31 +584,34 @@ describe('Flow', function() { flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status",random:"otherProperty"}); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + setTimeout(function() { - 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","1"); - statusMessage.status.source.should.have.a.property("type","test"); - statusMessage.status.source.should.have.a.property("name","a"); + currentNodes["sn"].should.have.a.property("handled",1); + var statusMessage = currentNodes["sn"].messages[0]; - currentNodes["sn2"].should.have.a.property("handled",1); - statusMessage = currentNodes["sn2"].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","1"); + statusMessage.status.source.should.have.a.property("type","test"); + statusMessage.status.source.should.have.a.property("name","a"); - statusMessage.should.have.a.property("status"); - statusMessage.status.should.have.a.property("text","my-status"); - statusMessage.status.should.have.a.property("random","otherProperty"); - statusMessage.status.should.have.a.property("source"); - statusMessage.status.source.should.have.a.property("id","1"); - statusMessage.status.source.should.have.a.property("type","test"); - statusMessage.status.source.should.have.a.property("name","a"); + currentNodes["sn2"].should.have.a.property("handled",1); + statusMessage = currentNodes["sn2"].messages[0]; + + statusMessage.should.have.a.property("status"); + statusMessage.status.should.have.a.property("text","my-status"); + statusMessage.status.should.have.a.property("random","otherProperty"); + statusMessage.status.should.have.a.property("source"); + statusMessage.status.source.should.have.a.property("id","1"); + statusMessage.status.source.should.have.a.property("type","test"); + statusMessage.status.source.should.have.a.property("name","a"); - flow.stop().then(function() { - done(); - }); + flow.stop().then(function() { + done(); + }); + },50) }); it("passes a status event to the adjacent scoped status node ",function(done) { var config = flowUtils.parseConfig([ @@ -596,21 +632,23 @@ describe('Flow', function() { flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status"}); - currentNodes["sn"].should.have.a.property("handled",0); - currentNodes["sn2"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn2"].messages[0]; + setTimeout(function() { + currentNodes["sn"].should.have.a.property("handled",0); + currentNodes["sn2"].should.have.a.property("handled",1); + var statusMessage = currentNodes["sn2"].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","1"); - statusMessage.status.source.should.have.a.property("type","test"); - statusMessage.status.source.should.have.a.property("name","a"); + 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","1"); + statusMessage.status.source.should.have.a.property("type","test"); + statusMessage.status.source.should.have.a.property("name","a"); - flow.stop().then(function() { - done(); - }); + flow.stop().then(function() { + done(); + }); + },50); }); }); @@ -636,33 +674,35 @@ describe('Flow', function() { flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"}); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + setTimeout(function() { + 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","1"); - statusMessage.error.source.should.have.a.property("type","test"); - statusMessage.error.source.should.have.a.property("name","a"); + 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","1"); + statusMessage.error.source.should.have.a.property("type","test"); + statusMessage.error.source.should.have.a.property("name","a"); - currentNodes["sn2"].should.have.a.property("handled",1); - statusMessage = currentNodes["sn2"].messages[0]; + currentNodes["sn2"].should.have.a.property("handled",1); + statusMessage = currentNodes["sn2"].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","1"); - statusMessage.error.source.should.have.a.property("type","test"); - statusMessage.error.source.should.have.a.property("name","a"); + 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","1"); + statusMessage.error.source.should.have.a.property("type","test"); + statusMessage.error.source.should.have.a.property("name","a"); - // Node sn3 has uncaught:true - so should not get called - currentNodes["sn3"].should.have.a.property("handled",0); + // Node sn3 has uncaught:true - so should not get called + currentNodes["sn3"].should.have.a.property("handled",0); - flow.stop().then(function() { - done(); - }); + flow.stop().then(function() { + done(); + }); + },50); }); it("passes an error event to the adjacent scoped catch node ",function(done) { var config = flowUtils.parseConfig([ @@ -684,39 +724,42 @@ describe('Flow', function() { flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"}); - currentNodes["sn"].should.have.a.property("handled",0); - currentNodes["sn2"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn2"].messages[0]; + setTimeout(function() { + currentNodes["sn"].should.have.a.property("handled",0); + currentNodes["sn2"].should.have.a.property("handled",1); + var statusMessage = currentNodes["sn2"].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","1"); - statusMessage.error.source.should.have.a.property("type","test"); - statusMessage.error.source.should.have.a.property("name","a"); + 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","1"); + statusMessage.error.source.should.have.a.property("type","test"); + statusMessage.error.source.should.have.a.property("name","a"); - // Node sn3/4 have uncaught:true - so should not get called - currentNodes["sn3"].should.have.a.property("handled",0); - currentNodes["sn4"].should.have.a.property("handled",0); + // Node sn3/4 have uncaught:true - so should not get called + currentNodes["sn3"].should.have.a.property("handled",0); + currentNodes["sn4"].should.have.a.property("handled",0); - // Inject error that sn1/2 will ignore - so should get picked up by sn3 - flow.handleError(config.flows["t1"].nodes["3"],"my-error-2",{a:"foo-2"}); + // Inject error that sn1/2 will ignore - so should get picked up by sn3 + flow.handleError(config.flows["t1"].nodes["3"],"my-error-2",{a:"foo-2"}); + setTimeout(function() { + currentNodes["sn"].should.have.a.property("handled",0); + currentNodes["sn2"].should.have.a.property("handled",1); + currentNodes["sn3"].should.have.a.property("handled",1); + currentNodes["sn4"].should.have.a.property("handled",1); - currentNodes["sn"].should.have.a.property("handled",0); - currentNodes["sn2"].should.have.a.property("handled",1); - currentNodes["sn3"].should.have.a.property("handled",1); - currentNodes["sn4"].should.have.a.property("handled",1); + statusMessage = currentNodes["sn3"].messages[0]; + statusMessage.should.have.a.property("error"); + statusMessage.error.should.have.a.property("message","my-error-2"); + statusMessage.error.should.have.a.property("source"); + statusMessage.error.source.should.have.a.property("id","3"); + statusMessage.error.source.should.have.a.property("type","test"); - statusMessage = currentNodes["sn3"].messages[0]; - statusMessage.should.have.a.property("error"); - statusMessage.error.should.have.a.property("message","my-error-2"); - statusMessage.error.should.have.a.property("source"); - statusMessage.error.source.should.have.a.property("id","3"); - statusMessage.error.source.should.have.a.property("type","test"); - - flow.stop().then(function() { - done(); - }); + flow.stop().then(function() { + done(); + }); + },50); + },50); }); it("moves any existing error object sideways",function(done){ var config = flowUtils.parseConfig([ @@ -733,22 +776,54 @@ describe('Flow', function() { var activeNodes = flow.getActiveNodes(); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo",error:"existing"}); + setTimeout(function() { + currentNodes["sn"].should.have.a.property("handled",1); + var statusMessage = currentNodes["sn"].messages[0]; - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + statusMessage.should.have.a.property("_error","existing"); + 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","1"); + statusMessage.error.source.should.have.a.property("type","test"); + statusMessage.error.source.should.have.a.property("name","a"); - statusMessage.should.have.a.property("_error","existing"); - 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","1"); - statusMessage.error.source.should.have.a.property("type","test"); - statusMessage.error.source.should.have.a.property("name","a"); - - flow.stop().then(function() { - done(); - }); + flow.stop().then(function() { + done(); + }); + },50); }); it("prevents an error looping more than 10 times",function(){}); }); + + describe("#handleComplete",function() { + it("passes a complete event to the adjacent Complete node",function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"testDone",name:"a",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"test",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"testDone",foo:"a",wires:[]}, + {id:"cn",x:10,y:10,z:"t1",type:"complete",scope:["1","3"],foo:"a",wires:[]} + ]); + var flow = Flow.create({},config,config.flows["t1"]); + + flow.start(); + + var activeNodes = flow.getActiveNodes(); + Object.keys(activeNodes).should.have.length(4); + + var msg = {payload: "hello world"} + var n1 = currentNodes["1"].receive(msg); + setTimeout(function() { + currentNodes["cn"].should.have.a.property("handled",2); + currentNodes["cn"].messages[0].should.have.a.property("handled",1); + currentNodes["cn"].messages[1].should.have.a.property("handled",2); + flow.stop().then(function() { + done(); + }); + },50); + }); + }); + + }); diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js index d6cab9732..f92a01328 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js @@ -271,32 +271,34 @@ describe('Subflow', function() { 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); + setTimeout(function() { + 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); + 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(); - }); + // 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(); + }); + },50); }); it("instantiates a subflow inside a subflow and stops it",function(done) { var config = flowUtils.parseConfig([ @@ -322,17 +324,14 @@ describe('Subflow', function() { 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(); - }); + setTimeout(function() { + 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(); + }); + },50); }); it("rewires a subflow node on update/start",function(done){ @@ -369,27 +368,31 @@ describe('Subflow', function() { 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); + setTimeout(function() { + 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) + flow.update(newConfig,newConfig.flows["t1"]); + flow.start(diff) - currentNodes["1"].receive({payload:"test2"}); + currentNodes["1"].receive({payload:"test2"}); + setTimeout(function() { - 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); + 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(); - }); + flow.stop().then(function() { + done(); + }); + },50); + },50); }); }); describe('#stop', function() { @@ -436,20 +439,20 @@ describe('Subflow', function() { var activeNodes = flow.getActiveNodes(); activeNodes["1"].receive({payload:"test"}); + setTimeout(function() { + currentNodes["sn"].should.have.a.property("handled",1); + var statusMessage = currentNodes["sn"].messages[0]; - 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"); - 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(); - }); + flow.stop().then(function() { + done(); + }); + },50); }); it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { var config = flowUtils.parseConfig([ @@ -472,21 +475,23 @@ describe('Subflow', function() { activeNodes["1"].receive({payload:"test"}); - parentFlowStatusCalled.should.be.false(); + setTimeout(function() { + parentFlowStatusCalled.should.be.false(); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + 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"); + 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() { + flow.stop().then(function() { - done(); - }); + done(); + }); + },50); }); }); @@ -517,19 +522,21 @@ describe('Subflow', function() { activeNodes["1"].receive({payload:"test-payload"}); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + setTimeout(function() { + 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-payload"); - statusMessage.status.should.have.a.property("source"); - statusMessage.status.source.should.have.a.property("id","2"); - statusMessage.status.source.should.have.a.property("type","subflow:sf1"); + statusMessage.should.have.a.property("status"); + statusMessage.status.should.have.a.property("text","test-payload"); + statusMessage.status.should.have.a.property("source"); + statusMessage.status.source.should.have.a.property("id","2"); + statusMessage.status.source.should.have.a.property("type","subflow:sf1"); - flow.stop().then(function() { + flow.stop().then(function() { - done(); - }); + done(); + }); + },50); }); it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) { var config = flowUtils.parseConfig([ @@ -557,19 +564,21 @@ describe('Subflow', function() { activeNodes["1"].receive({payload:{text:"payload-obj"}}); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + setTimeout(function() { + 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","payload-obj"); - statusMessage.status.should.have.a.property("source"); - statusMessage.status.source.should.have.a.property("id","2"); - statusMessage.status.source.should.have.a.property("type","subflow:sf1"); + statusMessage.should.have.a.property("status"); + statusMessage.status.should.have.a.property("text","payload-obj"); + statusMessage.status.should.have.a.property("source"); + statusMessage.status.source.should.have.a.property("id","2"); + statusMessage.status.source.should.have.a.property("type","subflow:sf1"); - flow.stop().then(function() { + flow.stop().then(function() { - done(); - }); + done(); + }); + },50); }); it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { var config = flowUtils.parseConfig([ @@ -597,19 +606,21 @@ describe('Subflow', function() { activeNodes["1"].receive({status:{text:"status-obj"}}); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + setTimeout(function() { + 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","status-obj"); - statusMessage.status.should.have.a.property("source"); - statusMessage.status.source.should.have.a.property("id","2"); - statusMessage.status.source.should.have.a.property("type","subflow:sf1"); + statusMessage.should.have.a.property("status"); + statusMessage.status.should.have.a.property("text","status-obj"); + statusMessage.status.should.have.a.property("source"); + statusMessage.status.source.should.have.a.property("id","2"); + statusMessage.status.source.should.have.a.property("type","subflow:sf1"); - flow.stop().then(function() { + flow.stop().then(function() { - done(); - }); + done(); + }); + },50); }); it("does not emit a regular status event if it contains a subflow-status node", function(done) { var config = flowUtils.parseConfig([ @@ -666,18 +677,20 @@ describe('Subflow', function() { activeNodes["1"].receive({payload:"test"}); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + setTimeout(function() { + 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"); + 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(); - }); + flow.stop().then(function() { + done(); + }); + },50); }); it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { var config = flowUtils.parseConfig([ @@ -699,20 +712,22 @@ describe('Subflow', function() { activeNodes["1"].receive({payload:"test"}); - parentFlowErrorCalled.should.be.false(); + setTimeout(function() { + parentFlowErrorCalled.should.be.false(); - currentNodes["sn"].should.have.a.property("handled",1); - var statusMessage = currentNodes["sn"].messages[0]; + 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"); + 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(); - }); + flow.stop().then(function() { + done(); + }); + },50); }); }); @@ -756,11 +771,13 @@ describe('Subflow', function() { process.env["__KEY__"] = "__VAL__"; currentNodes["1"].receive({payload: "test"}); - currentNodes["3"].should.have.a.property("received", "__VAL__"); - - flow.stop().then(function() { - done(); - }); + setTimeout(function() { + currentNodes["3"].should.have.a.property("received", "__VAL__"); + + flow.stop().then(function() { + done(); + }); + },50); }); it("can access subflow env var", function(done) { @@ -792,13 +809,15 @@ describe('Subflow', function() { } process.env["__KEY__"] = "__VAL0__"; setEnv(testenv_node, "__KEY__", "__VAL1__"); - - currentNodes["1"].receive({payload: "test"}); - currentNodes["3"].should.have.a.property("received", "__VAL1__"); - flow.stop().then(function() { - done(); - }); + currentNodes["1"].receive({payload: "test"}); + setTimeout(function() { + currentNodes["3"].should.have.a.property("received", "__VAL1__"); + + flow.stop().then(function() { + done(); + }); + },50); }); it("can access nested subflow env var", function(done) { @@ -840,21 +859,27 @@ describe('Subflow', function() { process.env["__KEY__"] = "__VAL0__"; currentNodes["1"].receive({payload: "test"}); - currentNodes["3"].should.have.a.property("received", "__VAL0__"); + setTimeout(function() { + currentNodes["3"].should.have.a.property("received", "__VAL0__"); - setEnv(node_sf1_1, "__KEY__", "__VAL1__"); - currentNodes["1"].receive({payload: "test"}); - currentNodes["3"].should.have.a.property("received", "__VAL1__"); + setEnv(node_sf1_1, "__KEY__", "__VAL1__"); + currentNodes["1"].receive({payload: "test"}); + setTimeout(function() { + currentNodes["3"].should.have.a.property("received", "__VAL1__"); - setEnv(node_sf2_1, "__KEY__", "__VAL2__"); - currentNodes["1"].receive({payload: "test"}); - currentNodes["3"].should.have.a.property("received", "__VAL2__"); + setEnv(node_sf2_1, "__KEY__", "__VAL2__"); + currentNodes["1"].receive({payload: "test"}); + setTimeout(function() { + currentNodes["3"].should.have.a.property("received", "__VAL2__"); - flow.stop().then(function() { - done(); - }); + flow.stop().then(function() { + done(); + }); + },50); + },50); + },50); }); - + }); - + });