1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'master' into context-store-logging

This commit is contained in:
Nick O'Leary 2018-07-20 20:23:19 +01:00 committed by GitHub
commit bf5d36d6bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1352 additions and 143 deletions

View File

@ -1,3 +1,64 @@
#### 0.19: Milestone Release
Editor
- Add Context data sidebar
- Index all node properties when searching Fixes #1446
- Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779
- Prevent horizontal scroll when palette name cannot wrap
- Ignore middle-click on node/ports to enable panning
- Better wire layout when looping back
- fix appearence of retry button of remote branch management dialog
- Handle releasing ctrl when using quick-add node dialog
- Add $env function to JSONata expressions
- Widen support for env var to use ${} or $() syntax
- Add env-var support to TypedInput
- Show unknown node properties in info tab
- Add node icon picker widget
- Only edit nodes on dbl click on primary button with no modifiers
- Allow subflows to be put in any palette category
- Add flow navigator widget
- Cache flow library result to improve response time Fixes #1753
- Add middle-button-drag to pan the workspace
- allow multi-line category name in editor
- Redesign sidebar tabs
- Do not disable the export-clipboard menu option with empty selection
Nodes
- Change: Ensure runtime errors in Change node can be caught Fixes #1769
- File: Add output to File Out node
- Function: add expandable JavaScript editor pane
- Function: allow id and name reference in function node code (#1731)
- HTTP Request: Move to request module
- HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278
- Join: accumulate top level properties
- Join: allow environment variable as reduce init value
- JSON: add JSON schema validation via msg.schema
- Pi: Let nrgpio code work with python 3
- Pi: let Pi nodes be visible/editable on all platforms
- Switch: add isEmpty rule
- TCP: queue messages while connecting; closes #1414
- TLS: Add servername option to TLS config node for SNI Fixes #1805
- UDP: Don't accidentally re-use udp port when set to not do so
Persistent Context
- Add persistable context option
- Add default memory store
- Add file-based context store
- Add async mode to evaluateJSONataExpression
- Update RED.util.evaluateNodeProperty to support context stores
Runtime
- Support flow.disabled and .info in /flow API
- Node errors should be Strings not Errors Fixes #1781
- Add detection of connection timeout in git communication Fixes #1770
- Handle loading empty nodesDir
- Add 'private' property to userDir generated package.json
- Add RED.require to allow nodes to access other modules
#### 0.18.7: Maintenance Release
Editor Fixes

View File

@ -706,7 +706,7 @@ RED.editor = (function() {
pickerBackground.on("mousedown", hide);
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(picker);
searchInput = $('<input type="text">').attr("placeholder","Search icons").appendTo(searchDiv).searchBox({
searchInput = $('<input type="text">').attr("placeholder",RED._("editor.searchIcons")).appendTo(searchDiv).searchBox({
delay: 50,
change: function() {
var searchTerm = $(this).val().trim();
@ -730,7 +730,7 @@ RED.editor = (function() {
var iconList = $('<div class="red-ui-icon-list">').appendTo(picker);
var metaRow = $('<div class="red-ui-icon-meta"></div>').appendTo(picker);
var summary = $('<span>').appendTo(metaRow);
var resetButton = $('<button class="editor-button editor-button-small">use default</button>').appendTo(metaRow).click(function(e) {
var resetButton = $('<button class="editor-button editor-button-small">'+RED._("editor.useDefault")+'</button>').appendTo(metaRow).click(function(e) {
e.preventDefault();
hide();
done(null);

View File

@ -162,7 +162,7 @@
$("#node-input-op1").typedInput({
default: 'str',
typeField: $("#node-input-op1type"),
types:['flow','global','str','num','bool','json',
types:['flow','global','str','num','bool','json','bin','date','env',
optionPayload,
optionNothing
]
@ -170,7 +170,7 @@
$("#node-input-op2").typedInput({
default: 'str',
typeField: $("#node-input-op2type"),
types:['flow','global','str','num','bool','json',
types:['flow','global','str','num','bool','json','bin','date','env',
optionOriginalPayload,
optionLatestPayload,
optionNothing

View File

@ -26,7 +26,7 @@
<dt class="optional">kill <span class="property-type">文字列</span></dt>
<dd>execードのプロセスに対して送るシグナルの種別を指定します</dd>
<dt class="optional">pid <span class="property-type">数値|文字列</span></dt>
<dd>シグナル送信対象のexecードのプロセスID</dd>
<dd>シグナル送信対象のexecードのプロセスIDを指定します</dd>
</dl>
<h3>出力</h3>
@ -60,13 +60,12 @@
</ol>
<h3>詳細</h3>
<p>デフォルトでは、<code>exec</code>システムコールを用いてコマンドを呼び出してその完了を待ち、出力を返します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
<p><code>spawn</code>を使ってコマンドを実行し、
標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
<p><code>spawn</code>を使ってコマンドを実行し、標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>という返却値を返します。</p>
<p>エラー発生時には、3番目の端子の<code>msg.payload</code><code>message</code><code>signal</code>など付加情報を返します。</p>
<p>実行対象のコマンドはノード設定で定義します。<code>msg.payload</code>や追加引数をコマンドに追加することもできます。</p>
<p>コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- <code>"This is a single parameter"</code></p>
<p>コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- <code>"これは一つのパラメータです"</code></p>
<p>返却する<code>payload</code>は通常<i>文字列</i>ですが、UTF8文字以外が存在すると<i>バッファ</i>となります。</p>
<p>ードが実行中の場合、ステータスアイコンとPIDを表示します。この状態変化は<code>status</code>ノードで検知できます。</p>
<p>ードが実行中の場合、ステータスアイコンとPIDを表示します。この状態変化は<code>Status</code>ノードで検知できます。</p>
<h4>プロセスの停止</h4>
<p><code>msg.kill</code>を受信すると、実行中のプロセスを停止することができます。<code>msg.kill</code>には送出するシグナルの種別を指定します。例えば、<code>SIGINT</code><code>SIGQUIT</code><code>SIGHUP</code>などです。空の文字列を指定した場合には、<code>SIGTERM</code>を指定したものとみなします。</p>
<p>ードが1つ以上のプロセスを実行している場合、<code>msg.pid</code>に停止対象のPIDを指定しなければなりません。</p>

View File

@ -30,7 +30,9 @@
<dt class="optional">payload</dt>
<dd>リクエストボディとして送るデータ</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd><code>true</code>をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。</dd>
<dd><code>false</code>をセットすると、自己署名証明書を使用するhttpsサイトへのリクエストを許可します。</dd>
<dt class="optional">followRedirects</dt>
<dd><code>false</code>をセットすると、リダイレクトを行いません。デフォルトは<code>true</code>です。</dd>
</dl>
<h3>出力</h3>
<dl class="message-properties">

View File

@ -29,7 +29,11 @@
<li><b>その他</b> - これより前のルールにマッチするものがなかった場合に適用</li>
</ol>
<h3>注釈</h3>
<p><code>is true/false</code><code>is null</code>のルールは、型に対して厳密な比較を行います。型変換した上での比較はしません。</p>
<p><code>is empty</code>のルールは、長さ0の文字列・配列・バッファ、またはプロパティを持たないオブジェクトを出力します。<code>null</code><code>undefined</code>は出力しません。</p>
<h3>メッセージ列の扱い</h3>
<p>switchードは入力メッセージの列に関する情報を保持する<code>msg.parts</code>をデフォルトでは変更しません。</p>
<p><b>メッセージ列の補正</b>」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。<code>nodeMessageBufferMaxLength</code>を設定すると、蓄積するメッセージ数を制限できます。</p>
<p><b>メッセージ列の補正</b>」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。<b>settings.js</b><code>nodeMessageBufferMaxLength</code>を設定すると、蓄積するメッセージ数を制限できます。</p>
</script>

View File

@ -30,5 +30,5 @@
</dd>
</dl>
<h4>メッセージの蓄積</h4>
<p>このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。<code>nodeMessageBufferMaxLength</code>を指定することで蓄積するメッセージの最大値を制限することができます。</p>
<p>このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。<b>settings.js</b><code>nodeMessageBufferMaxLength</code>を指定することで蓄積するメッセージの最大値を制限することができます。</p>
</script>

View File

@ -20,6 +20,8 @@
<dl class="message-properties">
<dt>payload<span class="property-type">オブジェクト | 文字列</span></dt>
<dd>JavaScriptオブジェクトもしくはJSON文字列</dd>
<dt>schema<span class="property-type">オブジェクト</span></dt>
<dd>JSONの検証に利用するJSONスキーマ。設定されていない場合は検証を行いません。</dd>
</dl>
<h3>出力</h3>
<dl class="message-properties">
@ -30,9 +32,12 @@
<li>入力がJavaScriptオブジェクトの場合、JSON文字列に変換します。JSON文字列は整形することも可能です。</li>
</ul>
</dd>
<dt>schemaError<span class="property-type">配列</span></dt>
<dd>JSONの検証でエラーが発生した場合、Catchードを利用し、エラーを配列として<code>schemaError</code>プロパティから取得することができます。</dd>
</dl>
<h3>詳細</h3>
<p>デフォルトの変換対象は<code>msg.payload</code>ですが、他のメッセージプロパティを変換対象とすることも可能です。</p>
<p>双方向の変換を自動選択するのではなく、特定の変換のみ行うように設定できます。この機能は、例えば、<code>HTTP In</code>ードに対するリクエストがcontent-typeを正しく設定していない場合であっても、JSONードによる変換結果がJavaScriptオブジェクトであることを保証するために利用します。</p>
<p>JSON文字列への変換が指定されている場合、受信した文字列に対してさらなるチェックは行いません。すなわち、文字列がJSONとして正しいかどうかの検査や、整形オプションを指定していたとしても整形処理を実施しません。</p>
<p>JSONスキーマの詳細については、<a href="http://json-schema.org/latest/json-schema-validation.html">こちら</a>を参照してください。</p>
</script>

View File

@ -21,6 +21,8 @@
<dt class="optional">filename <span class="property-type">文字列</span></dt>
<dd>対象ファイル名をノードに設定していない場合、このプロパティでファイルを指定できます</dd>
</dl>
<h3>出力</h3>
<p>書き込みの完了時、入力メッセージを出力端子に送出します。</p>
<h3>詳細</h3>
<p>入力メッセージのペイロードをファイルの最後に追記します。改行(\n)を各データの最後に追加することもできます。</p>
<p><code>msg.filename</code>を使う場合、書き込みを行う毎にファイルをクローズします。より良い性能を得るためにはファイル名をノードに設定してください。</p>

View File

@ -147,7 +147,7 @@
}
if ((rule.t === 'btwn') || (rule.t === 'index')) {
label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'else' ) {
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'empty' && rule.t !== 'nempty' && rule.t !== 'else' ) {
label += " "+getValueLabel(rule.vt,rule.v);
}
return label;
@ -199,7 +199,7 @@
} else if (type === "istype") {
typeField.typedInput("width",(newWidth-selectWidth-70));
} else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else") {
// valueField.hide();
} else {
valueField.typedInput("width",(newWidth-selectWidth-70));
@ -295,7 +295,7 @@
numValueField.typedInput('hide');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else") {
valueField.typedInput('hide');
typeValueField.typedInput('hide');
}
@ -396,7 +396,7 @@
var rule = $(this);
var type = rule.find("select").val();
var r = {t:type};
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else")) {
if ((type === "btwn") || (type === "index")) {
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');

View File

@ -34,7 +34,7 @@ module.exports = function(RED) {
'empty': function(a) {
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
return a.length === 0;
} else if (typeof a === 'object') {
} else if (typeof a === 'object' && a !== null) {
return Object.keys(a).length === 0;
}
return false;
@ -42,7 +42,7 @@ module.exports = function(RED) {
'nempty': function(a) {
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
return a.length !== 0;
} else if (typeof a === 'object') {
} else if (typeof a === 'object' && a !== null) {
return Object.keys(a).length !== 0;
}
return false;

View File

@ -143,7 +143,7 @@ module.exports = function(RED) {
if (rule.fromt === "msg") {
resolve(RED.util.getMessageProperty(msg,rule.from));
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
var contextKey = RED.util.parseContextStore(rule.from);
var contextKey = RED.util.parseContextStore(rule.from);
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
if (err) {
reject(err);
@ -166,12 +166,10 @@ module.exports = function(RED) {
try {
fromRE = new RegExp(fromRE, "g");
} catch (e) {
reject(new Error(RED._("change.errors.invalid-from",{error:e.message})));
return;
return Promise.reject(new Error(RED._("change.errors.invalid-from",{error:e.message})));
}
} else {
reject(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
return;
return Promise.reject(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
}
return {
fromType,

View File

@ -37,6 +37,8 @@
<dt class="optional">filename <span class="property-type">string</span></dt>
<dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
</dl>
<h3>Output</h3>
<p>On completion of write, input message is sent to output port.</p>
<h3>Details</h3>
<p>Each message payload will be added to the end of the file, optionally appending
a newline (\n) character between each one.</p>

View File

@ -265,6 +265,8 @@
"settingIcon": "Icon",
"noDefaultLabel": "none",
"defaultLabel": "use default label",
"searchIcons": "Search icons",
"useDefault": "use default",
"errors": {
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it"
}

View File

@ -264,6 +264,8 @@
"settingIcon": "アイコン",
"noDefaultLabel": "なし",
"defaultLabel": "既定の名前を使用",
"searchIcons": "アイコンを検索",
"useDefault": "デフォルトを使用",
"errors": {
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします"
}
@ -455,8 +457,14 @@
"filtered": "__count__ 個が無効"
},
"context": {
"name": "コンテキスト",
"label": "コンテキスト"
"name": "コンテキストデータ",
"label": "コンテキストデータ",
"none": "選択されていません",
"refresh": "読み込みのため更新してください",
"empty": "データが存在しません",
"node": "Node",
"flow": "Flow",
"global": "Global"
},
"palette": {
"name": "パレットの管理",
@ -637,6 +645,9 @@
"eval": "表現評価エラー:\n __message__"
}
},
"jsEditor": {
"title": "JavaScriptエディタ"
},
"jsonEditor": {
"title": "JSONエディタ",
"format": "JSONフォーマット"

View File

@ -203,6 +203,8 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
var storagePath = getStoragePath(this.storageBaseDir ,scope);
fs.outputFile(storagePath + ".json", JSON.stringify(newContext, undefined, 4), "utf8").catch(function(err) {
});
} else if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
} else {
this._set(scope,key,value,callback);
}

View File

@ -27,7 +27,7 @@ util.inherits(injectNode, nodePage);
var payloadType = {
"flow": 1,
"global": 2,
"string": 3,
"str": 3,
"num": 4,
"bool": 5,
"json": 6,
@ -43,6 +43,13 @@ var timeType = {
"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")]');

View File

@ -24,4 +24,23 @@ function debugNode(id) {
util.inherits(debugNode, nodePage);
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');
// 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', value);
}
}
module.exports = debugNode;

View File

@ -0,0 +1,35 @@
/**
* 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 functionNode(id) {
nodePage.call(this, id);
}
util.inherits(functionNode, nodePage);
functionNode.prototype.setCode = function(value) {
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)]);
}
}
module.exports = functionNode;

View File

@ -0,0 +1,55 @@
/**
* 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 templateNode(id) {
nodePage.call(this, 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.setFormat = function(type) {
browser.selectByValue('#node-input-format', type);
}
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<value.length; i++) {
browser.keys([value.charAt(i)]);
}
browser.keys(['Control', 'Shift', 'End', 'Shift', 'Control']);
browser.keys(['Delete']);
}
module.exports = templateNode;

View File

@ -0,0 +1,51 @@
/**
* 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 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.setUrl = function(value) {
browser.setValue('#node-input-url', value);
}
module.exports = httpinNode;

View File

@ -0,0 +1,59 @@
/**
* 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 httpRequestNode(id) {
nodePage.call(this, 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.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.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

@ -0,0 +1,27 @@
/**
* 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 httpResponseNode(id) {
nodePage.call(this, id);
}
util.inherits(httpResponseNode, nodePage);
module.exports = httpResponseNode;

View File

@ -24,14 +24,58 @@ function changeNode(id) {
util.inherits(changeNode, nodePage);
var tType = {
"set": 1,
"change": 2,
"delete": 3,
"move": 4,
};
var totType = {
"msg": 1,
"flow": 2,
"global": 3,
"str": 4,
"num": 5,
"bool": 6,
"json": 7,
"bin": 8,
"date": 9,
"jsonata": 10,
};
var ptType = {
"msg": 1,
"flow": 2,
"global": 3,
};
function setT(rule, index) {
browser.selectByValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', rule);
}
changeNode.prototype.ruleSet = function(to, index) {
// It is better to create a function whose input value is the object type in the future,
changeNode.prototype.ruleSet = function(p, pt, to, tot, index) {
index = index ? index : 1;
setT("set", index);
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to);
if (pt) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
var num = 5 * index + 6;
var ptXPath = '/html/body/div[' + num + ']/a[' + ptType[pt] + ']';
browser.clickWithWait(ptXPath);
}
if (p) {
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
}
if (tot) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]');
var num = 5 * index + 7;
var totXPath = '/html/body/div[' + num + ']/a[' + totType[tot] + ']';
browser.clickWithWait(totXPath);
}
if (to) {
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input' , to);
}
}
changeNode.prototype.ruleDelete = function(index) {

View File

@ -0,0 +1,31 @@
/**
* 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 htmlNode(id) {
nodePage.call(this, id);
}
util.inherits(htmlNode, nodePage);
htmlNode.prototype.setTag = function(value) {
browser.setValue('#node-input-tag', value);
}
module.exports = htmlNode;

View File

@ -16,17 +16,30 @@
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 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 nodeCatalog = {
// input
"inject": injectNode,
"httpin": httpinNode,
// output
"debug": debugNode,
"httpResponse": httpResponseNode,
// function
"function": functionNode,
"template": templateNode,
"change": changeNode,
"range": rangeNode,
"httpRequest": httpRequestNode,
"html": htmlNode,
}
function create(type, id) {

View File

@ -17,11 +17,17 @@
var idMap = {
// input
"inject": "#palette_node_inject",
"httpin": "#palette_node_http_in",
// output
"debug": "#palette_node_debug",
"httpResponse": "#palette_node_http_response",
// function
"function": "#palette_node_function",
"template": "#palette_node_template",
"change": "#palette_node_change",
"range": "#palette_node_range",
"httpRequest": "#palette_node_http_request",
"html": "#palette_node_html",
};
function getId(type) {

View File

@ -24,6 +24,8 @@ var workspace = require('../../pageobjects/workspace/workspace_page');
var specUtil = require('../../pageobjects/util/spec_util_page');
var nodeWidth = 200;
var nodeHeight = 100;
var httpNodeRoot = "/api";
// https://cookbook.nodered.org/
describe('cookbook', function() {
@ -46,7 +48,7 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 2);
changeNode.edit();
changeNode.ruleSet("Hello World!");
changeNode.ruleSet("payload", "msg", "Hello World!");
changeNode.clickOk();
injectNode.connect(changeNode);
@ -150,7 +152,7 @@ describe('cookbook', function() {
var debugNode = workspace.addNode("debug", nodeWidth * 2);
injectNode.edit();
injectNode.setPayload("string", "Started!")
injectNode.setPayload("str", "Started!")
injectNode.setOnce(true);
injectNode.clickOk();
injectNode.connect(debugNode);
@ -185,4 +187,270 @@ describe('cookbook', function() {
// skip this case since it needs up to one minite.
it.skip('trigger a flow at a specific time');
});
describe('HTTP requests', function() {
it('simple get request', function() {
var injectNode = workspace.addNode("inject");
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth);
var htmlNode = workspace.addNode("html", nodeWidth * 2);
var debugNode = workspace.addNode("debug", nodeWidth * 3);
httpRequetNode.edit();
httpRequetNode.setMethod("get");
httpRequetNode.setUrl(helper.url());
httpRequetNode.clickOk();
htmlNode.edit();
htmlNode.setTag("title");
htmlNode.clickOk();
injectNode.connect(httpRequetNode);
httpRequetNode.connect(htmlNode);
htmlNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.be.equal('"Node-RED"');
});
it('set the URL of a request', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth * 1.5);
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth * 2.5);
var debugNode = workspace.addNode("debug", nodeWidth * 3.5);
injectNode.edit();
injectNode.setPayload("str", helper.url());
injectNode.clickOk();
changeNode.edit();
changeNode.ruleSet("url", "msg", "payload", "msg");
changeNode.clickOk();
injectNode.connect(changeNode);
changeNode.connect(httpRequetNode);
httpRequetNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.containEql('<title>Node-RED</title>');
});
it('set the URL of a request using a template', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth * 1.5);
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth * 2.5);
var debugNode = workspace.addNode("debug", nodeWidth * 3.5);
injectNode.edit();
injectNode.setPayload("str", 'settings');
injectNode.clickOk();
changeNode.edit();
changeNode.ruleSet("query", "msg", "payload", "msg");
changeNode.clickOk();
httpRequetNode.edit();
httpRequetNode.setUrl(helper.url() + "/{{{query}}}");
httpRequetNode.clickOk();
injectNode.connect(changeNode);
changeNode.connect(httpRequetNode);
httpRequetNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.containEql('httpNodeRoot');
});
it('set the query string parameters', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth);
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth * 2);
var debugNode = workspace.addNode("debug", nodeWidth * 3);
injectNode.edit();
injectNode.setPayload("str", 'Nick');
injectNode.clickOk();
changeNode.edit();
changeNode.ruleSet("query", "msg", "payload", "msg");
changeNode.clickOk();
httpRequetNode.edit();
httpRequetNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}');
httpRequetNode.clickOk();
injectNode.connect(changeNode);
changeNode.connect(httpRequetNode);
httpRequetNode.connect(debugNode);
// The code for confirmation starts from here.
var httpinNode = workspace.addNode("httpin", 0, nodeHeight);
var templateNode = workspace.addNode("template", nodeWidth, nodeHeight);
var httpResponseNode = workspace.addNode("httpResponse", nodeWidth * 2, nodeHeight);
httpinNode.edit();
httpinNode.setMethod("get");
httpinNode.setUrl("/set-query");
httpinNode.clickOk();
templateNode.edit();
templateNode.setSyntax("mustache");
templateNode.setFormat("handlebars");
templateNode.setTemplate("Hello {{req.query.q}}");
templateNode.clickOk();
httpinNode.connect(templateNode);
templateNode.connect(httpResponseNode);
// The code for confirmation ends here.
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql('"Hello Nick"');
});
it('get a parsed JSON response', function() {
var injectNode = workspace.addNode("inject");
var changeNode_setPost = workspace.addNode("change", nodeWidth);
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth * 2);
var debugNode = workspace.addNode("debug", nodeWidth * 3);
injectNode.edit();
injectNode.setPayload("str", "json-response");
injectNode.clickOk();
changeNode_setPost.edit();
changeNode_setPost.ruleSet("post", "msg", "payload", "msg");
changeNode_setPost.clickOk();
httpRequetNode.edit();
httpRequetNode.setMethod("get");
var url = helper.url() + httpNodeRoot + "/{{post}}";
httpRequetNode.setUrl(url);
httpRequetNode.setRet("obj");
httpRequetNode.clickOk();
debugNode.edit();
debugNode.setTarget("msg", "payload.title");
debugNode.clickOk();
injectNode.connect(changeNode_setPost);
changeNode_setPost.connect(httpRequetNode);
httpRequetNode.connect(debugNode);
// The code for confirmation starts from here.
var httpinNode = workspace.addNode("httpin", 0, nodeHeight);
var templateNode = workspace.addNode("template", nodeWidth * 1.5, nodeHeight);
var changeNode_setHeader = workspace.addNode("change", nodeWidth * 2.5, nodeHeight);
var httpResponseNode = workspace.addNode("httpResponse", nodeWidth * 3.5, nodeHeight);
httpinNode.edit();
httpinNode.setMethod("get");
httpinNode.setUrl("/json-response");
httpinNode.clickOk();
templateNode.edit();
templateNode.setSyntax("mustache");
templateNode.setFormat("handlebars");
templateNode.setTemplate("{\"title\": \"Hello\"}");
templateNode.clickOk();
changeNode_setHeader.edit();
changeNode_setHeader.ruleSet("headers", "msg", "{\"content-type\":\"application/json\"}", "json");
changeNode_setHeader.clickOk();
httpinNode.connect(templateNode);
templateNode.connect(changeNode_setHeader);
changeNode_setHeader.connect(httpResponseNode);
// The code for confirmation ends here.
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql('"Hello"');
});
it('get a binary response', function() {
var injectNode = workspace.addNode("inject");
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth);
var debugNode = workspace.addNode("debug", nodeWidth * 2);
httpRequetNode.edit();
httpRequetNode.setMethod("get");
httpRequetNode.setUrl(helper.url() + "/settings");
httpRequetNode.setRet("bin");
httpRequetNode.clickOk();
injectNode.connect(httpRequetNode);
httpRequetNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']);
});
it('set a request header', function() {
var injectNode = workspace.addNode("inject");
var functionNode = workspace.addNode("function", nodeWidth);
var httpRequetNode = workspace.addNode("httpRequest", nodeWidth * 2);
var debugNode = workspace.addNode("debug", nodeWidth * 3);
functionNode.edit();
functionNode.setCode("msg.payload = \"data to post\";");
functionNode.clickOk();
httpRequetNode.edit();
httpRequetNode.setMethod("post");
var url = helper.url() + httpNodeRoot + "/set-header";
httpRequetNode.setUrl(url);
httpRequetNode.clickOk();
injectNode.connect(functionNode);
functionNode.connect(httpRequetNode);
httpRequetNode.connect(debugNode);
// The code for confirmation starts from here.
var httpinNode = workspace.addNode("httpin", 0, nodeHeight);
var templateNode = workspace.addNode("template", nodeWidth * 1.5, nodeHeight);
var httpResponseNode = workspace.addNode("httpResponse", nodeWidth * 2.5, nodeHeight);
httpinNode.edit();
httpinNode.setMethod("post");
httpinNode.setUrl("/set-header");
httpinNode.clickOk();
templateNode.edit();
templateNode.setSyntax("mustache");
templateNode.setFormat("handlebars");
templateNode.setTemplate("{{ payload }}");
templateNode.clickOk();
httpinNode.connect(templateNode);
templateNode.connect(httpResponseNode);
// The code for confirmation ends here.
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.eql('"data to post"');
});
});
});

View File

@ -36,6 +36,9 @@ describe('template node', function() {
function initContext(done) {
Context.init({
contextStorage: {
memory0: { // do not use (for excluding effect fallback)
module: "memory"
},
memory1: {
module: "memory"
},
@ -337,7 +340,7 @@ describe('template node', function() {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in flow context
n2.context().flow.get("payload", "memory", function (err, val) {
n2.context().flow.get("payload", "memory1", function (err, val) {
val.should.equal("payload=foo");
done();
});
@ -375,7 +378,7 @@ describe('template node', function() {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in global context
n2.context().global.get("payload", "memory", function (err, val) {
n2.context().global.get("payload", "memory1", function (err, val) {
val.should.equal("payload=foo");
done();
});

View File

@ -35,6 +35,9 @@ describe('trigger node', function() {
},
memory1: {
module: "memory"
},
memory2: {
module: "memory"
}
}
});
@ -96,6 +99,74 @@ describe('trigger node', function() {
});
});
function basicTest(type, val, rval) {
it('should output 1st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
delete process.env[val];
done();
}
catch(err) { done(err); }
});
n1.emit("input", {payload:null});
});
});
it('should output 2st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.property("payload", "foo");
c++;
}
else {
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
delete process.env[val];
done();
}
}
catch(err) { done(err); }
});
n1.emit("input", {payload:null});
});
});
}
basicTest("num", 10);
basicTest("str", "10");
basicTest("bool", true);
var val_json = '{ "x":"vx", "y":"vy", "z":"vz" }';
basicTest("json", val_json, JSON.parse(val_json));
var val_buf = "[1,2,3,4,5]";
basicTest("bin", val_buf, Buffer.from(JSON.parse(val_buf)));
basicTest("env", "NR-TEST", "env-val");
it('should output 1 then 0 when triggered (default)', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
@ -336,8 +407,8 @@ describe('trigger node', function() {
});
it('should be able to return things from persistable flow and global context variables', function (done) {
var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory0)::foo", "op1type": "flow",
"op2": "#:(memory0)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow",
"op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
@ -360,8 +431,8 @@ describe('trigger node', function() {
var context = n1.context();
var flow = context.flow;
var global = context.global;
flow.set("foo", "foo", "memory0", function (err) {
global.set("bar", "bar", "memory0", function (err) {
flow.set("foo", "foo", "memory1", function (err) {
global.set("bar", "bar", "memory1", function (err) {
n1.emit("input", { payload: null });
});
});
@ -372,8 +443,8 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory0)::val", "op1type": "global",
"op2": "#:(memory1)::val", "op2type": "global"
"op1": "#:(memory1)::val", "op1type": "global",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
@ -399,8 +470,8 @@ describe('trigger node', function() {
}
});
var global = n1.context().global;
global.set("val", "foo", "memory0", function (err) {
global.set("val", "bar", "memory1", function (err) {
global.set("val", "foo", "memory1", function (err) {
global.set("val", "bar", "memory2", function (err) {
n1.emit("input", { payload: null });
});
});
@ -411,8 +482,8 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory0)::val", "op1type": "flow",
"op2": "#:(memory1)::val", "op2type": "flow"
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "flow"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
@ -438,8 +509,8 @@ describe('trigger node', function() {
}
});
var flow = n1.context().flow;
flow.set("val", "foo", "memory0", function (err) {
flow.set("val", "bar", "memory1", function (err) {
flow.set("val", "foo", "memory1", function (err) {
flow.set("val", "bar", "memory2", function (err) {
n1.emit("input", { payload: null });
});
});
@ -450,8 +521,8 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow & global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory0)::val", "op1type": "flow",
"op2": "#:(memory1)::val", "op2type": "global"
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
@ -479,8 +550,8 @@ describe('trigger node', function() {
var context = n1.context();
var flow = context.flow;
var global = context.flow;
flow.set("val", "foo", "memory0", function (err) {
global.set("val", "bar", "memory1", function (err) {
flow.set("val", "foo", "memory1", function (err) {
global.set("val", "bar", "memory2", function (err) {
n1.emit("input", { payload: null });
});
});

View File

@ -379,7 +379,7 @@ describe('switch Node', function() {
singularSwitchTest("empty", true, false, undefined, done);
});
it('should check if payload is empty (0)', function(done) {
singularSwitchTest("empty", true, false, null, done);
singularSwitchTest("empty", true, false, 0, done);
});
it('should check if payload is not empty (string)', function(done) {
@ -413,7 +413,7 @@ describe('switch Node', function() {
singularSwitchTest("nempty", true, false, undefined, done);
});
it('should check if payload is not empty (0)', function(done) {
singularSwitchTest("nempty", true, false, null, done);
singularSwitchTest("nempty", true, false, 0, done);
});

View File

@ -1272,6 +1272,25 @@ describe('change Node', function() {
});
});
it('reports invalid fromValue', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"null","fromt":"msg","to":"abc","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "change";
});
logEvents.should.have.length(1);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'changeNode1');
done();
},25);
changeNode1.receive({payload:"",null:null});
});
});
describe('env var', function() {
before(function() {
process.env.NR_TEST_A = 'foo';

View File

@ -53,75 +53,143 @@ describe('file Nodes', function() {
});
it('should write to a file', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true}];
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
n1.emit("input", {payload:"test"});
setTimeout(function() {
var n2 = helper.getNode("helperNode1");
n2.on("input", function(msg) {
var f = fs.readFileSync(fileToTest);
f.should.have.length(4);
fs.unlinkSync(fileToTest);
msg.should.have.property("payload", "test");
done();
},wait);
});
n1.receive({payload:"test"});
});
});
it('should append to a file and add newline', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}];
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
try {
fs.unlinkSync(fileToTest);
}catch(err) {}
} catch(err) {
}
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
n1.emit("input", {payload:"test2"}); // string
var n2 = helper.getNode("helperNode1");
var count = 0;
var data = ["test2", true, 999, [2]];
n2.on("input", function (msg) {
msg.should.have.property("payload", data[count]);
if (count === 3) {
var f = fs.readFileSync(fileToTest).toString();
if (os.type() !== "Windows_NT") {
f.should.have.length(19);
f.should.equal("test2\ntrue\n999\n[2]\n");
}
else {
f.should.have.length(23);
f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n");
}
done();
}
count++;
});
n1.receive({payload:"test2"}); // string
setTimeout(function() {
n1.emit("input", {payload:true}); // boolean
n1.receive({payload:true}); // boolean
},30);
setTimeout(function() {
n1.emit("input", {payload:999}); // number
n1.receive({payload:999}); // number
},60);
setTimeout(function() {
n1.emit("input", {payload:[2]}); // object (array)
n1.receive({payload:[2]}); // object (array)
},90);
setTimeout(function() {
var f = fs.readFileSync(fileToTest).toString();
if (os.type() !== "Windows_NT") {
f.should.have.length(19);
f.should.equal("test2\ntrue\n999\n[2]\n");
}
else {
f.should.have.length(23);
f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n");
}
done();
},wait);
});
});
it('should append to a file after it has been deleted ', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false}];
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
try {
fs.unlinkSync(fileToTest);
} catch(err) {}
} catch(err) {
}
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
// Send two messages to the file
n1.emit("input", {payload:"one"});
n1.emit("input", {payload:"two"});
setTimeout(function() {
var n2 = helper.getNode("helperNode1");
var data = ["one", "two", "three", "four"];
var count = 0;
n2.on("input", function (msg) {
msg.should.have.property("payload", data[count]);
try {
// Check they got appended as expected
var f = fs.readFileSync(fileToTest).toString();
f.should.equal("onetwo");
if (count === 1) {
// Check they got appended as expected
var f = fs.readFileSync(fileToTest).toString();
f.should.equal("onetwo");
// Delete the file
fs.unlinkSync(fileToTest);
// Delete the file
fs.unlinkSync(fileToTest);
setTimeout(function() {
// Send two more messages to the file
n1.receive({payload:"three"});
n1.receive({payload:"four"});
}, wait);
}
if (count === 3) {
var f = fs.readFileSync(fileToTest).toString();
f.should.equal("threefour");
fs.unlinkSync(fileToTest);
done();
}
} catch(err) {
done(err);
}
count++;
});
// Send two messages to the file
n1.receive({payload:"one"});
n1.receive({payload:"two"});
});
});
// Send two more messages to the file
n1.emit("input", {payload:"three"});
n1.emit("input", {payload:"four"});
it('should append to a file after it has been recreated ', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
try {
fs.unlinkSync(fileToTest);
} catch(err) {
}
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
var n2 = helper.getNode("helperNode1");
var data = ["one", "two", "three", "four"];
var count = 0;
n2.on("input", function (msg) {
try {
msg.should.have.property("payload", data[count]);
if (count == 1) {
// Check they got appended as expected
var f = fs.readFileSync(fileToTest).toString();
f.should.equal("onetwo");
setTimeout(function() {
if (os.type() === "Windows_NT") {
var dummyFile = path.join(resourcesDir,"50-file-test-dummy.txt");
fs.rename(fileToTest, dummyFile, function() {
recreateTest(n1, dummyFile);
});
} else {
recreateTest(n1, fileToTest);
}
}
if (count == 3) {
// Check the file was updated
try {
var f = fs.readFileSync(fileToTest).toString();
@ -131,42 +199,16 @@ describe('file Nodes', function() {
} catch(err) {
done(err);
}
},wait);
} catch(err) {
done(err);
}
},wait);
});
});
it('should append to a file after it has been recreated ', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false}];
try {
fs.unlinkSync(fileToTest);
} catch(err) {}
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
// Send two messages to the file
n1.emit("input", {payload:"one"});
n1.emit("input", {payload:"two"});
setTimeout(function() {
try {
// Check they got appended as expected
var f = fs.readFileSync(fileToTest).toString();
f.should.equal("onetwo");
if (os.type() === "Windows_NT") {
var dummyFile = path.join(resourcesDir,"50-file-test-dummy.txt");
fs.rename(fileToTest, dummyFile, function() {
recreateTest(n1, dummyFile);
});
} else {
recreateTest(n1, fileToTest);
}
} catch(err) {
done(err);
}
},wait);
count++;
});
// Send two messages to the file
n1.receive({payload:"one"});
n1.receive({payload:"two"});
});
function recreateTest(n1, fileToDelete) {
@ -177,30 +219,23 @@ describe('file Nodes', function() {
fs.writeFileSync(fileToTest,"");
// Send two more messages to the file
n1.emit("input", {payload:"three"});
n1.emit("input", {payload:"four"});
setTimeout(function() {
// Check the file was updated
try {
var f = fs.readFileSync(fileToTest).toString();
f.should.equal("threefour");
fs.unlinkSync(fileToTest);
done();
} catch(err) {
done(err);
}
},wait);
n1.receive({payload:"three"});
n1.receive({payload:"four"});
}
});
it('should use msg.filename if filename not set in node', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true}];
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
n1.emit("input", {payload:"fine", filename:fileToTest});
setTimeout(function() {
var n2 = helper.getNode("helperNode1");
n2.on("input", function (msg) {
msg.should.have.property("payload", "fine");
msg.should.have.property("filename", fileToTest);
var f = fs.readFileSync(fileToTest).toString();
if (os.type() !== "Windows_NT") {
f.should.have.length(5);
@ -211,16 +246,20 @@ describe('file Nodes', function() {
f.should.equal("fine\r\n");
}
done();
},wait);
});
n1.receive({payload:"fine", filename:fileToTest});
});
});
it('should be able to delete the file', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":"delete"}];
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":"delete", wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
n1.emit("input", {payload:"fine"});
setTimeout(function() {
var n2 = helper.getNode("helperNode1");
n2.on("input", function (msg) {
try {
var f = fs.readFileSync(fileToTest).toString();
f.should.not.equal("fine");
@ -230,7 +269,9 @@ describe('file Nodes', function() {
e.code.should.equal("ENOENT");
done();
}
},wait);
});
n1.receive({payload:"fine"});
});
});

View File

@ -308,13 +308,21 @@ describe('context', function() {
});
it('should fail when using invalid store name', function(done) {
Context.init({contextStorage:{'Invalid name':"noexist"}});
Context.init({contextStorage:{'Invalid name':{module:testPlugin}}});
Context.load().then(function(){
done("An error was not thrown");
}).catch(function(){
done();
});
});
it('should fail when using invalid sign character', function (done) {
Context.init({ contextStorage:{'abc-123':{module:testPlugin}}});
Context.load().then(function () {
done("An error was not thrown");
}).catch(function () {
done();
});
});
it('should fail when using invalid default context', function(done) {
Context.init({contextStorage:{default:"noexist"}});
Context.load().then(function(){
@ -339,6 +347,20 @@ describe('context', function() {
done();
});
});
it('should fail to load invalid module', function (done) {
Context.init({contextStorage: {
test: {
module: function (config) {
throw new Error("invalid plugin was loaded.");
}
}
}});
Context.load().then(function () {
done("An error was not thrown");
}).catch(function () {
done();
});
});
});
describe('close modules',function(){
@ -419,7 +441,7 @@ describe('context', function() {
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","bar","defaultt",cb);
context.set("foo","bar","default",cb);
context.get("foo","default",cb);
context.keys("default",cb);
stubGet.called.should.be.false();
@ -763,6 +785,93 @@ describe('context', function() {
});
}).catch(function(err){ done(err); });
});
it('should throw an error if callback of context.get is not a function', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
var context = Context.get("1", "flow");
context.get("foo", "memory", "callback");
done("should throw an error.");
}).catch(function () {
done();
});
});
it('should throw an error if callback of context.get is not specified', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
var context = Context.get("1", "flow");
context.get("foo", "memory");
done("should throw an error.");
}).catch(function () {
done();
});
});
it('should throw an error if callback of context.set is not a function', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
var context = Context.get("1", "flow");
context.set("foo", "bar", "memory", "callback");
done("should throw an error.");
}).catch(function () {
done();
});
});
it('should not throw an error if callback of context.set is not specified', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
var context = Context.get("1", "flow");
context.set("foo", "bar", "memory");
done();
}).catch(done);
});
it('should throw an error if callback of context.keys is not a function', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
var context = Context.get("1", "flow");
context.keys("memory", "callback");
done("should throw an error.");
}).catch(function () {
done();
});
});
it('should throw an error if callback of context.keys is not specified', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
var context = Context.get("1", "flow");
context.keys("memory");
done("should throw an error.");
}).catch(function () {
done();
});
});
});
describe('listStores', function () {
it('should list context storages', function (done) {
Context.init({ contextStorage: contextDefaultStorage });
Context.load().then(function () {
var list = Context.listStores();
list.default.should.equal("default");
list.stores.should.eql(["default", "test"]);
done();
}).catch(done);
});
it('should list context storages without default storage', function (done) {
Context.init({ contextStorage: contextStorage });
Context.load().then(function () {
var list = Context.listStores();
list.default.should.equal("test");
list.stores.should.eql(["test"]);
done();
}).catch(done);
});
});
describe('delete context',function(){

View File

@ -63,6 +63,15 @@ describe('localfilesystem',function() {
});
});
it('should store local scope property', function (done) {
context.set("abc:def", "foo.bar", "test", function (err) {
context.get("abc:def", "foo", function (err, value) {
value.should.be.eql({ bar: "test" });
done();
});
});
});
it('should delete property',function(done) {
context.set("nodeX","foo.abc.bar1","test1",function(err){
context.set("nodeX","foo.abc.bar2","test2",function(err){
@ -240,6 +249,81 @@ describe('localfilesystem',function() {
});
});
});
it('should throw an error when getting a value with invalid key', function (done) {
context.set("nodeX","foo","bar",function(err) {
context.get("nodeX"," ",function(err,value) {
should.exist(err);
done();
});
});
});
it('should throw an error when setting a value with invalid key',function (done) {
context.set("nodeX"," ","bar",function (err) {
should.exist(err);
done();
});
});
it('should throw an error when callback of get() is not a function',function (done) {
try {
context.get("nodeX","foo","callback");
done("should throw an error.");
} catch (err) {
done();
};
});
it('should throw an error when callback of get() is not specified',function (done) {
try {
context.get("nodeX","foo");
done("should throw an error.");
} catch (err) {
done();
};
});
it('should throw an error when callback of set() is not a function',function (done) {
try {
context.set("nodeX","foo","bar","callback");
done("should throw an error.");
} catch (err) {
done();
};
});
it('should not throw an error when callback of set() is not specified', function (done) {
try {
context.set("nodeX"," ","bar");
done();
} catch (err) {
done("should not throw an error.");
};
});
it('should handle empty context file', function (done) {
fs.outputFile(path.join(resourcesDir,"contexts","nodeX","flow.json"),"",function(){
context.get("nodeX", "foo", function (err, value) {
should.not.exist(value);
context.set("nodeX", "foo", "test", function (err) {
context.get("nodeX", "foo", function (err, value) {
value.should.be.equal("test");
done();
});
});
});
});
});
it('should throw an error when reading corrupt context file', function (done) {
fs.outputFile(path.join(resourcesDir, "contexts", "nodeX", "flow.json"),"{abc",function(){
context.get("nodeX", "foo", function (err, value) {
should.exist(err);
done();
});
});
});
});
describe('#keys',function() {
@ -286,6 +370,24 @@ describe('localfilesystem',function() {
});
});
});
it('should throw an error when callback of keys() is not a function', function (done) {
try {
context.keys("nodeX", "callback");
done("should throw an error.");
} catch (err) {
done();
};
});
it('should throw an error when callback of keys() is not specified', function (done) {
try {
context.keys("nodeX");
done("should throw an error.");
} catch (err) {
done();
};
});
});
describe('#delete',function() {
@ -497,4 +599,131 @@ describe('localfilesystem',function() {
});
});
describe('Configuration', function () {
it('should change a base directory', function (done) {
var differentBaseContext = LocalFileSystem({
base: "contexts2",
dir: resourcesDir,
cache: false
});
differentBaseContext.open().then(function () {
differentBaseContext.set("node2", "foo2", "bar2", function (err) {
differentBaseContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function(err, value) {
should.not.exist(value);
done();
});
});
});
});
});
it('should use userDir', function (done) {
var userDirContext = LocalFileSystem({
base: "contexts2",
cache: false,
settings: {
userDir: resourcesDir
}
});
userDirContext.open().then(function () {
userDirContext.set("node2", "foo2", "bar2", function (err) {
userDirContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
});
it('should use NODE_RED_HOME', function (done) {
var oldNRH = process.env.NODE_RED_HOME;
process.env.NODE_RED_HOME = resourcesDir;
fs.mkdirSync(resourcesDir);
fs.writeFileSync(path.join(resourcesDir,".config.json"),"");
var nrHomeContext = LocalFileSystem({
base: "contexts2",
cache: false
});
try {
nrHomeContext.open().then(function () {
nrHomeContext.set("node2", "foo2", "bar2", function (err) {
nrHomeContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
} finally {
process.env.NODE_RED_HOME = oldNRH;
}
});
it('should use HOME_PATH', function (done) {
var oldNRH = process.env.NODE_RED_HOME;
var oldHOMEPATH = process.env.HOMEPATH;
process.env.NODE_RED_HOME = resourcesDir;
process.env.HOMEPATH = resourcesDir;
var homePath = path.join(resourcesDir, ".node-red");
fs.outputFile(path.join(homePath, ".config.json"),"",function(){
var homeContext = LocalFileSystem({
base: "contexts2",
cache: false
});
try {
homeContext.open().then(function () {
homeContext.set("node2", "foo2", "bar2", function (err) {
homeContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
} finally {
process.env.NODE_RED_HOME = oldNRH;
process.env.HOMEPATH = oldHOMEPATH;
}
});
});
it('should use HOME_PATH', function (done) {
var oldNRH = process.env.NODE_RED_HOME;
var oldHOMEPATH = process.env.HOMEPATH;
var oldHOME = process.env.HOME;
process.env.NODE_RED_HOME = resourcesDir;
process.env.HOMEPATH = resourcesDir;
process.env.HOME = resourcesDir;
var homeContext = LocalFileSystem({
base: "contexts2",
cache: false
});
try {
homeContext.open().then(function () {
homeContext.set("node2", "foo2", "bar2", function (err) {
homeContext.get("node2", "foo2", function (err, value) {
value.should.be.equal("bar2");
context.get("node2", "foo2", function (err, value) {
should.not.exist(value);
done();
});
});
});
});
} finally {
process.env.NODE_RED_HOME = oldNRH;
process.env.HOMEPATH = oldHOMEPATH;
process.env.HOME = oldHOME;
}
});
});
});

View File

@ -144,6 +144,32 @@ describe('memory',function() {
keysY.should.have.length(1);
keysY[0].should.equal("hoge");
});
it('should enumerate global context keys', function () {
var keys = context.keys("global");
keys.should.be.an.Array();
keys.should.be.empty();
context.set("global", "foo", "bar");
keys = context.keys("global");
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("global", "abc.def", "bar");
keys = context.keys("global");
keys.should.have.length(2);
keys[1].should.equal("abc");
});
it('should not return specific keys as global context keys', function () {
var keys = context.keys("global");
context.set("global", "set", "bar");
context.set("global", "get", "bar");
context.set("global", "keys", "bar");
keys = context.keys("global");
keys.should.have.length(0);
});
});
describe('async',function() {
@ -212,6 +238,14 @@ describe('memory',function() {
should.not.exist(context.get("nodeY","foo"));
});
});
it('should not clean global context', function () {
context.set("global", "foo", "abc");
context.get("global", "foo").should.equal("abc");
return context.clean(["global"]).then(function () {
should.exist(context.get("global", "foo"));
});
});
});
});