Compare commits

...

29 Commits

Author SHA1 Message Date
Hiroyasu Nishiyama
6fa8b7f5f1 fix persistable context handling of sort node & existing error in testcases 2018-09-05 16:04:12 +01:00
Nick O'Leary
a2d03c14ae Update CHANGELOG for 0.19.3 2018-09-05 09:49:09 +01:00
Dave Conway-Jones
c667a0e74c debug node - show ring at start until first msg 2018-09-05 09:45:34 +01:00
Dave Conway-Jones
8123828113 improve split node accumulation test to include early complete 2018-09-05 08:36:56 +01:00
Dave Conway-Jones
72b8dbb45b Split node - fix complete to send msg for k/v object
and update info to try to clarify.
2018-09-04 22:54:28 +01:00
Dave Conway-Jones
7703875740 tidy split node merged object key typed input 2018-09-04 17:41:14 +01:00
Nick O'Leary
6442bb8a13 Set the JavaScript editor to full-screen 2018-09-04 13:30:06 +01:00
Nick O'Leary
f29d7c9252 Fixup localfilesystem registry test 2018-09-04 11:37:04 +01:00
Nick O'Leary
2f7f53ed96 Filter global modules installed locally
If a module is found both locally and globally installed, the local
copy will take precedence. This will allow a user to upgrade a
node module that they may not otherwise be able to touch
2018-09-04 11:26:05 +01:00
Nick O'Leary
94031a52a5 Add svg to permitted icon extension list 2018-08-31 21:02:09 +01:00
Dave Conway-Jones
d67f91e7ed debug node - indicate status all the time if selected to do so 2018-08-31 16:31:08 +01:00
Nick O'Leary
f37697c4fb Merge pull request #1870 from natcl/json-schema
JSON node: fix schema validation for obj -> obj or str -> str
2018-08-31 11:25:31 +01:00
Dave Conway-Jones
69448c7329 pi nodes - increase test coverage slightly 2018-08-30 20:54:03 +01:00
Dave Conway-Jones
8e9815fb91 TCP-request node - only write payload
to close #1869
2018-08-30 20:47:39 +01:00
Nathanaël Lécaudé
4cdd7978cf JSON schema: remove unused function 2018-08-29 13:40:37 -04:00
Nathanaël Lécaudé
40d81358f4 JSON schema: perform validation when obj -> obj or str -> str 2018-08-29 13:36:28 -04:00
Nathanaël Lécaudé
c7b62aed91 JSON schema: add draft-06 support (via $schema keyword) 2018-08-29 12:20:04 -04:00
Stefan Machmeier
c0e7d6d826 Mqtt proxy configuration for websocket connection, #1651. 2018-08-29 09:53:07 +01:00
Nick O'Leary
f809377de8 Merge pull request #1854 from kazuhitoyokoi/master-fixtypointestcase4functionnode
Fix typo in test case
2018-08-28 21:19:48 +01:00
Nick O'Leary
9767bd9697 Merge pull request #1860 from node-red-hitachi/uitest-refactoring
Refactored UI testing code following a design note
2018-08-28 21:06:36 +01:00
Nick O'Leary
3a55528552 Merge pull request #1861 from SPIRIT-21/master
Allows MQTT Shared Subscriptions for MQTT-In core node
2018-08-28 21:04:53 +01:00
Nick O'Leary
56197ffe3a Merge pull request #1851 from t4skforce/patch-1
fixed some linting issues
2018-08-28 20:58:51 +01:00
Nick O'Leary
0f0d0c046c Merge pull request #1853 from node-red-hitachi/fix-icon-spec-for-typedInput
Fix use of HTML tag or CSS class specification as icon of TypedInput
2018-08-28 20:44:06 +01:00
nakanishi
ecc4973645 Fixed the problems that were caused by timing issue 2018-08-27 17:34:04 +09:00
Lars Oldiges
df161ce672 Allows MQTT Shared Subscriptions for MQTT-In core node 2018-08-22 13:20:49 +02:00
nakanishi
72fe30892e Refactor UI testing code following a design note 2018-08-22 14:36:30 +09:00
Kazuhito Yokoi
695873d35a Fix typo in test case for function node 2018-08-06 21:14:53 +09:00
Hiroyasu Nishiyama
15da19dcea fix use of HTML tag or CSS class specification as icon of typedInput 2018-08-06 10:28:02 +09:00
t4skforce
d5bdc1600b fixed some linting issues
* added some semicolons
* removed double parsing of ```err.stack``` into ```var stack```
2018-07-31 22:54:15 +02:00
37 changed files with 527 additions and 249 deletions

View File

@@ -1,3 +1,19 @@
#### 0.19.3: Maintenance Release
- Split node - fix complete to send msg for k/v object
- Remove unused Join node merged object key typed input
- Set the JavaScript editor to full-screen
- Filter global modules installed locally
- Add svg to permitted icon extension list
- Debug node - indicate status all the time if selected to do so
- pi nodes - increase test coverage slightly
- TCP-request node - only write payload
- JSON schema: perform validation when obj -> obj or str -> str
- JSON schema: add draft-06 support (via $schema keyword)
- Mqtt proxy configuration for websocket connection, #1651.
- Allows MQTT Shared Subscriptions for MQTT-In core node
- Fix use of HTML tag or CSS class specification as icon of typedInput
#### 0.19.2: Maintenance Release
- Ensure node default colour is used if palette.theme has no match

View File

@@ -522,10 +522,18 @@
this.selectLabel.empty();
var image;
if (opt.icon) {
image = new Image();
image.name = opt.icon;
image.src = opt.icon;
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(this.selectLabel);
}
else if (opt.icon.indexOf("/") !== -1) {
image = new Image();
image.name = opt.icon;
image.src = opt.icon;
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
}
else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel);
}
} else {
this.selectLabel.text(opt.label);
}

View File

@@ -32,7 +32,7 @@ RED.editor.types._js = (function() {
var trayOptions = {
title: options.title,
width: "inherit",
width: options.width||"inherit",
buttons: [
{
id: "node-dialog-cancel",

View File

@@ -19,7 +19,11 @@ module.exports = function(RED) {
if (this.tosidebar === undefined) { this.tosidebar = true; }
this.severity = n.severity || 40;
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
this.status({});
if (this.tostatus) {
this.oldStatus = {fill:"grey", shape:"ring"};
this.status(this.oldStatus);
}
else { this.status({}); }
var node = this;
var levels = {
@@ -122,12 +126,12 @@ module.exports = function(RED) {
if (state === "enable") {
node.active = true;
res.sendStatus(200);
if (node.tostatus) { node.status({}); }
if (node.tostatus) { node.status({fill:"grey", shape:"dot"}); }
} else if (state === "disable") {
node.active = false;
res.sendStatus(201);
if (node.tostatus && node.hasOwnProperty("oldStatus")) {
node.oldStatus.shape = "ring";
node.oldStatus.shape = "dot";
node.status(node.oldStatus);
}
} else {

View File

@@ -126,6 +126,7 @@
var value = that.editor.getValue();
RED.editor.editJavaScript({
value: value,
width: "Infinity",
cursor: that.editor.getCursorPosition(),
complete: function(v,cursor) {
that.editor.setValue(v, -1);

View File

@@ -42,7 +42,7 @@ module.exports = function(RED) {
if (type === 'object') {
type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
}
node.error(RED._("function.error.non-message-returned",{ type: type }))
node.error(RED._("function.error.non-message-returned",{ type: type }));
}
}
}
@@ -203,9 +203,9 @@ module.exports = function(RED) {
if (util.hasOwnProperty('promisify')) {
sandbox.setTimeout[util.promisify.custom] = function(after, value) {
return new Promise(function(resolve, reject) {
sandbox.setTimeout(function(){ resolve(value) }, after);
sandbox.setTimeout(function(){ resolve(value); }, after);
});
}
};
}
var context = vm.createContext(sandbox);
try {
@@ -241,7 +241,6 @@ module.exports = function(RED) {
var line = 0;
var errorMessage;
var stack = err.stack.split(/\r?\n/);
if (stack.length > 0) {
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
@@ -265,13 +264,13 @@ module.exports = function(RED) {
});
this.on("close", function() {
while (node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop())
clearTimeout(node.outstandingTimers.pop());
}
while (node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop())
clearInterval(node.outstandingIntervals.pop());
}
this.status({});
})
});
} catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
@@ -280,4 +279,4 @@ module.exports = function(RED) {
}
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");
}
};

View File

@@ -19,11 +19,26 @@ module.exports = function(RED) {
var mqtt = require("mqtt");
var util = require("util");
var isUtf8 = require('is-utf8');
var HttpsProxyAgent = require('https-proxy-agent');
var url = require('url');
function matchTopic(ts,t) {
if (ts == "#") {
return true;
}
/* The following allows shared subscriptions (as in MQTT v5)
http://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html#_Toc514345522
4.8.2 describes shares like:
$share/{ShareName}/{filter}
$share is a literal string that marks the Topic Filter as being a Shared Subscription Topic Filter.
{ShareName} is a character string that does not include "/", "+" or "#"
{filter} The remainder of the string has the same syntax and semantics as a Topic Filter in a non-shared subscription. Refer to section 4.7.
*/
else if(ts.startsWith("$share")){
ts = ts.replace(/^\$share\/[^#+/]+\/(.*)/g,"$1");
}
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
return re.test(t);
}
@@ -96,12 +111,29 @@ module.exports = function(RED) {
if (typeof this.cleansession === 'undefined') {
this.cleansession = true;
}
var prox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; }
// Create the URL to pass in to the MQTT.js library
if (this.brokerurl === "") {
// if the broker may be ws:// or wss:// or even tcp://
if (this.broker.indexOf("://") > -1) {
this.brokerurl = this.broker;
// Only for ws or wss, check if proxy env var for additional configuration
if (this.brokerurl.indexOf("wss://") > -1 || this.brokerurl.indexOf("ws://") > -1 )
// check if proxy is set in env
if (prox) {
var parsedUrl = url.parse(this.brokerurl);
var proxyOpts = url.parse(prox);
// true for wss
proxyOpts.secureEndpoint = parsedUrl.protocol ? parsedUrl.protocol === 'wss:' : true;
// Set Agent for wsOption in MQTT
var agent = new HttpsProxyAgent(proxyOpts);
this.options.wsOptions = {
agent: agent
}
}
} else {
// construct the std mqtt:// url
if (this.usetls) {
@@ -435,4 +467,4 @@ module.exports = function(RED) {
}
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
};
};

View File

@@ -33,9 +33,7 @@ module.exports = function(RED) {
*/
const enqueue = (queue, item) => {
// drop msgs from front of queue if size is going to be exceeded
if (queue.size() === msgQueueSize) {
queue.shift();
}
if (queue.size() === msgQueueSize) { queue.shift(); }
queue.push(item);
return queue;
};
@@ -646,7 +644,7 @@ module.exports = function(RED) {
}
else if (!clients[connection_id].connecting && clients[connection_id].connected) {
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue));
clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue).payload);
}
}
});

View File

@@ -295,7 +295,8 @@
For object outputs, once this count has been reached, the node can be configured to send a message for each subsequent message
received.</p>
<p>A <i>timeout</i> can be set to trigger sending the new message using whatever has been received so far.</p>
<p>If a message is received with the <b>msg.complete</b> property set, the output message is sent.</p>
<p>If a message is received with the <b>msg.complete</b> property set, the output message is finalised and sent.
This resets any part counts.</p>
<h4>Reduce Sequence mode</h4>
<p>When configured to join in reduce mode, an expression is applied to each
@@ -439,10 +440,7 @@
$("#node-input-joiner").typedInput({
default: 'str',
typeField: $("#node-input-joinerType"),
types:[
'str',
'bin'
]
types:['str', 'bin']
});
$("#node-input-property").typedInput({
@@ -451,7 +449,7 @@
});
$("#node-input-key").typedInput({
types:['msg', {value:"merge", label:"", hasValue:false}]
types:['msg']
});
$("#node-input-build").change();

View File

@@ -586,7 +586,10 @@ module.exports = function(RED) {
}
else {
if (msg.hasOwnProperty('complete')) {
completeSend(partId);
if (inflight[partId]) {
inflight[partId].msg.complete = msg.complete;
completeSend(partId);
}
}
else {
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
@@ -594,6 +597,7 @@ module.exports = function(RED) {
}
return;
}
if (!inflight.hasOwnProperty(partId)) {
if (payloadType === 'object' || payloadType === 'merged') {
inflight[partId] = {
@@ -604,19 +608,6 @@ module.exports = function(RED) {
msg:RED.util.cloneMessage(msg)
};
}
else if (node.accumulate === true) {
if (msg.hasOwnProperty("reset")) { delete inflight[partId]; }
inflight[partId] = inflight[partId] || {
currentCount:0,
payload:{},
targetCount:targetCount,
type:payloadType,
msg:RED.util.cloneMessage(msg)
}
if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') {
inflight[partId].payload = [];
}
}
else {
inflight[partId] = {
currentCount:0,

View File

@@ -196,9 +196,10 @@ module.exports = function(RED) {
}).catch(err => {
node.error(err,msg);
});
return;
}
var parts = msg.parts;
if (!parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
return;
}
var gid = parts.id;
@@ -242,7 +243,8 @@ module.exports = function(RED) {
delete pending[key];
}
}
pending_count = 0; })
pending_count = 0;
});
}
RED.nodes.registerType("sort", SortNode);

View File

@@ -19,6 +19,7 @@ module.exports = function(RED) {
const Ajv = require('ajv');
const ajv = new Ajv({allErrors: true, schemaId: 'auto'});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
function JSONNode(n) {
RED.nodes.createNode(this,n);
@@ -29,6 +30,7 @@ module.exports = function(RED) {
this.compiledSchema = null;
var node = this;
this.on("input", function(msg) {
var validate = false;
if (msg.schema) {
@@ -65,7 +67,17 @@ module.exports = function(RED) {
}
catch(e) { node.error(e.message,msg); }
} else {
node.send(msg);
// If node.action is str and value is str
if (validate) {
if (this.compiledSchema(JSON.parse(msg[node.property]))) {
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
}
} else {
node.send(msg);
}
}
}
else if (typeof value === "object") {
@@ -84,13 +96,22 @@ module.exports = function(RED) {
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
node.send(msg);
}
}
catch(e) { node.error(RED._("json.errors.dropped-error")); }
}
else { node.warn(RED._("json.errors.dropped-object")); }
} else {
node.send(msg);
// If node.action is obj and value is object
if (validate) {
if (this.compiledSchema(value)) {
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
}
} else {
node.send(msg);
}
}
}
else { node.warn(RED._("json.errors.dropped")); }

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.19.2",
"version": "0.19.3",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -42,13 +42,14 @@
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors": "2.8.4",
"cron": "1.3.0",
"cron": "1.4.1",
"denque": "1.3.0",
"express": "4.16.3",
"express-session": "1.15.6",
"fs-extra": "5.0.0",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1",
"i18next": "11.6.0",
"is-utf8": "0.2.1",
"js-yaml": "3.12.0",
@@ -56,9 +57,9 @@
"jsonata": "1.5.4",
"media-typer": "0.3.0",
"memorystore": "1.6.0",
"mqtt": "2.18.5",
"mqtt": "2.18.8",
"multer": "1.3.1",
"mustache": "2.3.1",
"mustache": "2.3.2",
"node-red-node-email": "0.1.*",
"node-red-node-feedparser": "^0.1.12",
"node-red-node-rbe": "0.2.*",
@@ -73,7 +74,7 @@
"request": "2.88.0",
"semver": "5.5.1",
"sentiment": "2.1.0",
"uglify-js": "3.4.8",
"uglify-js": "3.4.9",
"when": "3.7.8",
"ws": "1.1.5",
"xml2js": "0.4.19"
@@ -85,7 +86,7 @@
"chromedriver": "^2.41.0",
"grunt": "~1.0.3",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.2.0",
"grunt-cli": "~1.3.1",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",

View File

@@ -23,7 +23,7 @@ var i18n;
var settings;
var disableNodePathScan = false;
var iconFileExtensions = [".png", ".gif"];
var iconFileExtensions = [".png", ".gif", ".svg"];
function init(runtime) {
settings = runtime.settings;
@@ -290,6 +290,33 @@ function getNodeFiles(disableNodePathScan) {
if (!disableNodePathScan) {
var moduleFiles = scanTreeForNodesModules();
// Filter the module list to ignore global modules
// that have also been installed locally - allowing the user to
// update a module they may not otherwise be able to touch
moduleFiles.sort(function(A,B) {
if (A.local && !B.local) {
return -1
} else if (!A.local && B.local) {
return 1
}
return 0;
})
var knownModules = {};
moduleFiles = moduleFiles.filter(function(mod) {
var result;
if (!knownModules[mod.package.name]) {
knownModules[mod.package.name] = true;
result = true;
} else {
result = false;
}
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
log.debug(" "+mod.dir);
return result;
});
moduleFiles.forEach(function(moduleFile) {
var nodeModuleFiles = getModuleNodeFiles(moduleFile);
nodeList[moduleFile.package.name] = {

View File

@@ -24,7 +24,7 @@ function injectNode(id) {
util.inherits(injectNode, nodePage);
var payloadType = {
var payloadTypeList = {
"flow": 1,
"global": 2,
"str": 3,
@@ -36,54 +36,43 @@ var payloadType = {
"env": 9,
};
var timeType = {
var repeatTypeList = {
"none": 1,
"interval": 2,
"intervalBetweenTimes": 3,
"atASpecificTime": 4,
};
var timeType = {
"none": 1,
"interval": 2,
"intervalBetweenTimes": 3,
"atASpecificTime": 4,
};
var timeType = {
"none": 1,
"interval": 2,
"intervalBetweenTimes": 3,
"atASpecificTime": 4,
};
injectNode.prototype.setPayload = function(type, value) {
injectNode.prototype.setPayload = function(payloadType, payload) {
// Open a payload type list.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]');
// Select a payload type.
var payloadTypeXPath = '//*[@class="red-ui-typedInput-options"]/a[' + payloadType[type] + ']';
var payloadTypeXPath = '//*[@class="red-ui-typedInput-options"]/a[' + payloadTypeList[payloadType] + ']';
browser.clickWithWait(payloadTypeXPath);
if (value) {
if (payload) {
// Input a value.
browser.setValue('//*[@class="red-ui-typedInput-input"]/input', value);
browser.setValue('//*[@class="red-ui-typedInput-input"]/input', payload);
}
}
injectNode.prototype.setTopic = function(value) {
browser.setValue('#node-input-topic', value);
injectNode.prototype.setTopic = function(topic) {
browser.setValue('#node-input-topic', topic);
}
injectNode.prototype.setOnce = function(value) {
browser.clickWithWait('#node-input-once');
injectNode.prototype.setOnce = function(once) {
var isChecked = browser.isSelected('#node-input-once');
if (isChecked !== once) {
browser.clickWithWait('#node-input-once');
}
}
injectNode.prototype.setTimeType = function(type) {
var timeTypeXPath = '//*[@id="inject-time-type-select"]/option[' + timeType[type] + ']';
browser.clickWithWait(timeTypeXPath);
injectNode.prototype.setRepeat = function(repeatType) {
var repeatTypeXPath = '//*[@id="inject-time-type-select"]/option[' + repeatTypeList[repeatType] + ']';
browser.clickWithWait(repeatTypeXPath);
}
injectNode.prototype.setRepeat = function(sec) {
browser.setValue('#inject-time-interval-count', sec);
injectNode.prototype.setRepeatInterval = function(repeat) {
browser.setValue('#inject-time-interval-count', repeat);
}
module.exports = injectNode;

View File

@@ -24,22 +24,20 @@ function debugNode(id) {
util.inherits(debugNode, nodePage);
var target = {
"msg": 1,
"full": 2
};
debugNode.prototype.setTarget = function(type, value) {
debugNode.prototype.setOutput = function(complete) {
// Open a payload type list.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]/button');
// Select a payload type.
var xPath = '/html/body/div[11]/a[' + target[type] + ']';
browser.clickWithWait(xPath);
if (value) {
if (complete !== 'true') {
// Select the "msg" type.
browser.clickWithWait('/html/body/div[11]/a[1]');
// Input the path in msg.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input');
browser.keys(['Control', 'a', 'Control']);
browser.keys(['Delete']);
browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', value);
browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete);
} else {
// Select the "complete msg object" type.
browser.clickWithWait('/html/body/div[11]/a[2]');
}
}

View File

@@ -24,12 +24,17 @@ function functionNode(id) {
util.inherits(functionNode, nodePage);
functionNode.prototype.setCode = function(value) {
functionNode.prototype.setFunction = function(func) {
browser.click('#node-input-func-editor');
browser.keys(['Control', 'Home', 'Control']);
for (var i=0; i<value.length; i++) {
browser.keys([value.substr(i, 1)]);
for (var i = 0; i < func.length; i++) {
browser.keys([func.charAt(i)]);
}
// Delete the unnecessary code that ace editor does the autocompletion.
browser.keys(['Control', 'Shift', 'End', 'Shift', 'Control']);
browser.keys(['Delete']);
// Need to wait until ace editor correctly checks the syntax.
browser.pause(50);
}
module.exports = functionNode;

View File

@@ -24,29 +24,20 @@ function templateNode(id) {
util.inherits(templateNode, nodePage);
var syntaxType = {
"mustache": 1,
"plain": 2
};
templateNode.prototype.setSyntax = function(type) {
// Open a method type list.
browser.clickWithWait('#node-input-syntax');
// Select a method type.
var syntaxTypeXPath = '//*[@id="node-input-syntax"]/option[' + syntaxType[type] + ']';
browser.clickWithWait(syntaxTypeXPath);
templateNode.prototype.setSyntax = function(syntax) {
browser.selectWithWait('#node-input-syntax', syntax);
}
templateNode.prototype.setFormat = function(type) {
browser.selectByValue('#node-input-format', type);
templateNode.prototype.setFormat = function(format) {
browser.selectWithWait('#node-input-format', format);
}
templateNode.prototype.setTemplate = function(value) {
templateNode.prototype.setTemplate = function(template) {
browser.click('#node-input-template-editor');
browser.keys(['Control', 'a', 'Control']); // call twice to release the keys.
// Need to add a character one by one since some words such as 'Control' are treated as a special word.
for (var i=0; i<value.length; i++) {
browser.keys([value.charAt(i)]);
for (var i = 0; i < template.length; i++) {
browser.keys([template.charAt(i)]);
}
browser.keys(['Control', 'Shift', 'End', 'Shift', 'Control']);
browser.keys(['Delete']);

View File

@@ -22,30 +22,14 @@ function httpinNode(id) {
nodePage.call(this, id);
}
function setMethod(type) {
browser.selectByValue('#node-input-method', type);
}
util.inherits(httpinNode, nodePage);
var methodType = {
"get": 1,
"post": 2,
"put": 3,
"delete": 4,
"patch": 5,
};
httpinNode.prototype.setMethod = function(type) {
// Open a method type list.
browser.clickWithWait('#node-input-method');
// Select a method type.
var methodTypeXPath = '//*[@id="node-input-method"]/option[' + methodType[type] + ']';
browser.clickWithWait(methodTypeXPath);
httpinNode.prototype.setMethod = function(method) {
browser.selectWithWait('#node-input-method', method);
}
httpinNode.prototype.setUrl = function(value) {
browser.setValue('#node-input-url', value);
httpinNode.prototype.setUrl = function(url) {
browser.setValue('#node-input-url', url);
}
module.exports = httpinNode;

View File

@@ -24,36 +24,16 @@ function httpRequestNode(id) {
util.inherits(httpRequestNode, nodePage);
var methodType = {
"get": 1,
"post": 2,
"put": 3,
"delete": 4,
"setByMsgMethod": 5,
};
var retType = {
"txt": 1,
"bin": 2,
"obj": 3,
};
httpRequestNode.prototype.setUrl = function(value) {
browser.setValue('#node-input-url', value);
httpRequestNode.prototype.setUrl = function(url) {
browser.setValue('#node-input-url', url);
}
httpRequestNode.prototype.setMethod = function(type) {
// Open a method type list.
browser.clickWithWait('#node-input-method');
// Select a method type.
var methodTypeXPath = '//*[@id="node-input-method"]/option[' + methodType[type] + ']';
browser.clickWithWait(methodTypeXPath);
httpRequestNode.prototype.setMethod = function(method) {
browser.selectWithWait('#node-input-method', method);
}
httpRequestNode.prototype.setRet = function(type) {
browser.clickWithWait('#node-input-ret');
var retTypeXPath = '//*[@id="node-input-ret"]/option[' + retType[type] + ']';
browser.clickWithWait(retTypeXPath);
httpRequestNode.prototype.setReturn = function(ret) {
browser.selectWithWait('#node-input-ret', ret);
}
module.exports = httpRequestNode;

View File

@@ -24,13 +24,6 @@ function changeNode(id) {
util.inherits(changeNode, nodePage);
var tType = {
"set": 1,
"change": 2,
"delete": 3,
"move": 4,
};
var totType = {
"msg": 1,
"flow": 2,
@@ -42,6 +35,7 @@ var totType = {
"bin": 8,
"date": 9,
"jsonata": 10,
"env": 11,
};
var ptType = {
@@ -50,8 +44,8 @@ var ptType = {
"global": 3,
};
function setT(rule, index) {
browser.selectByValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', rule);
function setT(t, index) {
browser.selectWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', t);
}
// It is better to create a function whose input value is the object type in the future,

View File

@@ -24,8 +24,8 @@ function rangeNode(id) {
util.inherits(rangeNode, nodePage);
rangeNode.prototype.setAction = function(value) {
browser.selectByValue('#node-input-action', value);
rangeNode.prototype.setAction = function(action) {
browser.selectWithWait('#node-input-action', action);
}
rangeNode.prototype.setRange = function(minin, maxin, minout, maxout) {

View File

@@ -24,8 +24,8 @@ function htmlNode(id) {
util.inherits(htmlNode, nodePage);
htmlNode.prototype.setTag = function(value) {
browser.setValue('#node-input-tag', value);
htmlNode.prototype.setSelector = function(tag) {
browser.setValue('#node-input-tag', tag);
}
module.exports = htmlNode;

View File

@@ -31,13 +31,13 @@ var formatType = {
"stream": 4
};
fileinNode.prototype.setFilename = function(value) {
browser.setValue('#node-input-filename', value);
fileinNode.prototype.setFilename = function(filename) {
browser.setValue('#node-input-filename', filename);
}
fileinNode.prototype.setFormat = function(type) {
fileinNode.prototype.setOutput = function(format) {
browser.clickWithWait('#node-input-format');
var formatTypeXPath = '//*[@id="node-input-format"]/option[' + formatType[type] + ']';
var formatTypeXPath = '//*[@id="node-input-format"]/option[' + formatType[format] + ']';
browser.clickWithWait(formatTypeXPath);
}

View File

@@ -38,6 +38,12 @@ function init() {
var ret = browser.getText(selector);
return ret;
}, false);
browser.addCommand("selectWithWait", function(selector, value) {
browser.waitForVisible(selector, 5000);
var ret = browser.selectByValue(selector, value);
return ret;
}, false);
}
module.exports = {

View File

@@ -19,8 +19,8 @@ var should = require("should");
var fs = require('fs-extra');
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/workspace_page');
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var nodeWidth = 200;

View File

@@ -17,8 +17,8 @@
var should = require("should");
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/workspace_page');
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var nodeWidth = 200;
var nodeHeight = 100;
@@ -65,7 +65,7 @@ describe('cookbook', function() {
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello');
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.clickOk();
injectNode.connect(httpRequestNode);
@@ -105,7 +105,7 @@ describe('cookbook', function() {
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-query?name=Nick');
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.clickOk();
injectNode.connect(httpRequestNode);
@@ -145,7 +145,7 @@ describe('cookbook', function() {
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-param/Dave');
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.clickOk();
injectNode.connect(httpRequestNode);
@@ -185,12 +185,12 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 3, nodeHeight);
changeNode.edit();
changeNode.ruleSet("headers", "msg", "{\"user-agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\"}", "json");
changeNode.ruleSet("headers", "msg", '{"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}', "json");
changeNode.clickOk();
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-headers');
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.clickOk();
injectNode.connect(changeNode);
@@ -249,7 +249,7 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight * 2);
httpRequestNode.edit();
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-data');
httpRequestNode.clickOk();
@@ -280,7 +280,7 @@ describe('cookbook', function() {
templateNode.edit();
templateNode.setSyntax("mustache");
templateNode.setFormat("handlebars");
templateNode.setTemplate("{ \"Hello\": \"World\" }");
templateNode.setTemplate('{ "Hello": "World" }');
templateNode.clickOk();
changeNode.edit();
@@ -299,12 +299,12 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight);
httpRequestNode.edit();
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-json');
httpRequestNode.clickOk();
debugNode.edit();
debugNode.setTarget("msg", "headers");
debugNode.setOutput("headers");
debugNode.clickOk();
injectNode.connect(httpRequestNode);
@@ -332,7 +332,7 @@ describe('cookbook', function() {
fileinNode.edit();
fileinNode.setFilename("test/resources/file-in-node/test.txt");
fileinNode.setFormat("");
fileinNode.setOutput("");
fileinNode.clickOk();
changeNode.edit();
@@ -352,7 +352,7 @@ describe('cookbook', function() {
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-file');
httpRequestNode.setMethod("get");
httpRequestNode.setMethod("GET");
httpRequestNode.clickOk();
injectNode.connect(httpRequestNode);
@@ -396,7 +396,7 @@ describe('cookbook', function() {
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-raw');
httpRequestNode.setMethod("post");
httpRequestNode.setMethod("POST");
httpRequestNode.clickOk();
injectNode.connect(httpRequestNode);
@@ -440,12 +440,12 @@ describe('cookbook', function() {
injectNode.clickOk();
changeNode.edit();
changeNode.ruleSet("headers", "msg", "{\"content-type\":\"application/x-www-form-urlencoded\"}", "json");
changeNode.ruleSet("headers", "msg", '{"content-type":"application/x-www-form-urlencoded"}', "json");
changeNode.clickOk();
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-form');
httpRequestNode.setMethod("post");
httpRequestNode.setMethod("POST");
httpRequestNode.clickOk();
injectNode.connect(changeNode);
@@ -486,16 +486,16 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 3, nodeHeight);
injectNode.edit()
injectNode.setPayload("json", "{\"name\":\"Nick\"}");
injectNode.setPayload("json", '{"name":"Nick"}');
injectNode.clickOk();
changeNode.edit();
changeNode.ruleSet("headers", "msg", "{\"content-type\":\"application/json\"}", "json");
changeNode.ruleSet("headers", "msg", '{"content-type":"application/json"}', "json");
changeNode.clickOk();
httpRequestNode.edit();
httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/hello-json');
httpRequestNode.setMethod("post");
httpRequestNode.setMethod("POST");
httpRequestNode.clickOk();
injectNode.connect(changeNode);
@@ -531,13 +531,13 @@ describe('cookbook', function() {
httpinNodeFormat.clickOk();
functionNodeFormat.edit();
functionNodeFormat.setCode("msg.payload = JSON.stringify(msg.req.cookies,null,4);");
functionNodeFormat.setFunction("msg.payload = JSON.stringify(msg.req.cookies,null,4);\nreturn msg;");
functionNodeFormat.clickOk();
templateNode.edit();
templateNode.setSyntax("mustache");
templateNode.setFormat("handlebars");
templateNode.setTemplate("<html>\n<head></head>\n<body>\n<h1>Cookies</h1>\n<p></p><a href=\"hello-cookie/add\">Add a cookie</a> &bull; <a href=\"hello-cookie/clear\">Clear cookies</a></p>\n<pre>{{ payload }}</pre>\n</body>\n</html>");
templateNode.setTemplate('<html>\n<head></head>\n<body>\n<h1>Cookies</h1>\n<p></p><a href="hello-cookie/add">Add a cookie</a> &bull; <a href="hello-cookie/clear">Clear cookies</a></p>\n<pre>{{ payload }}</pre>\n</body>\n</html>');
templateNode.clickOk();
httpinNodeFormat.connect(functionNodeFormat);
@@ -550,7 +550,7 @@ describe('cookbook', function() {
httpinNodeAdd.clickOk();
functionNodeAdd.edit();
functionNodeAdd.setCode("msg.cookies = { };\n msg.cookies[\"demo-\"+(Math.floor(Math.random()*1000))] = Date.now();");
functionNodeAdd.setFunction('msg.cookies = { };\n msg.cookies["demo-"+(Math.floor(Math.random()*1000))] = Date.now();\nreturn msg;');
functionNodeAdd.clickOk();
changeNode.edit();
@@ -571,7 +571,7 @@ describe('cookbook', function() {
httpinNodeClear.clickOk();
functionNodeClear.edit();
functionNodeClear.setCode("var cookieNames = Object.keys(msg.req.cookies).filter(function(cookieName) { return /^demo-/.test(cookieName);});\nmsg.cookies = {};\n\ncookieNames.forEach(function(cookieName) {\n msg.cookies[cookieName] = null;\n});\n\n");
functionNodeClear.setFunction("var cookieNames = Object.keys(msg.req.cookies).filter(function(cookieName) { return /^demo-/.test(cookieName);});\nmsg.cookies = {};\n\ncookieNames.forEach(function(cookieName) {\n msg.cookies[cookieName] = null;\n});\nreturn msg;\n");
functionNodeClear.clickOk();
httpinNodeClear.connect(functionNodeClear);

View File

@@ -19,8 +19,8 @@ var should = require("should");
var fs = require('fs-extra');
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/workspace_page');
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var specUtil = require('../../pageobjects/util/spec_util_page');
var nodeWidth = 200;
@@ -168,8 +168,8 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 2);
injectNode.edit();
injectNode.setTimeType("interval");
injectNode.setRepeat(1);
injectNode.setRepeat("interval");
injectNode.setRepeatInterval(1);
injectNode.clickOk();
injectNode.connect(debugNode);
@@ -196,12 +196,12 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 3);
httpRequetNode.edit();
httpRequetNode.setMethod("get");
httpRequetNode.setMethod("GET");
httpRequetNode.setUrl(helper.url());
httpRequetNode.clickOk();
htmlNode.edit();
htmlNode.setTag("title");
htmlNode.setSelector("title");
htmlNode.clickOk();
injectNode.connect(httpRequetNode);
@@ -336,14 +336,14 @@ describe('cookbook', function() {
changeNodeSetPost.clickOk();
httpRequetNode.edit();
httpRequetNode.setMethod("get");
httpRequetNode.setMethod("GET");
var url = helper.url() + httpNodeRoot + "/{{post}}";
httpRequetNode.setUrl(url);
httpRequetNode.setRet("obj");
httpRequetNode.setReturn("obj");
httpRequetNode.clickOk();
debugNode.edit();
debugNode.setTarget("msg", "payload.title");
debugNode.setOutput("payload.title");
debugNode.clickOk();
injectNode.connect(changeNodeSetPost);
@@ -364,11 +364,11 @@ describe('cookbook', function() {
templateNode.edit();
templateNode.setSyntax("mustache");
templateNode.setFormat("handlebars");
templateNode.setTemplate("{\"title\": \"Hello\"}");
templateNode.setTemplate('{"title": "Hello"}');
templateNode.clickOk();
changeNodeSetHeader.edit();
changeNodeSetHeader.ruleSet("headers", "msg", "{\"content-type\":\"application/json\"}", "json");
changeNodeSetHeader.ruleSet("headers", "msg", '{"content-type":"application/json"}', "json");
changeNodeSetHeader.clickOk();
httpinNode.connect(templateNode);
@@ -389,9 +389,9 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 2);
httpRequetNode.edit();
httpRequetNode.setMethod("get");
httpRequetNode.setMethod("GET");
httpRequetNode.setUrl(helper.url() + "/settings");
httpRequetNode.setRet("bin");
httpRequetNode.setReturn("bin");
httpRequetNode.clickOk();
injectNode.connect(httpRequetNode);
@@ -413,11 +413,11 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 3);
functionNode.edit();
functionNode.setCode("msg.payload = \"data to post\";");
functionNode.setFunction('msg.payload = "data to post";\nreturn msg;');
functionNode.clickOk();
httpRequetNode.edit();
httpRequetNode.setMethod("post");
httpRequetNode.setMethod("POST");
var url = helper.url() + httpNodeRoot + "/set-header";
httpRequetNode.setUrl(url);
httpRequetNode.clickOk();

View File

@@ -1216,7 +1216,7 @@ describe('function node', function() {
msg.should.have.property('payload', n1.id);
done();
});
n1.receive({payload:"foo",topicb: "bar"});
n1.receive({payload:"foo",topic: "bar"});
});
});
@@ -1230,7 +1230,7 @@ describe('function node', function() {
msg.should.have.property('payload', n1.name);
done();
});
n1.receive({payload:"foo",topicb: "bar"});
n1.receive({payload:"foo",topic: "bar"});
});
});

View File

@@ -15,7 +15,8 @@
**/
var should = require("should");
var rpi = require("../../../../nodes/core/hardware/36-rpi-gpio.js");
var rpiNode = require("../../../../nodes/core/hardware/36-rpi-gpio.js");
var statusNode = require("../../../../nodes/core/core/25-status.js");
var helper = require("node-red-node-test-helper");
var fs = require("fs");
@@ -50,7 +51,7 @@ describe('RPI GPIO Node', function() {
it('should load Input node', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }];
helper.load(rpi, flow, function() {
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio in');
try {
@@ -69,7 +70,7 @@ describe('RPI GPIO Node', function() {
it('should load Output node', function(done) {
var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }];
helper.load(rpi, flow, function() {
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio out');
try {
@@ -86,4 +87,62 @@ describe('RPI GPIO Node', function() {
});
});
it('should read a dummy value high (not on Pi)', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", pin:"7", intype:"up", debounce:"25", read:true, wires:[["n2"]] },
{id:"n2", type:"helper"}];
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'pi/7');
msg.should.have.property('payload', 1);
done();
} catch(err) {
done(err);
}
});
});
});
it('should read a dummy value low (not on Pi)', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", pin:"11", intype:"down", debounce:"25", read:true, wires:[["n2"]] },
{id:"n2", type:"helper"}];
helper.load(rpiNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'pi/11');
msg.should.have.property('payload', 0);
done();
} catch(err) {
done(err);
}
});
});
});
it('should be able preset out to a dummy value (not on Pi)', function(done) {
var flow = [{id:"n1", type:"rpi-gpio out", pin:"7", out:"out", level:"0", set:true, freq:"", wires:[], z:"1"},
{id:"n2", type:"status", scope:null, wires:[["n3"]], z:"1"},
{id:"n3", type:"helper", z:"1"}];
helper.load([rpiNode,statusNode], flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
n3.on("input", function(msg) {
try {
msg.should.have.property('status');
msg.status.should.have.property('text', "rpi-gpio.status.na");
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"1"});
});
});
});

View File

@@ -603,14 +603,14 @@ describe('JOIN node', function() {
});
it('should accumulate a merged object', function(done) {
var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:1},
var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:3},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
if (c === 5) {
if (c === 3) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("a",3);
@@ -632,14 +632,14 @@ describe('JOIN node', function() {
});
it('should be able to reset an accumulation', function(done) {
var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:1},
var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:3},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
if (c === 3) {
if (c === 1) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("a",1);
@@ -649,11 +649,20 @@ describe('JOIN node', function() {
}
catch(e) { done(e) }
}
if (c === 5) {
if (c === 2) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("b",2);
msg.payload.should.have.property("c",1);
msg.payload.should.have.property("e",2);
msg.payload.should.have.property("f",1);
}
catch(e) { done(e) }
}
if (c === 3) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("g",2);
msg.payload.should.have.property("h",1);
msg.payload.should.have.property("i",3);
done();
}
catch(e) { done(e) }
@@ -664,8 +673,11 @@ describe('JOIN node', function() {
n1.receive({payload:{b:2}, topic:"b"});
n1.receive({payload:{c:3}, topic:"c"});
n1.receive({payload:{d:4}, topic:"d", complete:true});
n1.receive({payload:{b:2}, topic:"e"});
n1.receive({payload:{c:1}, topic:"f"});
n1.receive({payload:{e:2}, topic:"e"});
n1.receive({payload:{f:1}, topic:"f", complete:true});
n1.receive({payload:{g:2}, topic:"g"});
n1.receive({payload:{h:1}, topic:"h"});
n1.receive({payload:{i:3}, topic:"i"});
});
});

View File

@@ -66,13 +66,25 @@ describe('SORT node', function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property(target);
var data = msg[target];
data.length.should.equal(data_out.length);
for(var i = 0; i < data_out.length; i++) {
data[i].should.equal(data_out[i]);
try {
msg.should.have.property(target);
var data = msg[target];
data.length.should.equal(data_out.length);
for(var i = 0; i < data_out.length; i++) {
var data0 = data[i];
var data1 = data_out[i];
if (typeof data0 === "object") {
data0.should.deepEqual(data1);
}
else {
data0.should.equal(data1);
}
}
done();
}
catch(e) {
console.log(e);
}
done();
});
var msg = {};
msg[target] = data_in;
@@ -93,6 +105,34 @@ describe('SORT node', function() {
}
function check_sort1(flow, key, key_type, data_in, data_out, done) {
function equals(v0, v1) {
var k0 = Object.keys(v0);
var k1 = Object.keys(v1);
if (k0.length === k1.length) {
for (var i = 0; i < k0.length; i++) {
var k = k0[i];
if (!v1.hasOwnProperty(k) ||
(v0[k] !== v1[k])) {
return false;
}
}
return true;
}
return false;
}
function indexOf(a, v) {
for(var i = 0; i < a.length; i++) {
var av = a[i];
if ((typeof v === 'object') && equals(v, av)) {
return i;
}
else if (v === av) {
return i;
}
}
return -1;
}
var sort = flow[0];
var prop = (key_type === "msg") ? key : "payload";
sort.targetType = "seq";
@@ -107,7 +147,7 @@ describe('SORT node', function() {
msg.should.have.property("parts");
msg.parts.should.have.property("count", data_out.length);
var data = msg[prop];
var index = data_out.indexOf(data);
var index = indexOf(data_out, data);
msg.parts.should.have.property("index", index);
count++;
if (count === data_out.length) {
@@ -136,7 +176,6 @@ describe('SORT node', function() {
check_sort1(flow, exp, "jsonata", data_in, data_out, done);
}
(function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, wires:[["n2"]]},
{id:"n2", type:"helper"}];
@@ -239,6 +278,19 @@ describe('SORT node', function() {
});
})();
(function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, wires:[["n2"]]},
{id:"n2", type:"helper"}];
var conv = function(x) {
return x.map(function(v) { return { val:v }; });
};
var data_in = conv([ "200", "4", "30", "1000" ]);
var data_out = conv([ "4", "30", "200", "1000" ]);
it('should sort payload of objects', function(done) {
check_sort0C(flow, "val", data_in, data_out, done);
});
})();
it('should sort payload by context (exp, not number, ascending)', function(done) {
var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$flowContext($)", msgKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"},
{id:"n2", type:"helper",z:"flow"},
@@ -268,7 +320,7 @@ describe('SORT node', function() {
});
it('should sort message group by context (exp, not number, ascending)', function(done) {
var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$globalContext(payload)", msgKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"},
var flow = [{id:"n1", type:"sort", target:"data", targetType:"seq", seqKey:"$globalContext(payload)", seqKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"},
{id:"n2", type:"helper",z:"flow"},
{id:"flow", type:"tab"}];
var data_in = [ "first", "second", "third", "fourth" ];
@@ -282,15 +334,20 @@ describe('SORT node', function() {
n1.context()["global"].set("third","3");
n1.context()["global"].set("fourth","2");
n2.on("input", function(msg) {
msg.should.have.property("payload");
msg.should.have.property("parts");
msg.parts.should.have.property("count", data_out.length);
var data = msg["payload"];
var index = data_out.indexOf(data);
msg.parts.should.have.property("index", index);
count++;
if (count === data_out.length) {
done();
try {
msg.should.have.property("payload");
msg.should.have.property("parts");
msg.parts.should.have.property("count", data_out.length);
var data = msg["payload"];
var index = data_out.indexOf(data);
msg.parts.should.have.property("index", index);
count++;
if (count === data_out.length) {
done();
}
}
catch(e) {
done(e);
}
});
var len = data_in.length;
@@ -332,7 +389,7 @@ describe('SORT node', function() {
});
it('should sort message group by persistable context (exp, not number, descending)', function(done) {
var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$flowContext(payload,\"memory\")", msgKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"},
var flow = [{id:"n1", type:"sort", target:"data", targetType:"seq", seqKey:"$flowContext(payload,\"memory\")", seqKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"},
{id:"n2", type:"helper",z:"flow"},
{id:"flow", type:"tab"}];
var data_in = [ "first", "second", "third", "fourth" ];

View File

@@ -265,6 +265,23 @@ describe('JSON node', function() {
});
});
it('should pass an object if provided a valid object and schema and action is object', function(done) {
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
should.equal(msg.payload.number, 3);
should.equal(msg.payload.string, "allo");
done();
});
var obj = {"number": 3, "string": "allo"};
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
jn1.receive({payload:obj, schema:schema});
});
});
it('should pass a string if provided a valid object and schema', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
@@ -281,6 +298,22 @@ describe('JSON node', function() {
});
});
it('should pass a string if provided a valid JSON string and schema and action is string', function(done) {
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
should.equal(msg.payload, '{"number":3,"string":"allo"}');
done();
});
var jsonString = '{"number":3,"string":"allo"}';
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
jn1.receive({payload:jsonString, schema:schema});
});
});
it('should log an error if passed an invalid object and valid schema', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
@@ -305,6 +338,78 @@ describe('JSON node', function() {
});
});
it('should log an error if passed an invalid object and valid schema and action is object', function(done) {
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
try {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
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();
} catch(err) {
done(err);
}
});
});
it('should log an error if passed an invalid JSON string and valid schema', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
try {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
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();
} catch(err) {
done(err);
}
});
});
it('should log an error if passed an invalid JSON string and valid schema and action is string', function(done) {
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
try {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
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();
} catch(err) {
done(err);
}
});
});
it('should log an error if passed a valid object and invalid schema', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];

View File

@@ -128,7 +128,7 @@ describe("red/nodes/registry/localfilesystem",function() {
}
return _join.apply(null,arguments);
}));
localfilesystem.init({i18n:{registerMessageCatalog:function(){}},events:{emit:function(){}},settings:{coreNodesDir:moduleDir}});
localfilesystem.init({log:{debug:function(){}},i18n:{registerMessageCatalog:function(){}},events:{emit:function(){}},settings:{coreNodesDir:moduleDir}});
var nodeList = localfilesystem.getNodeFiles();
nodeList.should.have.a.property("node-red");
var nm = nodeList['node-red'];