Add node done API

This commit is contained in:
Nick O'Leary 2019-07-08 23:23:33 +01:00
parent f0a51bafbe
commit 3b5ea0f15f
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
17 changed files with 1039 additions and 498 deletions

View File

@ -0,0 +1,136 @@
<script type="text/x-red" data-template-name="complete">
<div class="form-row node-input-target-row">
<button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div>
<div class="form-row node-input-target-row node-input-target-list-row" style="min-height: 100px">
<div id="node-input-complete-target-container-div"></div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('complete',{
category: 'input',
color:"#c0edc0",
defaults: {
name: {value:""},
scope: {value:[]},
uncaught: {value:false}
},
inputs:0,
outputs:1,
icon: "alert.svg",
label: function() {
if (this.name) {
return this.name;
}
return this._("complete.completeNodes",{number:this.scope.length});
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var node = this;
var scope = node.scope || [];
this._resize = function() {
var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.length;i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-target-list-row");
editorRow.css("height",height+"px");
};
var dirList = $("#node-input-complete-target-container-div").css({width: "100%", height: "100%"})
.treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
item.node.highlighted = true;
item.node.dirty = true;
RED.view.redraw();
}).on("treelistitemmouseout", function(e, item) {
item.node.highlighted = false;
item.node.dirty = true;
RED.view.redraw();
})
var candidateNodes = RED.nodes.filterNodes({z:node.z});
var allChecked = true;
var items = [];
var nodeItemMap = {};
candidateNodes.forEach(function(n) {
if (n.id === node.id) {
return;
}
var isChecked = scope.indexOf(n.id) !== -1;
allChecked = allChecked && isChecked;
var nodeDef = RED.nodes.getType(n.type);
var label;
var sublabel;
if (nodeDef) {
var l = nodeDef.label;
label = (typeof l === "function" ? l.call(n) : l)||"";
sublabel = n.type;
if (sublabel.indexOf("subflow:") === 0) {
var subflowId = sublabel.substring(8);
var subflow = RED.nodes.subflow(subflowId);
sublabel = "subflow : "+subflow.name;
}
}
if (!nodeDef || !label) {
label = n.type;
}
nodeItemMap[n.id] = {
node: n,
label: label,
sublabel: sublabel,
selected: isChecked
};
items.push(nodeItemMap[n.id]);
});
dirList.treeList('data',items);
$("#node-input-complete-target-select").on("click", function(e) {
e.preventDefault();
var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
RED.tray.hide();
RED.view.selectNodes({
selected: preselected,
onselect: function(selection) {
RED.tray.show();
var newlySelected = {};
selection.forEach(function(n) {
newlySelected[n.id] = true;
if (nodeItemMap[n.id]) {
nodeItemMap[n.id].treeList.select(true);
}
})
preselected.forEach(function(id) {
if (!newlySelected[id]) {
nodeItemMap[id].treeList.select(false);
}
})
},
oncancel: function() {
RED.tray.show();
},
filter: function(n) {
return n.id !== node.id;
}
});
})
},
oneditsave: function() {
this.scope = $("#node-input-complete-target-container-div").treeList('selected').map(function(i) { return i.node.id})
},
oneditresize: function(size) {
this._resize();
}
});
</script>

View File

@ -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);
}

View File

@ -81,7 +81,7 @@ module.exports = function(RED) {
} }
} }
this.on("input", function(msg) { this.on("input", function(msg, done) {
if (this.complete === "true") { if (this.complete === "true") {
// debug complete msg object // debug complete msg object
if (this.console === "true") { if (this.console === "true") {
@ -90,13 +90,14 @@ module.exports = function(RED) {
if (this.active && this.tosidebar) { if (this.active && this.tosidebar) {
sendDebug({id:node.id, name:node.name, topic:msg.topic, msg:msg, _path:msg._path}); sendDebug({id:node.id, name:node.name, topic:msg.topic, msg:msg, _path:msg._path});
} }
done();
} else { } else {
prepareValue(msg,function(err,msg) { prepareValue(msg,function(err,debugMsg) {
if (err) { if (err) {
node.error(err); node.error(err);
return; return;
} }
var output = msg.msg; var output = debugMsg.msg;
if (node.console === "true") { if (node.console === "true") {
if (typeof output === "string") { if (typeof output === "string") {
node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output); node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
@ -114,9 +115,10 @@ module.exports = function(RED) {
} }
if (node.active) { if (node.active) {
if (node.tosidebar == true) { if (node.tosidebar == true) {
sendDebug(msg); sendDebug(debugMsg);
} }
} }
done();
}); });
} }
}) })

View File

@ -328,13 +328,14 @@ module.exports = function(RED) {
} }
if (valid) { if (valid) {
this.on('input', function(msg) { this.on('input', function(msg, done) {
applyRules(msg, 0, (err,msg) => { applyRules(msg, 0, (err,msg) => {
if (err) { if (err) {
node.error(err,msg); node.error(err,msg);
} else if (msg) { } else if (msg) {
node.send(msg); node.send(msg);
} }
done();
}) })
}); });
} }

View File

@ -0,0 +1,29 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="complete">
<p>Trigger a flow when another node completes its handling of a message.</p>
<h3>Details</h3>
<p>If a node tells the runtime when it has finished handling a message,
this node can be used to trigger a second flow.</p>
<p>For example, this can be used alongside a node with no output port,
such as the Email sending node, to continue the flow.</p>
<p>This node must be configured to handle the event for selected nodes in the
flow. Unlike the Catch node, it does not provide a 'handle all' mode automatically
applies to all nodes in the flow.</p>
<p>Not all nodes will trigger this event - it will depend on whether they
have been implemented to support this feature as introduced in Node-RED 1.0.</p>
</script>

View File

@ -112,6 +112,9 @@
"selected": "selected nodes" "selected": "selected nodes"
} }
}, },
"complete": {
"completeNodes": "complete: __number__"
},
"debug": { "debug": {
"output": "Output", "output": "Output",
"none": "None", "none": "None",

View File

@ -27,6 +27,8 @@ function Node(n) {
this.type = n.type; this.type = n.type;
this.z = n.z; this.z = n.z;
this._closeCallbacks = []; this._closeCallbacks = [];
this._inputCallback = null;
this._inputCallbacks = null;
if (n.name) { if (n.name) {
this.name = n.name; this.name = n.name;
@ -43,6 +45,10 @@ function Node(n) {
// as part of its constructure - config._flow will overwrite this._flow // as part of its constructure - config._flow will overwrite this._flow
// which we can tolerate as they are the same object. // which we can tolerate as they are the same object.
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true }) 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); this.updateWires(n.wires);
} }
@ -79,17 +85,95 @@ Node.prototype.context = function() {
return this._context; 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) { Node.prototype.on = function(event, callback) {
var node = this; var node = this;
if (event == "close") { if (event == "close") {
this._closeCallbacks.push(callback); 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 { } else {
this._on(event, callback); 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<c;i++) {
var cb = node._inputCallbacks[i];
if (cb.length === 2) {
c++;
}
try {
node._inputCallbacks[i](arg,function(err) {
c--;
if (c === 0) {
node._complete(arg,err);
}
});
} catch(err) {
node.error(err,msg);
}
}
}
}
Node.prototype._removeAllListeners = Node.prototype.removeAllListeners;
Node.prototype.removeAllListeners = function(name) {
if (name === "input") {
this._inputCallback = null;
this._inputCallbacks = null;
} else if (name === "close") {
this._closeCallbacks = [];
} else {
this._removeAllListeners(name);
}
}
Node.prototype.close = function(removed) { Node.prototype.close = function(removed) {
//console.log(this.type,this.id,removed); //console.log(this.type,this.id,removed);
var promises = []; var promises = [];
@ -233,11 +317,7 @@ Node.prototype.receive = function(msg) {
msg._msgid = redUtil.generateId(); msg._msgid = redUtil.generateId();
} }
this.metric("receive",msg); this.metric("receive",msg);
try {
this.emit("input",msg); this.emit("input",msg);
} catch(err) {
this.error(err,msg);
}
}; };
function log_helper(self, level, msg) { function log_helper(self, level, msg) {

View File

@ -23,6 +23,7 @@ var Subflow;
var Log; var Log;
var nodeCloseTimeout = 15000; var nodeCloseTimeout = 15000;
var asyncMessageDelivery = true;
/** /**
* This class represents a flow within the runtime. It is responsible for * This class represents a flow within the runtime. It is responsible for
@ -125,6 +126,7 @@ class Flow {
var id; var id;
this.catchNodes = []; this.catchNodes = [];
this.statusNodes = []; this.statusNodes = [];
this.completeNodeMap = {};
var configNodes = Object.keys(this.flow.configs); var configNodes = Object.keys(this.flow.configs);
var configNodeAttempts = {}; var configNodeAttempts = {};
@ -228,7 +230,7 @@ class Flow {
this.trace(" id | type | alias"); this.trace(" id | type | alias");
this.trace("------------------|--------------|-----------------"); this.trace("------------------|--------------|-----------------");
} }
// Build the map of catch/status nodes. // Build the map of catch/status/complete nodes.
for (id in this.activeNodes) { for (id in this.activeNodes) {
if (this.activeNodes.hasOwnProperty(id)) { if (this.activeNodes.hasOwnProperty(id)) {
node = this.activeNodes[id]; node = this.activeNodes[id];
@ -237,6 +239,13 @@ class Flow {
this.catchNodes.push(node); this.catchNodes.push(node);
} else if (node.type === "status") { } else if (node.type === "status") {
this.statusNodes.push(node); this.statusNodes.push(node);
} else if (node.type === "complete") {
if (node.scope) {
node.scope.forEach(id => {
this.completeNodeMap[id] = this.completeNodeMap[id] || [];
this.completeNodeMap[id].push(node);
})
}
} }
} }
} }
@ -516,6 +525,20 @@ class Flow {
return handled; 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() { dump() {
console.log("==================") console.log("==================")
console.log(this.TYPE, this.id); console.log(this.TYPE, this.id);
@ -562,6 +585,7 @@ function stopNode(node,removed) {
module.exports = { module.exports = {
init: function(runtime) { init: function(runtime) {
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000; nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery
Log = runtime.log; Log = runtime.log;
Subflow = require("./Subflow"); Subflow = require("./Subflow");
Subflow.init(runtime); Subflow.init(runtime);

View File

@ -729,6 +729,10 @@ module.exports = {
updateFlow: updateFlow, updateFlow: updateFlow,
removeFlow: removeFlow, removeFlow: removeFlow,
disableFlow:null, disableFlow:null,
enableFlow:null enableFlow:null,
isDeliveryModeAsync: function() {
// If settings is null, this is likely being run by unit tests
return !settings || !settings.runtimeSyncDelivery
}
}; };

View File

@ -268,6 +268,7 @@ describe('function node', function() {
helper.load(functionNode, flow, function() { helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
@ -283,6 +284,7 @@ describe('function node', function() {
} catch(err) { } catch(err) {
done(err); done(err);
} }
},50);
}); });
}); });
@ -291,7 +293,8 @@ describe('function node', function() {
helper.load(functionNode, flow, function() { helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
helper.getNode("n1").close(); setTimeout(function() {
n1.close().then(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
@ -308,6 +311,8 @@ describe('function node', function() {
done(err); done(err);
} }
}); });
},1500);
});
}); });
it('should set node context', function(done) { it('should set node context', function(done) {
@ -532,6 +537,7 @@ describe('function node', function() {
}); });
function checkCallbackError(name, done) { function checkCallbackError(name, done) {
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
@ -548,6 +554,7 @@ describe('function node', function() {
catch (e) { catch (e) {
done(e); done(e);
} }
},50);
} }
it('should get persistable node context (w/o callback)', function(done) { 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"}); n1.receive({payload:"foo",topic: "bar"});
} else { } else {
msg.should.have.property('payload', "hello"); msg.should.have.property('payload', "hello");
delete process.env._TEST_FOO_;
done(); done();
} }
} catch(err) { } catch(err) {
done(err);
} finally {
delete process.env._TEST_FOO_; delete process.env._TEST_FOO_;
done(err);
} }
}); });
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
@ -1285,6 +1292,7 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
@ -1300,6 +1308,7 @@ describe('function node', function() {
} catch (err) { } catch (err) {
done(err); done(err);
} }
},50);
}); });
}); });
it('should log a Debug Message', function (done) { it('should log a Debug Message', function (done) {
@ -1307,6 +1316,7 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
@ -1322,6 +1332,7 @@ describe('function node', function() {
} catch (err) { } catch (err) {
done(err); done(err);
} }
},50);
}); });
}); });
it('should log a Trace Message', function (done) { it('should log a Trace Message', function (done) {
@ -1329,6 +1340,7 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
@ -1344,6 +1356,7 @@ describe('function node', function() {
} catch (err) { } catch (err) {
done(err); done(err);
} }
},50);
}); });
}); });
it('should log a Warning Message', function (done) { it('should log a Warning Message', function (done) {
@ -1351,6 +1364,7 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
@ -1366,6 +1380,7 @@ describe('function node', function() {
} catch (err) { } catch (err) {
done(err); done(err);
} }
},50);
}); });
}); });
it('should log an Error Message', function (done) { it('should log an Error Message', function (done) {
@ -1373,6 +1388,7 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
setTimeout(function() {
try { try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
@ -1388,6 +1404,7 @@ describe('function node', function() {
} catch (err) { } catch (err) {
done(err); done(err);
} }
},50);
}); });
}); });
}); });

View File

@ -475,7 +475,11 @@ describe('SORT node', function() {
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(sortNode, flow, function() { helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var msg = { payload: 0,
parts: { id: "X", index: 0, count: 2} };
n1.receive(msg);
setTimeout(function() { setTimeout(function() {
n1.close().then(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "sort"; return evt[0].type == "sort";
}); });
@ -484,11 +488,8 @@ describe('SORT node', function() {
evt.should.have.property('type', "sort"); evt.should.have.property('type', "sort");
evt.should.have.property('msg', "sort.clear"); evt.should.have.property('msg', "sort.clear");
done(); done();
});
}, 150); }, 150);
var msg = { payload: 0,
parts: { id: "X", index: 0, count: 2} };
n1.receive(msg);
n1.close();
}); });
}); });

View File

@ -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 htmlNode = require("nr-test-utils").require("@node-red/nodes/core/parsers/70-HTML.js");
var helper = require("node-red-node-test-helper"); 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 resourcesDir = __dirname+ path.sep + ".." + path.sep + ".." + path.sep + ".." + path.sep + "resources" + path.sep;
var file = path.join(resourcesDir, "70-HTML-test-file.html"); var file = path.join(resourcesDir, "70-HTML-test-file.html");
@ -228,6 +228,8 @@ describe('html node', function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n1.receive({payload:null,topic: "bar"}); n1.receive({payload:null,topic: "bar"});
setTimeout(function() {
try {
helper.log().called.should.be.true(); helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "html"; return evt[0].type == "html";
@ -238,6 +240,8 @@ describe('html node', function() {
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }

View File

@ -148,6 +148,8 @@ describe('JSON node', function() {
var jn1 = helper.getNode("jn1"); var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2"); var jn2 = helper.getNode("jn2");
jn1.receive({payload:'foo',topic: "bar"}); jn1.receive({payload:'foo',topic: "bar"});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json"; return evt[0].type == "json";
}); });
@ -156,6 +158,8 @@ describe('JSON node', function() {
logEvents[0][0].msg.should.startWith("Unexpected token o"); logEvents[0][0].msg.should.startWith("Unexpected token o");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},20);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -378,6 +382,8 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var obj = {"number": "foo", "string": 3}; var obj = {"number": "foo", "string": 3};
jn1.receive({payload:obj, schema:schema}); jn1.receive({payload:obj, schema:schema});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json"; return evt[0].type == "json";
}); });
@ -386,6 +392,8 @@ describe('JSON node', function() {
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); 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); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -402,6 +410,8 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var obj = {"number": "foo", "string": 3}; var obj = {"number": "foo", "string": 3};
jn1.receive({payload:obj, schema:schema}); jn1.receive({payload:obj, schema:schema});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json"; return evt[0].type == "json";
}); });
@ -410,6 +420,8 @@ describe('JSON node', function() {
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); 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); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -426,6 +438,8 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var jsonString = '{"number":"Hello","string":3}'; var jsonString = '{"number":"Hello","string":3}';
jn1.receive({payload:jsonString, schema:schema}); jn1.receive({payload:jsonString, schema:schema});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json"; return evt[0].type == "json";
}); });
@ -434,6 +448,8 @@ describe('JSON node', function() {
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); 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); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -450,6 +466,8 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var jsonString = '{"number":"Hello","string":3}'; var jsonString = '{"number":"Hello","string":3}';
jn1.receive({payload:jsonString, schema:schema}); jn1.receive({payload:jsonString, schema:schema});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json"; return evt[0].type == "json";
}); });
@ -458,6 +476,8 @@ describe('JSON node', function() {
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); 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); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -474,6 +494,8 @@ describe('JSON node', function() {
var schema = "garbage"; var schema = "garbage";
var obj = {"number": "foo", "string": 3}; var obj = {"number": "foo", "string": 3};
jn1.receive({payload:obj, schema:schema}); jn1.receive({payload:obj, schema:schema});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json"; return evt[0].type == "json";
}); });
@ -482,6 +504,8 @@ describe('JSON node', function() {
logEvents[0][0].msg.should.equal("json.errors.schema-error-compile"); logEvents[0][0].msg.should.equal("json.errors.schema-error-compile");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }

View File

@ -130,6 +130,8 @@ describe('YAML node', function() {
var yn1 = helper.getNode("yn1"); var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2"); var yn2 = helper.getNode("yn2");
yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"}); yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"});
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) { var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "yaml"; return evt[0].type == "yaml";
}); });
@ -138,6 +140,8 @@ describe('YAML node', function() {
logEvents[0][0].msg.should.startWith("end of the stream"); logEvents[0][0].msg.should.startWith("end of the stream");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }

View File

@ -156,10 +156,50 @@ describe('Node', function() {
throw new Error("test error"); throw new Error("test error");
}); });
n.receive(message); n.receive(message);
setTimeout(function() {
n.error.called.should.be.true(); n.error.called.should.be.true();
n.error.firstCall.args[1].should.equal(message); n.error.firstCall.args[1].should.equal(message);
done(); 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 n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
var message = {payload:"hello world"}; var message = {payload:"hello world"};
var messageReceived = false;
n2.on('input',function(msg) { n2.on('input',function(msg) {
// msg equals message, and is not a new copy // msg equals message, and is not a new copy
messageReceived = true;
should.deepEqual(msg,message); should.deepEqual(msg,message);
should.strictEqual(msg,message); should.strictEqual(msg,message);
done(); done();
}); });
n1.send(message); 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) { 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) { it("logs the uuid for all messages sent", function(done) {
var logHandler = { var logHandler = {
msgIds:[],
messagesSent: 0, messagesSent: 0,
emit: function(event, msg) { emit: function(event, msg) {
if (msg.event == "node.abc.send" && msg.level == Log.METRIC) { if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
this.messagesSent++; this.messagesSent++;
this.msgIds.push(msg.msgid);
(typeof msg.msgid).should.not.be.equal("undefined"); (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 receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'});
var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'}); var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'});
sender.send({"some": "message"}); 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)
}) })
}); });

View File

@ -122,6 +122,7 @@ describe('Flow', function() {
currentNodes[node.id] = node; currentNodes[node.id] = node;
this.on('input',function(msg) { this.on('input',function(msg) {
node.handled++; node.handled++;
msg.handled = node.handled;
node.messages.push(msg); node.messages.push(msg);
node.send(msg); node.send(msg);
}); });
@ -136,12 +137,42 @@ describe('Flow', function() {
} }
util.inherits(TestAsyncNode,Node); 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() { before(function() {
getType = sinon.stub(typeRegistry,"get",function(type) { getType = sinon.stub(typeRegistry,"get",function(type) {
if (type=="test") { if (type=="test") {
return TestNode; return TestNode;
} else if (type=="testError"){ } else if (type=="testError"){
return TestErrorNode; return TestErrorNode;
} else if (type=="testDone"){
return TestDoneNode;
} else { } else {
return TestAsyncNode; return TestAsyncNode;
} }
@ -189,9 +220,7 @@ describe('Flow', function() {
currentNodes["2"].should.have.a.property("handled",0); currentNodes["2"].should.have.a.property("handled",0);
currentNodes["3"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",0);
currentNodes["3"].on("input", function() {
currentNodes["1"].receive({payload:"test"});
currentNodes["1"].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
currentNodes["2"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1);
@ -212,6 +241,8 @@ describe('Flow', function() {
} }
}); });
}); });
currentNodes["1"].receive({payload:"test"});
});
it("instantiates config nodes in the right order",function(done) { it("instantiates config nodes in the right order",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -350,6 +381,7 @@ describe('Flow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
// Message doesn't reach 3 as 2 is disabled // Message doesn't reach 3 as 2 is disabled
currentNodes["3"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",0);
@ -369,6 +401,7 @@ describe('Flow', function() {
done(err); done(err);
} }
}); });
},50);
}); });
}); });
@ -551,6 +584,8 @@ describe('Flow', function() {
flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status",random:"otherProperty"}); flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status",random:"otherProperty"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -576,6 +611,7 @@ describe('Flow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50)
}); });
it("passes a status event to the adjacent scoped status node ",function(done) { it("passes a status event to the adjacent scoped status node ",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -596,6 +632,7 @@ describe('Flow', function() {
flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status"}); flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",0); currentNodes["sn"].should.have.a.property("handled",0);
currentNodes["sn2"].should.have.a.property("handled",1); currentNodes["sn2"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn2"].messages[0]; var statusMessage = currentNodes["sn2"].messages[0];
@ -611,6 +648,7 @@ describe('Flow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
}); });
@ -636,6 +674,7 @@ describe('Flow', function() {
flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"}); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -663,6 +702,7 @@ describe('Flow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("passes an error event to the adjacent scoped catch node ",function(done) { it("passes an error event to the adjacent scoped catch node ",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -684,6 +724,7 @@ describe('Flow', function() {
flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"}); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",0); currentNodes["sn"].should.have.a.property("handled",0);
currentNodes["sn2"].should.have.a.property("handled",1); currentNodes["sn2"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn2"].messages[0]; var statusMessage = currentNodes["sn2"].messages[0];
@ -701,7 +742,7 @@ describe('Flow', function() {
// Inject error that sn1/2 will ignore - so should get picked up by sn3 // 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"}); flow.handleError(config.flows["t1"].nodes["3"],"my-error-2",{a:"foo-2"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",0); currentNodes["sn"].should.have.a.property("handled",0);
currentNodes["sn2"].should.have.a.property("handled",1); currentNodes["sn2"].should.have.a.property("handled",1);
currentNodes["sn3"].should.have.a.property("handled",1); currentNodes["sn3"].should.have.a.property("handled",1);
@ -717,6 +758,8 @@ describe('Flow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
},50);
}); });
it("moves any existing error object sideways",function(done){ it("moves any existing error object sideways",function(done){
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -733,7 +776,7 @@ describe('Flow', function() {
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo",error:"existing"}); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo",error:"existing"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -748,7 +791,39 @@ describe('Flow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("prevents an error looping more than 10 times",function(){}); 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);
});
});
}); });

View File

@ -271,6 +271,7 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1); // currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1); // currentNodes[sfInstanceId2].should.have.a.property("handled",1);
@ -297,6 +298,7 @@ describe('Subflow', function() {
// // stoppedNodes.should.have.a.property(sfConfigId); // // stoppedNodes.should.have.a.property(sfConfigId);
done(); done();
}); });
},50);
}); });
it("instantiates a subflow inside a subflow and stops it",function(done) { it("instantiates a subflow inside a subflow and stops it",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -322,17 +324,14 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1);
flow.stop().then(function() { flow.stop().then(function() {
Object.keys(currentNodes).should.have.length(0); Object.keys(currentNodes).should.have.length(0);
done(); done();
}); });
},50);
}); });
it("rewires a subflow node on update/start",function(done){ it("rewires a subflow node on update/start",function(done){
@ -369,6 +368,7 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId].should.have.a.property("handled",1); // currentNodes[sfInstanceId].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1); // currentNodes[sfInstanceId2].should.have.a.property("handled",1);
@ -379,6 +379,7 @@ describe('Subflow', function() {
flow.start(diff) flow.start(diff)
currentNodes["1"].receive({payload:"test2"}); currentNodes["1"].receive({payload:"test2"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",2); currentNodes["1"].should.have.a.property("handled",2);
// currentNodes[sfInstanceId].should.have.a.property("handled",2); // currentNodes[sfInstanceId].should.have.a.property("handled",2);
@ -390,6 +391,8 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
},50);
}); });
}); });
describe('#stop', function() { describe('#stop', function() {
@ -436,7 +439,7 @@ describe('Subflow', function() {
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -447,9 +450,9 @@ describe('Subflow', function() {
statusMessage.status.source.should.have.a.property("name","test-status-node"); statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -472,6 +475,7 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
parentFlowStatusCalled.should.be.false(); parentFlowStatusCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
@ -487,6 +491,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50);
}); });
}); });
@ -517,6 +522,7 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test-payload"}); activeNodes["1"].receive({payload:"test-payload"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -530,6 +536,7 @@ describe('Subflow', 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) { 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([ var config = flowUtils.parseConfig([
@ -557,6 +564,7 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:{text:"payload-obj"}}); activeNodes["1"].receive({payload:{text:"payload-obj"}});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -570,6 +578,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -597,6 +606,7 @@ describe('Subflow', function() {
activeNodes["1"].receive({status:{text:"status-obj"}}); activeNodes["1"].receive({status:{text:"status-obj"}});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -610,6 +620,7 @@ describe('Subflow', function() {
done(); done();
}); });
},50);
}); });
it("does not emit a regular status event if it contains a subflow-status node", function(done) { it("does not emit a regular status event if it contains a subflow-status node", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -666,6 +677,7 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
@ -678,6 +690,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -699,6 +712,7 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
parentFlowErrorCalled.should.be.false(); parentFlowErrorCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
@ -713,6 +727,7 @@ describe('Subflow', function() {
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
}); });
@ -756,11 +771,13 @@ describe('Subflow', function() {
process.env["__KEY__"] = "__VAL__"; process.env["__KEY__"] = "__VAL__";
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL__"); currentNodes["3"].should.have.a.property("received", "__VAL__");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("can access subflow env var", function(done) { it("can access subflow env var", function(done) {
@ -794,11 +811,13 @@ describe('Subflow', function() {
setEnv(testenv_node, "__KEY__", "__VAL1__"); setEnv(testenv_node, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL1__"); currentNodes["3"].should.have.a.property("received", "__VAL1__");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("can access nested subflow env var", function(done) { it("can access nested subflow env var", function(done) {
@ -840,19 +859,25 @@ describe('Subflow', function() {
process.env["__KEY__"] = "__VAL0__"; process.env["__KEY__"] = "__VAL0__";
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL0__"); currentNodes["3"].should.have.a.property("received", "__VAL0__");
setEnv(node_sf1_1, "__KEY__", "__VAL1__"); setEnv(node_sf1_1, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL1__"); currentNodes["3"].should.have.a.property("received", "__VAL1__");
setEnv(node_sf2_1, "__KEY__", "__VAL2__"); setEnv(node_sf2_1, "__KEY__", "__VAL2__");
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL2__"); currentNodes["3"].should.have.a.property("received", "__VAL2__");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
},50);
},50);
}); });
}); });