Compare commits

..

2 Commits

Author SHA1 Message Date
Dave Conway-Jones
376db60acc fix error messages for readonly 2018-08-30 09:32:50 +01:00
Dave Conway-Jones
ffa65afbb2 Catch readonly write errors more cleanly
Fail more cleanly when run from a readonly files system without setting readOnly true.
2018-08-26 10:49:53 +01:00
49 changed files with 271 additions and 963 deletions

View File

@@ -1,19 +1,3 @@
#### 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,18 +522,10 @@
this.selectLabel.empty();
var image;
if (opt.icon) {
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);
}
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 {
this.selectLabel.text(opt.label);
}

View File

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

View File

@@ -1004,7 +1004,7 @@ RED.sidebar.versionControl = (function() {
RED.sidebar.addTab({
id: "version-control",
label: RED._("sidebar.project.versionControl.history"),
name: RED._("sidebar.project.versionControl.projectHistory"),
name: "Project History",
content: sidebarContent,
enableOnEdit: false,
pinned: true,

View File

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

View File

@@ -53,14 +53,12 @@
<p>The Catch node can also be used to handle errors. To invoke a Catch node,
pass <code>msg</code> as a second argument to <code>node.error</code>:</p>
<pre>node.error("Error",msg);</pre>
<h4>Accessing Node Information</h4>
<h4>Referring Node Information</h4>
<p>In the function block, id and name of the node can be referenced using the following properties:</p>
<ul>
<li><code>node.id</code> - id of the node</li>
<li><code>node.name</code> - name of the node</li>
</ul>
<h4>Using environment variables</h4>
<p>Environment variables can be accessed using <code>env.get("MY_ENV_VAR")</code>.</p>
</script>
<script type="text/javascript">
@@ -128,7 +126,6 @@
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 }))
}
}
}
@@ -156,13 +156,6 @@ module.exports = function(RED) {
return node.context().global.keys.apply(node,arguments);
}
},
env: {
get: function(envVar) {
// For now, just return the env var. This will eventually
// also return project settings and subflow instance properties
return process.env[envVar]
}
},
setTimeout: function () {
var func = arguments[0];
var timerId;
@@ -210,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 {
@@ -248,6 +241,7 @@ 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++;
@@ -271,13 +265,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
@@ -286,4 +280,4 @@ module.exports = function(RED) {
}
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");
};
}

View File

@@ -19,26 +19,11 @@ 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);
}
@@ -111,29 +96,12 @@ 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) {
@@ -467,4 +435,4 @@ module.exports = function(RED) {
}
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
};
};

View File

@@ -33,7 +33,9 @@ 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;
};
@@ -644,7 +646,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).payload);
clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue));
}
}
});

View File

@@ -295,8 +295,7 @@
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 finalised and sent.
This resets any part counts.</p>
<p>If a message is received with the <b>msg.complete</b> property set, the output message is sent.</p>
<h4>Reduce Sequence mode</h4>
<p>When configured to join in reduce mode, an expression is applied to each
@@ -440,7 +439,10 @@
$("#node-input-joiner").typedInput({
default: 'str',
typeField: $("#node-input-joinerType"),
types:['str', 'bin']
types:[
'str',
'bin'
]
});
$("#node-input-property").typedInput({
@@ -449,7 +451,7 @@
});
$("#node-input-key").typedInput({
types:['msg']
types:['msg', {value:"merge", label:"", hasValue:false}]
});
$("#node-input-build").change();

View File

@@ -586,10 +586,7 @@ module.exports = function(RED) {
}
else {
if (msg.hasOwnProperty('complete')) {
if (inflight[partId]) {
inflight[partId].msg.complete = msg.complete;
completeSend(partId);
}
completeSend(partId);
}
else {
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
@@ -597,7 +594,6 @@ module.exports = function(RED) {
}
return;
}
if (!inflight.hasOwnProperty(partId)) {
if (payloadType === 'object' || payloadType === 'merged') {
inflight[partId] = {
@@ -608,6 +604,19 @@ 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,10 +196,9 @@ module.exports = function(RED) {
}).catch(err => {
node.error(err,msg);
});
return;
}
var parts = msg.parts;
if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
if (!parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
return;
}
var gid = parts.id;
@@ -243,8 +242,7 @@ module.exports = function(RED) {
delete pending[key];
}
}
pending_count = 0;
});
pending_count = 0; })
}
RED.nodes.registerType("sort", SortNode);

View File

@@ -19,7 +19,6 @@ 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);
@@ -30,7 +29,6 @@ module.exports = function(RED) {
this.compiledSchema = null;
var node = this;
this.on("input", function(msg) {
var validate = false;
if (msg.schema) {
@@ -67,17 +65,7 @@ module.exports = function(RED) {
}
catch(e) { node.error(e.message,msg); }
} else {
// 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);
}
node.send(msg);
}
}
else if (typeof value === "object") {
@@ -96,22 +84,13 @@ 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 {
// 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);
}
node.send(msg);
}
}
else { node.warn(RED._("json.errors.dropped")); }

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.19.3",
"version": "0.19.2",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -42,14 +42,13 @@
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors": "2.8.4",
"cron": "1.4.1",
"cron": "1.3.0",
"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",
@@ -57,9 +56,9 @@
"jsonata": "1.5.4",
"media-typer": "0.3.0",
"memorystore": "1.6.0",
"mqtt": "2.18.8",
"mqtt": "2.18.5",
"multer": "1.3.1",
"mustache": "2.3.2",
"mustache": "2.3.1",
"node-red-node-email": "0.1.*",
"node-red-node-feedparser": "^0.1.12",
"node-red-node-rbe": "0.2.*",
@@ -74,7 +73,7 @@
"request": "2.88.0",
"semver": "5.5.1",
"sentiment": "2.1.0",
"uglify-js": "3.4.9",
"uglify-js": "3.4.8",
"when": "3.7.8",
"ws": "1.1.5",
"xml2js": "0.4.19"
@@ -86,7 +85,7 @@
"chromedriver": "^2.41.0",
"grunt": "~1.0.3",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.1",
"grunt-cli": "~1.2.0",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",
@@ -104,7 +103,6 @@
"http-proxy": "^1.16.2",
"istanbul": "0.4.5",
"mocha": "^5.2.0",
"mosca": "^2.8.3",
"node-red-node-test-helper": "0.1.7",
"should": "^8.4.0",
"sinon": "1.17.7",

14
red.js
View File

@@ -101,8 +101,18 @@ if (parsedArgs.settings) {
var settingsStat = fs.statSync(defaultSettings);
if (settingsStat.mtime.getTime() <= settingsStat.ctime.getTime()) {
// Default settings file has not been modified - safe to copy
fs.copySync(defaultSettings,userSettingsFile);
settingsFile = userSettingsFile;
try {
fs.copySync(defaultSettings,userSettingsFile);
settingsFile = userSettingsFile;
}
catch (err) {
console.log("Failed to copy settings file to "+userSettingsFile);
console.log("Error: "+err.toString());
if (err.code == "EACCES") {
console.log("You may need to set readOnly: true, in settings.js");
}
process.exit(1);
}
} else {
// Use default settings.js as it has been modified
settingsFile = defaultSettings;

View File

@@ -587,7 +587,6 @@
"pullUnrelatedHistory": "<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>",
"pullChanges": "Pull changes",
"history": "history",
"projectHistory": "Project History",
"daysAgo": "__count__ day ago",
"daysAgo_plural": "__count__ days ago",
"hoursAgo": "__count__ hour ago",

View File

@@ -585,7 +585,6 @@
"pullUnrelatedHistory": "<p>リモートに関連のないコミット履歴があります。</p><p>本当に変更をプルしてローカルリポジトリに反映しますか?</p>",
"pullChanges": "プル変更",
"history": "履歴",
"projectHistory": "プロジェクト履歴",
"daysAgo": "__count__ 日前",
"daysAgo_plural": "__count__ 日前",
"hoursAgo": "__count__ 時間前",

View File

@@ -142,6 +142,7 @@
"restore": "Restoring __type__ file backup : __path__",
"restore-fail": "Restoring __type__ file backup failed : __message__",
"fsync-fail": "Flushing file __path__ to disk failed : __message__",
"fwrite-fail": "Writing backup file __path__ to disk failed : __message__",
"projects": {
"changing-project": "Setting active project : __project__",
"active-project": "Active project : __project__",

View File

@@ -23,7 +23,7 @@ var i18n;
var settings;
var disableNodePathScan = false;
var iconFileExtensions = [".png", ".gif", ".svg"];
var iconFileExtensions = [".png", ".gif"];
function init(runtime) {
settings = runtime.settings;
@@ -290,33 +290,6 @@ 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

@@ -81,7 +81,11 @@ module.exports = {
writeFile: function(path,content,backupPath) {
if (backupPath) {
if (fs.existsSync(path)) {
fs.renameSync(path,backupPath);
try {
fs.renameSync(path,backupPath);
} catch(e) {
log.warn(log._("storage.localfilesystem.fwrite-fail",{path:path, message:e.toString()}));
}
}
}
return when.promise(function(resolve,reject) {

View File

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

View File

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

View File

@@ -24,17 +24,12 @@ function functionNode(id) {
util.inherits(functionNode, nodePage);
functionNode.prototype.setFunction = function(func) {
functionNode.prototype.setCode = function(value) {
browser.click('#node-input-func-editor');
browser.keys(['Control', 'Home', 'Control']);
for (var i = 0; i < func.length; i++) {
browser.keys([func.charAt(i)]);
for (var i=0; i<value.length; i++) {
browser.keys([value.substr(i, 1)]);
}
// 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,20 +24,29 @@ function templateNode(id) {
util.inherits(templateNode, nodePage);
templateNode.prototype.setSyntax = function(syntax) {
browser.selectWithWait('#node-input-syntax', syntax);
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.setFormat = function(format) {
browser.selectWithWait('#node-input-format', format);
templateNode.prototype.setFormat = function(type) {
browser.selectByValue('#node-input-format', type);
}
templateNode.prototype.setTemplate = function(template) {
templateNode.prototype.setTemplate = function(value) {
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 < template.length; i++) {
browser.keys([template.charAt(i)]);
for (var i=0; i<value.length; i++) {
browser.keys([value.charAt(i)]);
}
browser.keys(['Control', 'Shift', 'End', 'Shift', 'Control']);
browser.keys(['Delete']);

View File

@@ -1,39 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
function setServer(broker, port) {
browser.setValue('#node-config-input-broker', broker);
port = port ? port : 1883;
browser.setValue('#node-config-input-port', port);
}
function clickOk() {
browser.clickWithWait('#node-config-dialog-ok');
// Wait until an config dialog closes.
browser.waitForVisible('#node-config-dialog-ok', 2000, true);
}
function edit() {
browser.clickWithWait('#node-input-lookup-broker');
// Wait until a config dialog opens.
browser.waitForVisible('#node-config-dialog-ok', 2000);
}
module.exports = {
setServer: setServer,
clickOk: clickOk,
edit: edit
};

View File

@@ -1,35 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var nodePage = require("../../node_page");
function mqttInNode(id) {
nodePage.call(this, id);
}
util.inherits(mqttInNode, nodePage);
mqttInNode.prototype.setTopic = function (topic) {
browser.setValue('#node-input-topic', topic);
}
mqttInNode.prototype.setQoS = function (qos) {
browser.selectWithWait('#node-input-qos', qos);
}
module.exports = mqttInNode;

View File

@@ -1,35 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var nodePage = require("../../node_page");
function mqttOutNode(id) {
nodePage.call(this, id);
}
util.inherits(mqttOutNode, nodePage);
mqttOutNode.prototype.setTopic = function(topic) {
browser.setValue('#node-input-topic', topic);
}
mqttOutNode.prototype.setRetain = function (retain) {
browser.selectWithWait('#node-input-retain', retain);
}
module.exports = mqttOutNode;

View File

@@ -22,14 +22,30 @@ function httpinNode(id) {
nodePage.call(this, id);
}
util.inherits(httpinNode, nodePage);
httpinNode.prototype.setMethod = function(method) {
browser.selectWithWait('#node-input-method', method);
function setMethod(type) {
browser.selectByValue('#node-input-method', type);
}
httpinNode.prototype.setUrl = function(url) {
browser.setValue('#node-input-url', url);
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.setUrl = function(value) {
browser.setValue('#node-input-url', value);
}
module.exports = httpinNode;

View File

@@ -24,16 +24,36 @@ function httpRequestNode(id) {
util.inherits(httpRequestNode, nodePage);
httpRequestNode.prototype.setUrl = function(url) {
browser.setValue('#node-input-url', url);
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.setMethod = function(method) {
browser.selectWithWait('#node-input-method', method);
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.setReturn = function(ret) {
browser.selectWithWait('#node-input-ret', ret);
httpRequestNode.prototype.setRet = function(type) {
browser.clickWithWait('#node-input-ret');
var retTypeXPath = '//*[@id="node-input-ret"]/option[' + retType[type] + ']';
browser.clickWithWait(retTypeXPath);
}
module.exports = httpRequestNode;

View File

@@ -24,6 +24,13 @@ function changeNode(id) {
util.inherits(changeNode, nodePage);
var tType = {
"set": 1,
"change": 2,
"delete": 3,
"move": 4,
};
var totType = {
"msg": 1,
"flow": 2,
@@ -35,7 +42,6 @@ var totType = {
"bin": 8,
"date": 9,
"jsonata": 10,
"env": 11,
};
var ptType = {
@@ -44,8 +50,8 @@ var ptType = {
"global": 3,
};
function setT(t, index) {
browser.selectWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', t);
function setT(rule, index) {
browser.selectByValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', rule);
}
// 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(action) {
browser.selectWithWait('#node-input-action', action);
rangeNode.prototype.setAction = function(value) {
browser.selectByValue('#node-input-action', value);
}
rangeNode.prototype.setRange = function(minin, maxin, minout, maxout) {

View File

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

View File

@@ -1,35 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var nodePage = require("../../node_page");
function jsonNode(id) {
nodePage.call(this, id);
}
util.inherits(jsonNode, nodePage);
jsonNode.prototype.setAction = function (action) {
browser.setValue('node-input-action', action);
}
jsonNode.prototype.setProperty = function(property) {
browser.setValue('//*[@id="dialog-form"]/div[2]/div/div/input', property);
}
module.exports = jsonNode;

View File

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

View File

@@ -18,15 +18,12 @@ var injectNode = require('./core/core/20-inject_page');
var debugNode = require('./core/core/58-debug_page');
var templateNode = require('./core/core/80-template_page');
var functionNode = require('./core/core/80-function_page');
var mqttInNode = require('./core/io/10-mqttin_page');
var mqttOutNode = require('./core/io/10-mqttout_page');
var httpinNode = require('./core/io/21-httpin_page');
var httpResponseNode = require('./core/io/21-httpresponse_page');
var changeNode = require('./core/logic/15-change_page');
var rangeNode = require('./core/logic/16-range_page');
var httpRequestNode = require('./core/io/21-httprequest_page');
var htmlNode = require('./core/parsers/70-HTML_page');
var jsonNode = require('./core/parsers/70-JSON_page');
var fileinNode = require('./core/storage/50-filein_page');
@@ -34,11 +31,9 @@ var nodeCatalog = {
// input
"inject": injectNode,
"httpin": httpinNode,
"mqttIn":mqttInNode,
// output
"debug": debugNode,
"httpResponse": httpResponseNode,
"mqttOut": mqttOutNode,
// function
"function": functionNode,
"template": templateNode,
@@ -46,7 +41,6 @@ var nodeCatalog = {
"range": rangeNode,
"httpRequest": httpRequestNode,
"html": htmlNode,
"json":jsonNode,
// storage
"filein": fileinNode,
}

View File

@@ -38,12 +38,6 @@ 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

@@ -18,11 +18,9 @@ var idMap = {
// input
"inject": "#palette_node_inject",
"httpin": "#palette_node_http_in",
"mqttIn": "#palette_node_mqtt_in",
// output
"debug": "#palette_node_debug",
"httpResponse": "#palette_node_http_response",
"mqttOut": "#palette_node_mqtt_out",
// function
"function": "#palette_node_function",
"template": "#palette_node_template",
@@ -30,7 +28,6 @@ var idMap = {
"range": "#palette_node_range",
"httpRequest": "#palette_node_http_request",
"html": "#palette_node_html",
"json": "#palette_node_json",
// storage
"filein": "#palette_node_file_in",
};

View File

@@ -17,8 +17,8 @@
var should = require("should");
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/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.setOutput("headers");
debugNode.setTarget("msg", "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.setOutput("");
fileinNode.setFormat("");
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.setFunction("msg.payload = JSON.stringify(msg.req.cookies,null,4);\nreturn msg;");
functionNodeFormat.setCode("msg.payload = JSON.stringify(msg.req.cookies,null,4);");
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.setFunction('msg.cookies = { };\n msg.cookies["demo-"+(Math.floor(Math.random()*1000))] = Date.now();\nreturn msg;');
functionNodeAdd.setCode("msg.cookies = { };\n msg.cookies[\"demo-\"+(Math.floor(Math.random()*1000))] = Date.now();");
functionNodeAdd.clickOk();
changeNode.edit();
@@ -571,7 +571,7 @@ describe('cookbook', function() {
httpinNodeClear.clickOk();
functionNodeClear.edit();
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.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.clickOk();
httpinNodeClear.connect(functionNodeClear);

View File

@@ -1,228 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require('when');
var should = require("should");
var fs = require('fs-extra');
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var specUtil = require('../../pageobjects/util/spec_util_page');
var mqttConfig = require('../../pageobjects/nodes/core/io/10-mqttconfig_page.js');
var nodeWidth = 200;
var nodeHeight = 100;
var httpNodeRoot = "/api";
var mqttServer;
var mosca = require('mosca');
var moscaSettings = {
port: 1883,
persistence: {
// Needs for retaining messages.
factory: mosca.persistence.Memory
}
};
// https://cookbook.nodered.org/
describe('cookbook', function() {
beforeEach(function() {
workspace.deleteAllNodes();
});
before(function() {
browser.call(function() {
return new Promise(function(resolve, reject) {
mqttServer = new mosca.Server(moscaSettings, function() {
resolve();
});
});
});
helper.startServer();
});
after(function() {
browser.call(function() {
return new Promise(function(resolve, reject) {
mqttServer.close(function() {
resolve();
});
});
});
helper.stopServer();
});
describe('MQTT', function() {
it('Add an MQTT broker to prepare for UI test', function() {
var mqttOutNode = workspace.addNode("mqttOut", nodeWidth);
mqttOutNode.edit();
mqttConfig.edit();
mqttConfig.setServer("localhost");
mqttConfig.clickOk();
mqttOutNode.clickOk();
workspace.deploy();
});
it('Connect to an MQTT broker', function() {
var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut", nodeWidth);
var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight);
var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight);
injectNode.edit();
injectNode.setPayload("num", 22);
injectNode.clickOk();
mqttOutNode.edit();
mqttOutNode.setTopic("sensors/livingroom/temp");
mqttOutNode.clickOk();
injectNode.connect(mqttOutNode);
mqttInNode.edit();
mqttInNode.setTopic("sensors/livingroom/temp");
mqttInNode.setQoS("2");
mqttInNode.clickOk();
mqttInNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql('"22"');
});
// skip this case since it is same as other cases.
it.skip('Publish messages to a topic');
it('Set the topic of a published message', function() {
var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut", nodeWidth * 2);
injectNode.edit();
injectNode.setPayload("num", 22);
injectNode.setTopic("sensors/kitchen/temperature");
injectNode.clickOk();
mqttOutNode.edit();
mqttOutNode.clickOk();
injectNode.connect(mqttOutNode);
// The code for confirmation starts from here.
var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight);
var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight);
mqttInNode.edit();
mqttInNode.setTopic("sensors/kitchen/temperature");
mqttInNode.clickOk();
mqttInNode.connect(debugNode);
// The code for confirmation ends here.
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql('"22"');
});
it('Publish a retained message to a topic', function() {
var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut", nodeWidth);
injectNode.edit();
injectNode.setPayload("num", 22);
injectNode.clickOk();
mqttOutNode.edit();
mqttOutNode.setTopic("sensors/livingroom/temp");
mqttOutNode.setRetain("true");
mqttOutNode.clickOk();
injectNode.connect(mqttOutNode);
workspace.deploy();
debugTab.open();
injectNode.clickLeftButton();
debugTab.clearMessage();
// The code for confirmation starts from here.
var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight);
var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight);
mqttInNode.edit();
mqttInNode.setTopic("sensors/livingroom/temp");
mqttInNode.clickOk();
mqttInNode.connect(debugNode);
// The code for confirmation ends here.
workspace.deploy();
debugTab.open();
debugTab.getMessage().should.eql('"22"');
});
// skip this case since it is same as other cases.
it.skip('Subscribe to a topic');
it('Receive a parsed JSON message', function() {
var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut", nodeWidth);
var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight);
var jsonNode = workspace.addNode("json", nodeWidth, nodeHeight);
var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight);
injectNode.edit();
injectNode.setPayload("json", '{"sensor_id": 1234, "temperature": 13 }');
injectNode.clickOk();
mqttOutNode.edit();
mqttOutNode.setTopic("sensors/livingroom/temp");
mqttOutNode.clickOk();
injectNode.connect(mqttOutNode);
mqttInNode.edit();
mqttInNode.setTopic("sensors/#");
mqttInNode.setQoS("2");
mqttInNode.clickOk();
jsonNode.edit();
jsonNode.setProperty("payload");
jsonNode.clickOk();
mqttInNode.connect(jsonNode);
jsonNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql(['1234', '13']);
});
});
});

View File

@@ -19,8 +19,8 @@ var should = require("should");
var fs = require('fs-extra');
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/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.setRepeat("interval");
injectNode.setRepeatInterval(1);
injectNode.setTimeType("interval");
injectNode.setRepeat(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.setSelector("title");
htmlNode.setTag("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.setReturn("obj");
httpRequetNode.setRet("obj");
httpRequetNode.clickOk();
debugNode.edit();
debugNode.setOutput("payload.title");
debugNode.setTarget("msg", "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.setReturn("bin");
httpRequetNode.setRet("bin");
httpRequetNode.clickOk();
injectNode.connect(httpRequetNode);
@@ -413,11 +413,11 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 3);
functionNode.edit();
functionNode.setFunction('msg.payload = "data to post";\nreturn msg;');
functionNode.setCode("msg.payload = \"data to post\";");
functionNode.clickOk();
httpRequetNode.edit();
httpRequetNode.setMethod("POST");
httpRequetNode.setMethod("post");
var url = helper.url() + httpNodeRoot + "/set-header";
httpRequetNode.setUrl(url);
httpRequetNode.clickOk();

View File

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

View File

@@ -1216,7 +1216,7 @@ describe('function node', function() {
msg.should.have.property('payload', n1.id);
done();
});
n1.receive({payload:"foo",topic: "bar"});
n1.receive({payload:"foo",topicb: "bar"});
});
});
@@ -1230,7 +1230,7 @@ describe('function node', function() {
msg.should.have.property('payload', n1.name);
done();
});
n1.receive({payload:"foo",topic: "bar"});
n1.receive({payload:"foo",topicb: "bar"});
});
});
@@ -1249,36 +1249,6 @@ describe('function node', function() {
});
});
it('should allow accessing env vars', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = env.get('_TEST_FOO_'); return msg;"},
{id:"n2", type:"helper"}];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
delete process.env._TEST_FOO_;
n2.on("input", function(msg) {
try {
if (count === 0) {
msg.should.have.property('payload', undefined);
process.env._TEST_FOO_ = "hello";
count++;
n1.receive({payload:"foo",topic: "bar"});
} else {
msg.should.have.property('payload', "hello");
done();
}
} catch(err) {
done(err);
} finally {
delete process.env._TEST_FOO_;
}
});
n1.receive({payload:"foo",topic: "bar"});
});
});
describe('Logger', function () {
it('should log an Info Message', function (done) {
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.log('test');"}];

View File

@@ -15,8 +15,7 @@
**/
var should = require("should");
var rpiNode = require("../../../../nodes/core/hardware/36-rpi-gpio.js");
var statusNode = require("../../../../nodes/core/core/25-status.js");
var rpi = require("../../../../nodes/core/hardware/36-rpi-gpio.js");
var helper = require("node-red-node-test-helper");
var fs = require("fs");
@@ -51,7 +50,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(rpiNode, flow, function() {
helper.load(rpi, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio in');
try {
@@ -70,7 +69,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(rpiNode, flow, function() {
helper.load(rpi, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio out');
try {
@@ -87,68 +86,4 @@ 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");
var count = 0;
n3.on("input", function(msg) {
// Only check the first status message received as it may get a
// 'closed' status as the test is tidied up.
if (count === 0) {
count++;
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:3},
var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:1},
{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 === 5) {
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:3},
var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:1},
{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 === 1) {
if (c === 3) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("a",1);
@@ -649,20 +649,11 @@ describe('JOIN node', function() {
}
catch(e) { done(e) }
}
if (c === 2) {
if (c === 5) {
try {
msg.should.have.property("payload");
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);
msg.payload.should.have.property("b",2);
msg.payload.should.have.property("c",1);
done();
}
catch(e) { done(e) }
@@ -673,11 +664,8 @@ 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:{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"});
n1.receive({payload:{b:2}, topic:"e"});
n1.receive({payload:{c:1}, topic:"f"});
});
});

View File

@@ -66,25 +66,13 @@ describe('SORT node', function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
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);
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]);
}
done();
});
var msg = {};
msg[target] = data_in;
@@ -105,34 +93,6 @@ 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";
@@ -147,7 +107,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 = indexOf(data_out, data);
var index = data_out.indexOf(data);
msg.parts.should.have.property("index", index);
count++;
if (count === data_out.length) {
@@ -176,6 +136,7 @@ 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"}];
@@ -278,19 +239,6 @@ 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"},
@@ -320,7 +268,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:"seq", seqKey:"$globalContext(payload)", seqKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"},
var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$globalContext(payload)", msgKeyType:"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" ];
@@ -334,20 +282,15 @@ describe('SORT node', function() {
n1.context()["global"].set("third","3");
n1.context()["global"].set("fourth","2");
n2.on("input", function(msg) {
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);
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();
}
});
var len = data_in.length;
@@ -389,7 +332,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:"seq", seqKey:"$flowContext(payload,\"memory\")", seqKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"},
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"},
{id:"n2", type:"helper",z:"flow"},
{id:"flow", type:"tab"}];
var data_in = [ "first", "second", "third", "fourth" ];

View File

@@ -265,23 +265,6 @@ 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"}];
@@ -298,22 +281,6 @@ 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"}];
@@ -338,78 +305,6 @@ 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({log:{debug:function(){}},i18n:{registerMessageCatalog:function(){}},events:{emit:function(){}},settings:{coreNodesDir:moduleDir}});
localfilesystem.init({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'];