Compare commits

...

42 Commits

Author SHA1 Message Date
Nick O'Leary
08fccc4e77 Update for 0.19.4 2018-09-17 11:26:06 +01:00
Nick O'Leary
c1d50e82e1 Fix race condition in non-cache lfs context
Fixes #1888
2018-09-17 10:31:00 +01:00
Nick O'Leary
9777af7cb5 LocalFileSystem Context: Remove extra flush code 2018-09-16 22:04:09 +01:00
Hiroki Uchikawa
fd86035865 Prevent race condition (#1889)
* Make pending Flag to be deleted after write process complete.

* Prevent executing write process until the previous process is completed

* Fix to prevent file write race condition when closing file context

* Make flushing rerun if pendingWrites was added
2018-09-16 21:15:23 +01:00
Nick O'Leary
a8ec032553 Allow context store name to be provided in the key
For nodes that get/set context, when multiple stores are configured
they will not know to parse the store name from the key. So they
will pass the store name in the key, such as #:(store)::key.

Currently that will cause that full string to be used as the key
and the default context store used - which is wrong.

The code now parses out the store name from the key if it is set -
athough if the call to get/set does include the store argument, it
will take precedence.

This only applies when the key is a string - it doesn't apply when
an array of keys is provided.
2018-09-14 23:21:05 +01:00
Nick O'Leary
66ee27c5fa Switch node: only use promises when absolutely necessary
Fixes a significant performance regression introduced when the
node was made async-only with the persistent context work.
2018-09-14 14:03:36 +01:00
Nick O'Leary
17a737ca88 Fix dbl-click handling on webkit-based browsers
d3.event.buttons is not as widely supported as I thought. Can
change this one instance as it is inside a click handler so
d3.event.button will be defined instead
2018-09-14 11:09:56 +01:00
Nick O'Leary
75e7c0e50d Ensure context.flow/global cannot be deleted or enumerated 2018-09-10 22:30:51 +01:00
Nick O'Leary
fc0cf1ff51 Handle context.get with multiple levels of unknown key
Fixes #1883
2018-09-09 23:47:31 +01:00
Nick O'Leary
0f4d46671f Fix global.get("foo.bar") for functionGlobalContext set values 2018-09-09 11:07:44 +01:00
Kazuhito Yokoi
048f9c0294 Fix node color bug (#1877)
* Fix node color bug

* Add color property into sample node

* Revert view.js

* Add color handling into getNodeColor()
2018-09-08 22:41:38 +01:00
Nick O'Leary
ca77842b5b Merge pull request #1857 from cclauss/patch-1
Define raw_input() in Python 3 & fix time.sleep()
2018-09-06 22:59:48 +01:00
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
cclauss
4374506981 Define raw_input() in Python 3 & fix time.sleep()
* __raw_input()__ was removed in Python 3 in favor of __input()__
* Fix __sleep()__ to match the import on line 22

[flake8](http://flake8.pycqa.org) testing of https://github.com/node-red/node-red on Python 3.7.0

$ __flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics__
```
./nodes/core/hardware/nrgpio.py:45:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:63:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:85:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:120:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:134:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:164:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:201:17: F821 undefined name 'time'
                time.sleep(10)
                ^
7     F821 undefined name 'raw_input'
7
```

@dceejay
2018-08-15 16:25:58 +02: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
49 changed files with 1013 additions and 462 deletions

View File

@@ -1,3 +1,34 @@
#### 0.19.4: Maintenance Release
- Fix race condition in non-cache lfs context Fixes #1888
- LocalFileSystem Context: Remove extra flush code
- Prevent race condition in caching mode of lfs context (#1889)
- Allow context store name to be provided in the key
- Switch node: only use promises when absolutely necessary
- Fix dbl-click handling on webkit-based browsers
- Ensure context.flow/global cannot be deleted or enumerated
- Handle context.get with multiple levels of unknown key Fixes #1883
- Fix global.get("foo.bar") for functionGlobalContext set values
- Fix node color bug (#1877)
- Merge pull request #1857 from cclauss/patch-1
- Define raw_input() in Python 3 & fix time.sleep()
#### 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

@@ -826,7 +826,11 @@ RED.utils = (function() {
}
result = nodeColorCache[type];
}
return result;
if (result) {
return result;
} else {
return "#ddd";
}
}
function addSpinnerOverlay(container,contain) {

View File

@@ -1766,7 +1766,7 @@ RED.view = (function() {
clickTime = now;
dblClickPrimed = (lastClickNode == mousedown_node &&
d3.event.buttons === 1 &&
d3.event.button === 0 &&
!d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey);
lastClickNode = mousedown_node;

View File

@@ -67,6 +67,7 @@
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
color: "#ddd", // set icon color
// set the icon (held in icons dir below where you save the node)
icon: "myicon.png", // saved in icons/myicon.png
label: function() { // sets the default label contents

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

@@ -21,7 +21,12 @@ import os
import subprocess
from time import sleep
bounce = 25;
try:
raw_input # Python 2
except NameError:
raw_input = input # Python 3
bounce = 25
if len(sys.argv) > 2:
cmd = sys.argv[1].lower()
@@ -198,7 +203,7 @@ if len(sys.argv) > 2:
elif cmd == "kbd": # catch keyboard button events
try:
while not os.path.isdir("/dev/input/by-path"):
time.sleep(10)
sleep(10)
infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip()
infile_path = "/dev/input/by-path/" + infile
EVENT_SIZE = struct.calcsize('llHHI')

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

@@ -92,31 +92,80 @@ module.exports = function(RED) {
}
function getProperty(node,msg) {
return new Promise((resolve,reject) => {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else {
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
}
});
} else {
if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
try {
return RED.util.evaluateJSONataExpression(node.property,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else {
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
try {
return RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
} catch(err) {
return undefined;
}
}
});
}
}
function getV1(node,msg,rule,hasParts) {
return new Promise( (resolve,reject) => {
if (node.useAsyncRules) {
return new Promise( (resolve,reject) => {
if (rule.vt === 'prev') {
resolve(node.previousValue);
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (rule.vt === 'json') {
resolve("json"); // TODO: ?! invalid case
} else if (rule.vt === 'null') {
resolve("null");
} else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
}
});
} else {
if (rule.vt === 'prev') {
resolve(node.previousValue);
return node.previousValue;
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
@@ -125,83 +174,120 @@ module.exports = function(RED) {
exp.assign("N", msg.parts.count);
}
}
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
try {
return RED.util.evaluateJSONataExpression(exp,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else if (rule.vt === 'json') {
resolve("json");
return "json"; // TODO: ?! invalid case
} else if (rule.vt === 'null') {
resolve("null");
return "null";
} else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
try {
return RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
} catch(err) {
return undefined;
}
}
});
}
}
function getV2(node,msg,rule) {
return new Promise((resolve,reject) => {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
resolve(node.previousValue);
} else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
} else {
resolve(v2);
}
})
} else {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
resolve(node.previousValue);
return node.previousValue;
} else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
try {
return RED.util.evaluateJSONataExpression(rule.v2,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
try {
return RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
} catch(err) {
return undefined;
}
} else {
resolve(v2);
return v2;
}
})
}
}
function applyRule(node, msg, property, state) {
return new Promise((resolve,reject) => {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var rule = node.rules[state.currentRule];
var v1,v2;
var rule = node.rules[state.currentRule];
var v1,v2;
getV1(node,msg,rule,state.hasParts).then(value => {
v1 = value;
}).then(()=>getV2(node,msg,rule)).then(value => {
v2 = value;
}).then(() => {
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return resolve(false);
getV1(node,msg,rule,state.hasParts).then(value => {
v1 = value;
}).then(()=>getV2(node,msg,rule)).then(value => {
v2 = value;
}).then(() => {
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
} else {
state.onward.push(null);
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return resolve(false);
}
} else {
state.onward.push(null);
}
resolve(state.currentRule < node.rules.length - 1);
});
})
} else {
var rule = node.rules[state.currentRule];
var v1 = getV1(node,msg,rule,state.hasParts);
var v2 = getV2(node,msg,rule);
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return false;
}
resolve(state.currentRule < node.rules.length - 1);
});
})
} else {
state.onward.push(null);
}
return state.currentRule < node.rules.length - 1
}
}
function applyRules(node, msg, property,state) {
@@ -215,7 +301,18 @@ module.exports = function(RED) {
msg.parts.hasOwnProperty("index")
}
}
return applyRule(node,msg,property,state).then(hasMore => {
if (node.useAsyncRules) {
return applyRule(node,msg,property,state).then(hasMore => {
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
} else {
node.previousValue = property;
return state.onward;
}
});
} else {
var hasMore = applyRule(node,msg,property,state);
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
@@ -223,7 +320,7 @@ module.exports = function(RED) {
node.previousValue = property;
return state.onward;
}
});
}
}
@@ -248,6 +345,14 @@ module.exports = function(RED) {
var valid = true;
var repair = n.repair;
var needsCount = repair;
this.useAsyncRules = (
this.propertyType === 'flow' ||
this.propertyType === 'global' || (
this.propertyType === 'jsonata' &&
/\$(flow|global)Context/.test(this.property)
)
);
for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i];
needsCount = needsCount || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
@@ -258,6 +363,13 @@ module.exports = function(RED) {
rule.vt = 'str';
}
}
this.useAsyncRules = this.useAsyncRules || (
rule.vt === 'flow' ||
rule.vt === 'global' || (
rule.vt === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v)
)
);
if (rule.vt === 'num') {
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
@@ -270,6 +382,9 @@ module.exports = function(RED) {
valid = false;
}
}
if (rule.vt === 'flow' || rule.vt === 'global' || rule.vt === 'jsonata') {
this.useAsyncRules = true;
}
if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) {
if (!isNaN(Number(rule.v2))) {
@@ -278,6 +393,13 @@ module.exports = function(RED) {
rule.v2t = 'str';
}
}
this.useAsyncRules = this.useAsyncRules || (
rule.v2t === 'flow' ||
rule.v2t === 'global' || (
rule.v2t === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v2)
)
);
if (rule.v2t === 'num') {
rule.v2 = Number(rule.v2);
} else if (rule.v2t === 'jsonata') {
@@ -290,7 +412,6 @@ module.exports = function(RED) {
}
}
}
if (!valid) {
return;
}
@@ -420,18 +541,32 @@ module.exports = function(RED) {
if (needsCount && checkParts && hasParts) {
return addMessageToPending(msg);
}
return getProperty(node,msg)
.then(property => applyRules(node,msg,property))
.then(onward => {
if (!repair || !hasParts) {
node.send(onward);
}
else {
sendGroupMessages(onward, msg);
}
}).catch(err => {
node.warn(err);
});
if (node.useAsyncRules) {
return getProperty(node,msg)
.then(property => applyRules(node,msg,property))
.then(onward => {
if (!repair || !hasParts) {
node.send(onward);
}
else {
sendGroupMessages(onward, msg);
}
}).catch(err => {
node.warn(err);
});
} else {
try {
var property = getProperty(node,msg);
var onward = applyRules(node,msg,property);
if (!repair || !hasParts) {
node.send(onward);
} else {
sendGroupMessages(onward, msg);
}
} catch(err) {
node.warn(err);
}
}
}
function clearPending() {
@@ -473,7 +608,11 @@ module.exports = function(RED) {
}
this.on('input', function(msg) {
processMessageQueue(msg);
if (node.useAsyncRules) {
processMessageQueue(msg);
} else {
processMessage(msg,true);
}
});
this.on('close', function() {

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.4",
"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

@@ -17,6 +17,7 @@
var clone = require("clone");
var log = require("../../log");
var memory = require("./memory");
var util = require("../../util");
var settings;
@@ -209,104 +210,131 @@ function createContext(id,seed) {
insertSeedValues = function(keys,values) {
if (!Array.isArray(keys)) {
if (values[0] === undefined) {
values[0] = seed[keys];
values[0] = util.getObjectProperty(seed,keys);
}
} else {
for (var i=0;i<keys.length;i++) {
if (values[i] === undefined) {
values[i] = seed[keys[i]];
values[i] = util.getObjectProperty(seed,keys[i]);
}
}
}
}
}
Object.defineProperties(obj, {
get: {
value: function(key, storage, callback) {
var context;
obj.get = function(key, storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function'){
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (callback) {
if (!seed) {
context.get(scope,key,callback);
} else {
context.get(scope,key,function() {
if (arguments[0]) {
callback(arguments[0]);
return;
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback && typeof callback !== 'function'){
throw new Error("Callback must be a function");
}
if (!Array.isArray(key)) {
var keyParts = util.parseContextStore(key);
key = keyParts.key;
if (!storage) {
storage = keyParts.store || "_";
}
var results = Array.prototype.slice.call(arguments,[1]);
insertSeedValues(key,results);
// Put the err arg back
results.unshift(undefined);
callback.apply(null,results);
});
}
} else {
// No callback, attempt to do this synchronously
var results = context.get(scope,key);
if (seed) {
if (Array.isArray(key)) {
insertSeedValues(key,results);
} else if (results === undefined){
results = seed[key];
} else {
if (!storage) {
storage = "_";
}
}
context = getContextStorage(storage);
if (callback) {
if (!seed) {
context.get(scope,key,callback);
} else {
context.get(scope,key,function() {
if (arguments[0]) {
callback(arguments[0]);
return;
}
var results = Array.prototype.slice.call(arguments,[1]);
insertSeedValues(key,results);
// Put the err arg back
results.unshift(undefined);
callback.apply(null,results);
});
}
} else {
// No callback, attempt to do this synchronously
var results = context.get(scope,key);
if (seed) {
if (Array.isArray(key)) {
insertSeedValues(key,results);
} else if (results === undefined){
results = util.getObjectProperty(seed,key);
}
}
return results;
}
}
},
set: {
value: function(key, value, storage, callback) {
var context;
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback && typeof callback !== 'function'){
throw new Error("Callback must be a function");
}
if (!Array.isArray(key)) {
var keyParts = util.parseContextStore(key);
key = keyParts.key;
if (!storage) {
storage = keyParts.store || "_";
}
} else {
if (!storage) {
storage = "_";
}
}
context = getContextStorage(storage);
context.set(scope, key, value, callback);
}
},
keys: {
value: function(storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (seed) {
if (callback) {
context.keys(scope, function(err,keys) {
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
});
} else {
var keys = context.keys(scope);
return Array.from(new Set(seedKeys.concat(keys)).keys())
}
} else {
return context.keys(scope, callback);
}
}
return results;
}
};
obj.set = function(key, value, storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
context.set(scope, key, value, callback);
};
obj.keys = function(storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (seed) {
if (callback) {
context.keys(scope, function(err,keys) {
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
});
} else {
var keys = context.keys(scope);
return Array.from(new Set(seedKeys.concat(keys)).keys())
}
} else {
return context.keys(scope, callback);
}
};
});
return obj;
}
@@ -320,9 +348,13 @@ function getContext(localId,flowId) {
}
var newContext = createContext(contextId);
if (flowId) {
newContext.flow = getContext(flowId);
Object.defineProperty(newContext, 'flow', {
value: getContext(flowId)
});
}
newContext.global = contexts['global'];
Object.defineProperty(newContext, 'global', {
value: contexts['global']
})
contexts[contextId] = newContext;
return newContext;
}

View File

@@ -140,6 +140,7 @@ function stringify(value) {
function LocalFileSystem(config){
this.config = config;
this.storageBaseDir = getBasePath(this.config);
this.writePromise = Promise.resolve();
if (config.hasOwnProperty('cache')?config.cache:true) {
this.cache = MemoryStore({});
}
@@ -213,12 +214,13 @@ LocalFileSystem.prototype.open = function(){
}
LocalFileSystem.prototype.close = function(){
if (this.cache && this._flushPendingWrites) {
var self = this;
if (this.cache && this._pendingWriteTimeout) {
clearTimeout(this._pendingWriteTimeout);
delete this._pendingWriteTimeout;
return this._flushPendingWrites();
this.flushInterval = 0;
}
return Promise.resolve();
return this.writePromise;
}
LocalFileSystem.prototype.get = function(scope, key, callback) {
@@ -230,14 +232,31 @@ LocalFileSystem.prototype.get = function(scope, key, callback) {
}
var storagePath = getStoragePath(this.storageBaseDir ,scope);
loadFile(storagePath + ".json").then(function(data){
var value;
if(data){
data = JSON.parse(data);
if (!Array.isArray(key)) {
callback(null, util.getObjectProperty(data,key));
try {
value = util.getObjectProperty(data,key);
} catch(err) {
if (err.code === "INVALID_EXPR") {
throw err;
}
value = undefined;
}
callback(null, value);
} else {
var results = [undefined];
for (var i=0;i<key.length;i++) {
results.push(util.getObjectProperty(data,key[i]))
try {
value = util.getObjectProperty(data,key[i]);
} catch(err) {
if (err.code === "INVALID_EXPR") {
throw err;
}
value = undefined;
}
results.push(value)
}
callback.apply(null,results);
}
@@ -260,15 +279,17 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
return;
} else {
this._pendingWriteTimeout = setTimeout(function() {
self._flushPendingWrites.call(self).catch(function(err) {
log.error(log._("context.localfilesystem.error-write",{message:err.toString()}))
self.writePromise = self.writePromise.then(function(){
return self._flushPendingWrites.call(self).catch(function(err) {
log.error(log._("context.localfilesystem.error-write",{message:err.toString()}));
});
});
}, this.flushInterval);
}
} else if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
} else {
loadFile(storagePath + ".json").then(function(data){
self.writePromise = self.writePromise.then(function() { return loadFile(storagePath + ".json") }).then(function(data){
var obj = data ? JSON.parse(data) : {}
if (!Array.isArray(key)) {
key = [key];

View File

@@ -32,7 +32,14 @@ Memory.prototype._getOne = function(scope, key) {
var value;
var error;
if(this.data[scope]){
value = util.getObjectProperty(this.data[scope], key);
try {
value = util.getObjectProperty(this.data[scope], key);
} catch(err) {
if (err.code === "INVALID_EXPR") {
throw err;
}
value = undefined;
}
}
return value;
}

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

@@ -130,13 +130,19 @@ function compareObjects(obj1,obj2) {
return true;
}
function createError(code, message) {
var e = new Error(message);
e.code = code;
return e;
}
function normalisePropertyExpression(str) {
// This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js
var length = str.length;
if (length === 0) {
throw new Error("Invalid property expression: zero-length");
throw createError("INVALID_EXPR","Invalid property expression: zero-length");
}
var parts = [];
var start = 0;
@@ -149,14 +155,14 @@ function normalisePropertyExpression(str) {
if (!inString) {
if (c === "'" || c === '"') {
if (i != start) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
inString = true;
quoteChar = c;
start = i+1;
} else if (c === '.') {
if (i===0) {
throw new Error("Invalid property expression: unexpected . at position 0");
throw createError("INVALID_EXPR","Invalid property expression: unexpected . at position 0");
}
if (start != i) {
v = str.substring(start,i);
@@ -167,57 +173,57 @@ function normalisePropertyExpression(str) {
}
}
if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression");
throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Next char is first char of an identifier: a-z 0-9 $ _
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
} else if (c === '[') {
if (i === 0) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
parts.push(str.substring(start,i));
}
if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression");
throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
inBox = true;
} else if (c === ']') {
if (!inBox) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
v = str.substring(start,i);
if (/^\d+$/.test(v)) {
parts.push(parseInt(v));
} else {
throw new Error("Invalid property expression: unexpected array expression at position "+start);
throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
}
}
start = i+1;
inBox = false;
} else if (c === ' ') {
throw new Error("Invalid property expression: unexpected ' ' at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected ' ' at position "+i);
}
} else {
if (c === quoteChar) {
if (i-start === 0) {
throw new Error("Invalid property expression: zero-length string at position "+start);
throw createError("INVALID_EXPR","Invalid property expression: zero-length string at position "+start);
}
parts.push(str.substring(start,i));
// If inBox, next char must be a ]. Otherwise it may be [ or .
if (inBox && !/\]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected array expression at position "+start);
throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
}
start = i+1;
inString = false;
@@ -226,7 +232,7 @@ function normalisePropertyExpression(str) {
}
if (inBox || inString) {
throw new Error("Invalid property expression: unterminated expression");
throw new createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
if (start < length) {
parts.push(str.substring(start));

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

@@ -113,6 +113,21 @@ describe('context', function() {
context2.global.get("foo").should.equal("test");
});
it('context.flow/global are not enumerable', function() {
var context1 = Context.get("1","flowA");
Object.keys(context1).length.should.equal(0);
Object.keys(context1.flow).length.should.equal(0);
Object.keys(context1.global).length.should.equal(0);
})
it('context.flow/global cannot be deleted', function() {
var context1 = Context.get("1","flowA");
delete context1.flow;
should.exist(context1.flow);
delete context1.global;
should.exist(context1.global);
})
it('deletes context',function() {
var context = Context.get("1","flowA");
should.not.exist(context.get("foo"));
@@ -191,15 +206,26 @@ describe('context', function() {
});
it('returns functionGlobalContext value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){
return Context.load().then(function(){
var context = Context.get("1","flowA");
var v = context.global.get('foo');
v.should.equal('bar');
});
})
it('returns functionGlobalContext sub-value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:{bar:123}}});
return Context.load().then(function(){
var context = Context.get("1","flowA");
var v = context.global.get('foo.bar');
should.equal(v,123);
});
})
});
describe('external context storage',function() {
@@ -473,6 +499,23 @@ describe('context', function() {
done();
}).catch(done);
});
it('should allow the store name to be provide in the key', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("#:(test)::foo","bar");
context.get("#:(test)::foo");
stubGet2.called.should.be.false();
stubSet2.called.should.be.false();
stubSet.calledWithExactly("1:flow","foo","bar",undefined).should.be.true();
stubGet.calledWith("1:flow","foo").should.be.true();
done();
}).catch(done);
});
it('should use default as the alias of other context', function(done) {
Context.init({contextStorage:contextAlias});
Context.load().then(function(){
@@ -579,16 +622,16 @@ describe('context', function() {
});
it('should return multiple functionGlobalContext values if key is an array', function(done) {
var fGC = { "foo1": 456, "foo2": 789 };
var fGC = { "foo1": 456, "foo2": {"bar":789} };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function(){
var context = Context.get("1","flow");
context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
context.global.get(["foo1","foo2.bar","foo3"], "memory", function(err,foo1,foo2,foo3){
if (err) {
done(err);
} else {
foo1.should.be.equal(456);
foo2.should.be.equal(789);
should.equal(foo1, 456);
should.equal(foo2, 789);
should.not.exist(foo3);
done();
}

View File

@@ -672,7 +672,7 @@ describe('localfilesystem',function() {
it('should enumerate context keys in the cache',function() {
var globalData = {foo:"bar"};
fs.outputFile(path.join(resourcesDir,defaultContextBase,"global","global.json"), JSON.stringify(globalData,null,4), "utf8").then(function(){
return fs.outputFile(path.join(resourcesDir,defaultContextBase,"global","global.json"), JSON.stringify(globalData,null,4), "utf8").then(function(){
context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 2});
return context.open()
}).then(function(){

View File

@@ -96,7 +96,7 @@ describe('memory',function() {
context.set("nodeX","three","test3");
context.set("nodeX","four","test4");
var values = context.get("nodeX",["one","unknown"]);
var values = context.get("nodeX",["one","unknown.with.multiple.levels"]);
values.should.eql(["test1",undefined])
})
it('should throw error if bad key included in multiple keys', function() {

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'];