diff --git a/CHANGELOG.md b/CHANGELOG.md index 075148316..6770f3158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +#### 0.19.3: Maintenance Release + + - Split node - fix complete to send msg for k/v object + - Remove unused Join node merged object key typed input + - Set the JavaScript editor to full-screen + - Filter global modules installed locally + - Add svg to permitted icon extension list + - Debug node - indicate status all the time if selected to do so + - pi nodes - increase test coverage slightly + - TCP-request node - only write payload + - JSON schema: perform validation when obj -> obj or str -> str + - JSON schema: add draft-06 support (via $schema keyword) + - Mqtt proxy configuration for websocket connection, #1651. + - Allows MQTT Shared Subscriptions for MQTT-In core node + - Fix use of HTML tag or CSS class specification as icon of typedInput + #### 0.19.2: Maintenance Release - Ensure node default colour is used if palette.theme has no match diff --git a/package.json b/package.json index 3aad5255d..d26870846 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "cookie": "0.3.1", "cookie-parser": "1.4.3", "cors": "2.8.4", - "cron": "1.3.0", + "cron": "1.4.1", "denque": "1.3.0", "express": "4.16.3", "express-session": "1.15.6", @@ -49,9 +49,9 @@ "media-typer": "0.3.0", "memorystore": "1.6.0", "mime": "1.4.1", - "mqtt": "2.18.5", + "mqtt": "2.18.8", "multer": "1.3.1", - "mustache": "2.3.1", + "mustache": "2.3.2", "node-red-node-email": "0.1.*", "node-red-node-feedparser": "^0.1.12", "node-red-node-rbe": "0.2.*", @@ -66,7 +66,7 @@ "request": "2.88.0", "semver": "5.5.1", "sentiment": "2.1.0", - "uglify-js": "3.4.8", + "uglify-js": "3.4.9", "when": "3.7.8", "ws": "1.1.5", "xml2js": "0.4.19" @@ -78,7 +78,7 @@ "chromedriver": "^2.41.0", "grunt": "~1.0.3", "grunt-chmod": "~1.1.1", - "grunt-cli": "~1.2.0", + "grunt-cli": "~1.3.1", "grunt-concurrent": "~2.3.1", "grunt-contrib-clean": "~1.1.0", "grunt-contrib-compress": "~1.4.0", diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-api/lib/editor/locales/en-US/editor.json index 689b030b1..550b9c6f5 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-api/lib/editor/locales/en-US/editor.json @@ -587,6 +587,7 @@ "pullUnrelatedHistory": "

The remote has an unrelated history of commits.

Are you sure you want to pull the changes into your local repository?

", "pullChanges": "Pull changes", "history": "history", + "projectHistory": "Project History", "daysAgo": "__count__ day ago", "daysAgo_plural": "__count__ days ago", "hoursAgo": "__count__ hour ago", diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/locales/ja/editor.json b/packages/node_modules/@node-red/editor-api/lib/editor/locales/ja/editor.json index 3da5791dd..779c0d089 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-api/lib/editor/locales/ja/editor.json @@ -585,6 +585,7 @@ "pullUnrelatedHistory": "

リモートに関連のないコミット履歴があります。

本当に変更をプルしてローカルリポジトリに反映しますか?

", "pullChanges": "プル変更", "history": "履歴", + "projectHistory": "プロジェクト履歴", "daysAgo": "__count__ 日前", "daysAgo_plural": "__count__ 日前", "hoursAgo": "__count__ 時間前", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 97a39a2a6..7ad9293c0 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -22,7 +22,7 @@ "express": "4.16.3", "memorystore": "1.6.0", "mime": "1.4.1", - "mustache": "2.3.1", + "mustache": "2.3.2", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js index cfb5a61c1..cc50ead2b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/js.js @@ -32,7 +32,7 @@ RED.editor.types._js = (function() { var trayOptions = { title: options.title, - width: "inherit", + width: options.width||"inherit", buttons: [ { id: "node-dialog-cancel", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js index ee8cb98f4..82af7bb6a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js @@ -1004,7 +1004,7 @@ RED.sidebar.versionControl = (function() { RED.sidebar.addTab({ id: "version-control", label: RED._("sidebar.project.versionControl.history"), - name: "Project History", + name: RED._("sidebar.project.versionControl.projectHistory"), content: sidebarContent, enableOnEdit: false, pinned: true, diff --git a/packages/node_modules/@node-red/nodes/core/core/58-debug.js b/packages/node_modules/@node-red/nodes/core/core/58-debug.js index cfd20b6ea..0d57ca491 100644 --- a/packages/node_modules/@node-red/nodes/core/core/58-debug.js +++ b/packages/node_modules/@node-red/nodes/core/core/58-debug.js @@ -20,7 +20,7 @@ module.exports = function(RED) { this.severity = n.severity || 40; this.active = (n.active === null || typeof n.active === "undefined") || n.active; if (this.tostatus) { - this.oldStatus = {fill:"grey", shape:this.active?"dot":"ring"}; + this.oldStatus = {fill:"grey", shape:"ring"}; this.status(this.oldStatus); } else { this.status({}); } @@ -131,7 +131,7 @@ module.exports = function(RED) { node.active = false; res.sendStatus(201); if (node.tostatus && node.hasOwnProperty("oldStatus")) { - node.oldStatus.shape = "ring"; + node.oldStatus.shape = "dot"; node.status(node.oldStatus); } } else { diff --git a/packages/node_modules/@node-red/nodes/core/core/80-function.html b/packages/node_modules/@node-red/nodes/core/core/80-function.html index 13ef20db0..913be7c41 100644 --- a/packages/node_modules/@node-red/nodes/core/core/80-function.html +++ b/packages/node_modules/@node-red/nodes/core/core/80-function.html @@ -128,6 +128,7 @@ var value = that.editor.getValue(); RED.editor.editJavaScript({ value: value, + width: "Infinity", cursor: that.editor.getCursorPosition(), complete: function(v,cursor) { that.editor.setValue(v, -1); diff --git a/packages/node_modules/@node-red/nodes/core/logic/17-split.html b/packages/node_modules/@node-red/nodes/core/logic/17-split.html index 9c2866b9c..fccb8c0d0 100644 --- a/packages/node_modules/@node-red/nodes/core/logic/17-split.html +++ b/packages/node_modules/@node-red/nodes/core/logic/17-split.html @@ -295,7 +295,8 @@ For object outputs, once this count has been reached, the node can be configured to send a message for each subsequent message received.

A timeout can be set to trigger sending the new message using whatever has been received so far.

-

If a message is received with the msg.complete property set, the output message is sent.

+

If a message is received with the msg.complete property set, the output message is finalised and sent. + This resets any part counts.

Reduce Sequence mode

When configured to join in reduce mode, an expression is applied to each @@ -439,10 +440,7 @@ $("#node-input-joiner").typedInput({ default: 'str', typeField: $("#node-input-joinerType"), - types:[ - 'str', - 'bin' - ] + types:['str', 'bin'] }); $("#node-input-property").typedInput({ @@ -451,7 +449,7 @@ }); $("#node-input-key").typedInput({ - types:['msg', {value:"merge", label:"", hasValue:false}] + types:['msg'] }); $("#node-input-build").change(); diff --git a/packages/node_modules/@node-red/nodes/core/logic/17-split.js b/packages/node_modules/@node-red/nodes/core/logic/17-split.js index 3170ba584..f5ad199ca 100644 --- a/packages/node_modules/@node-red/nodes/core/logic/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/logic/17-split.js @@ -586,7 +586,10 @@ module.exports = function(RED) { } else { if (msg.hasOwnProperty('complete')) { - completeSend(partId); + if (inflight[partId]) { + inflight[partId].msg.complete = msg.complete; + completeSend(partId); + } } else { node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object") @@ -594,6 +597,7 @@ module.exports = function(RED) { } return; } + if (!inflight.hasOwnProperty(partId)) { if (payloadType === 'object' || payloadType === 'merged') { inflight[partId] = { @@ -604,19 +608,6 @@ module.exports = function(RED) { msg:RED.util.cloneMessage(msg) }; } - else if (node.accumulate === true) { - if (msg.hasOwnProperty("reset")) { delete inflight[partId]; } - inflight[partId] = inflight[partId] || { - currentCount:0, - payload:{}, - targetCount:targetCount, - type:payloadType, - msg:RED.util.cloneMessage(msg) - } - if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') { - inflight[partId].payload = []; - } - } else { inflight[partId] = { currentCount:0, diff --git a/packages/node_modules/@node-red/nodes/core/logic/18-sort.js b/packages/node_modules/@node-red/nodes/core/logic/18-sort.js index 124392014..c330801e2 100644 --- a/packages/node_modules/@node-red/nodes/core/logic/18-sort.js +++ b/packages/node_modules/@node-red/nodes/core/logic/18-sort.js @@ -196,9 +196,10 @@ module.exports = function(RED) { }).catch(err => { node.error(err,msg); }); + return; } var parts = msg.parts; - if (!parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) { + if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) { return; } var gid = parts.id; @@ -242,7 +243,8 @@ module.exports = function(RED) { delete pending[key]; } } - pending_count = 0; }) + pending_count = 0; + }); } RED.nodes.registerType("sort", SortNode); diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index c48fe6226..5b06d7897 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -17,7 +17,7 @@ "cookie-parser": "1.4.3", "cookie": "0.3.1", "cors": "2.8.4", - "cron": "1.3.0", + "cron": "1.4.1", "denque": "1.3.0", "fs-extra": "5.0.0", "fs.notify": "0.0.4", @@ -26,9 +26,9 @@ "is-utf8": "0.2.1", "js-yaml": "3.12.0", "media-typer": "0.3.0", - "mqtt": "2.18.5", + "mqtt": "2.18.8", "multer": "1.3.1", - "mustache": "2.3.1", + "mustache": "2.3.2", "on-headers": "1.0.1", "raw-body": "2.3.3", "request": "2.88.0", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index e8e9c1eaa..3f5e29aa4 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -14,7 +14,7 @@ "dependencies": { "@node-red/util": "*", "semver": "5.5.1", - "uglify-js": "3.4.8", + "uglify-js": "3.4.9", "when": "3.7.8" } } diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index 5717b16f3..aed011650 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -18,9 +18,11 @@ 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", @@ -28,6 +30,7 @@ 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", }; diff --git a/test/editor/pageobjects/nodes/core/io/10-mqttconfig_page.js b/test/editor/pageobjects/nodes/core/io/10-mqttconfig_page.js new file mode 100644 index 000000000..69266f9e8 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/io/10-mqttconfig_page.js @@ -0,0 +1,39 @@ +/** + * 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 +}; diff --git a/test/editor/pageobjects/nodes/core/io/10-mqttin_page.js b/test/editor/pageobjects/nodes/core/io/10-mqttin_page.js new file mode 100644 index 000000000..31b909116 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/io/10-mqttin_page.js @@ -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 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; diff --git a/test/editor/pageobjects/nodes/core/io/10-mqttout_page.js b/test/editor/pageobjects/nodes/core/io/10-mqttout_page.js new file mode 100644 index 000000000..783d87b55 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/io/10-mqttout_page.js @@ -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 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; \ No newline at end of file diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js new file mode 100644 index 000000000..10a7e648f --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js @@ -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 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; diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 74083f91f..8cb364ad1 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -18,12 +18,15 @@ 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'); @@ -31,9 +34,11 @@ var nodeCatalog = { // input "inject": injectNode, "httpin": httpinNode, + "mqttIn":mqttInNode, // output "debug": debugNode, "httpResponse": httpResponseNode, + "mqttOut": mqttOutNode, // function "function": functionNode, "template": templateNode, @@ -41,6 +46,7 @@ var nodeCatalog = { "range": rangeNode, "httpRequest": httpRequestNode, "html": htmlNode, + "json":jsonNode, // storage "filein": fileinNode, } diff --git a/test/editor/specs/scenario/cookbook_mqtt_uispec.js b/test/editor/specs/scenario/cookbook_mqtt_uispec.js new file mode 100644 index 000000000..f8aeb3c87 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_mqtt_uispec.js @@ -0,0 +1,228 @@ +/** + * 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']); + }); + }); +}); diff --git a/test/nodes/core/logic/17-split_spec.js b/test/nodes/core/logic/17-split_spec.js index 00598c833..2fc245dd8 100644 --- a/test/nodes/core/logic/17-split_spec.js +++ b/test/nodes/core/logic/17-split_spec.js @@ -603,14 +603,14 @@ describe('JOIN node', function() { }); it('should accumulate a merged object', function(done) { - var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:1}, + var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:3}, {id:"n2", type:"helper"}]; helper.load(joinNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 5) { + if (c === 3) { try { msg.should.have.property("payload"); msg.payload.should.have.property("a",3); @@ -632,14 +632,14 @@ describe('JOIN node', function() { }); it('should be able to reset an accumulation', function(done) { - var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:1}, + var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:3}, {id:"n2", type:"helper"}]; helper.load(joinNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 3) { + if (c === 1) { try { msg.should.have.property("payload"); msg.payload.should.have.property("a",1); @@ -649,11 +649,20 @@ describe('JOIN node', function() { } catch(e) { done(e) } } - if (c === 5) { + if (c === 2) { try { msg.should.have.property("payload"); - msg.payload.should.have.property("b",2); - msg.payload.should.have.property("c",1); + msg.payload.should.have.property("e",2); + msg.payload.should.have.property("f",1); + } + catch(e) { done(e) } + } + if (c === 3) { + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("g",2); + msg.payload.should.have.property("h",1); + msg.payload.should.have.property("i",3); done(); } catch(e) { done(e) } @@ -664,8 +673,11 @@ describe('JOIN node', function() { n1.receive({payload:{b:2}, topic:"b"}); n1.receive({payload:{c:3}, topic:"c"}); n1.receive({payload:{d:4}, topic:"d", complete:true}); - n1.receive({payload:{b:2}, topic:"e"}); - n1.receive({payload:{c:1}, topic:"f"}); + n1.receive({payload:{e:2}, topic:"e"}); + n1.receive({payload:{f:1}, topic:"f", complete:true}); + n1.receive({payload:{g:2}, topic:"g"}); + n1.receive({payload:{h:1}, topic:"h"}); + n1.receive({payload:{i:3}, topic:"i"}); }); }); diff --git a/test/nodes/core/logic/18-sort_spec.js b/test/nodes/core/logic/18-sort_spec.js index 909fec260..ac8d7d11c 100644 --- a/test/nodes/core/logic/18-sort_spec.js +++ b/test/nodes/core/logic/18-sort_spec.js @@ -66,13 +66,25 @@ describe('SORT node', function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - msg.should.have.property(target); - var data = msg[target]; - data.length.should.equal(data_out.length); - for(var i = 0; i < data_out.length; i++) { - data[i].should.equal(data_out[i]); + try { + msg.should.have.property(target); + var data = msg[target]; + data.length.should.equal(data_out.length); + for(var i = 0; i < data_out.length; i++) { + var data0 = data[i]; + var data1 = data_out[i]; + if (typeof data0 === "object") { + data0.should.deepEqual(data1); + } + else { + data0.should.equal(data1); + } + } + done(); + } + catch(e) { + console.log(e); } - done(); }); var msg = {}; msg[target] = data_in; @@ -93,6 +105,34 @@ describe('SORT node', function() { } function check_sort1(flow, key, key_type, data_in, data_out, done) { + function equals(v0, v1) { + var k0 = Object.keys(v0); + var k1 = Object.keys(v1); + + if (k0.length === k1.length) { + for (var i = 0; i < k0.length; i++) { + var k = k0[i]; + if (!v1.hasOwnProperty(k) || + (v0[k] !== v1[k])) { + return false; + } + } + return true; + } + return false; + } + function indexOf(a, v) { + for(var i = 0; i < a.length; i++) { + var av = a[i]; + if ((typeof v === 'object') && equals(v, av)) { + return i; + } + else if (v === av) { + return i; + } + } + return -1; + } var sort = flow[0]; var prop = (key_type === "msg") ? key : "payload"; sort.targetType = "seq"; @@ -107,7 +147,7 @@ describe('SORT node', function() { msg.should.have.property("parts"); msg.parts.should.have.property("count", data_out.length); var data = msg[prop]; - var index = data_out.indexOf(data); + var index = indexOf(data_out, data); msg.parts.should.have.property("index", index); count++; if (count === data_out.length) { @@ -136,7 +176,6 @@ describe('SORT node', function() { check_sort1(flow, exp, "jsonata", data_in, data_out, done); } - (function() { var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, wires:[["n2"]]}, {id:"n2", type:"helper"}]; @@ -239,6 +278,19 @@ describe('SORT node', function() { }); })(); + (function() { + var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + var conv = function(x) { + return x.map(function(v) { return { val:v }; }); + }; + var data_in = conv([ "200", "4", "30", "1000" ]); + var data_out = conv([ "4", "30", "200", "1000" ]); + it('should sort payload of objects', function(done) { + check_sort0C(flow, "val", data_in, data_out, done); + }); + })(); + it('should sort payload by context (exp, not number, ascending)', function(done) { var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$flowContext($)", msgKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"}, {id:"n2", type:"helper",z:"flow"}, @@ -268,7 +320,7 @@ describe('SORT node', function() { }); it('should sort message group by context (exp, not number, ascending)', function(done) { - var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$globalContext(payload)", msgKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"}, + var flow = [{id:"n1", type:"sort", target:"data", targetType:"seq", seqKey:"$globalContext(payload)", seqKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"}, {id:"n2", type:"helper",z:"flow"}, {id:"flow", type:"tab"}]; var data_in = [ "first", "second", "third", "fourth" ]; @@ -282,15 +334,20 @@ describe('SORT node', function() { n1.context()["global"].set("third","3"); n1.context()["global"].set("fourth","2"); n2.on("input", function(msg) { - msg.should.have.property("payload"); - msg.should.have.property("parts"); - msg.parts.should.have.property("count", data_out.length); - var data = msg["payload"]; - var index = data_out.indexOf(data); - msg.parts.should.have.property("index", index); - count++; - if (count === data_out.length) { - done(); + try { + msg.should.have.property("payload"); + msg.should.have.property("parts"); + msg.parts.should.have.property("count", data_out.length); + var data = msg["payload"]; + var index = data_out.indexOf(data); + msg.parts.should.have.property("index", index); + count++; + if (count === data_out.length) { + done(); + } + } + catch(e) { + done(e); } }); var len = data_in.length; @@ -332,7 +389,7 @@ describe('SORT node', function() { }); it('should sort message group by persistable context (exp, not number, descending)', function(done) { - var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$flowContext(payload,\"memory\")", msgKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"}, + var flow = [{id:"n1", type:"sort", target:"data", targetType:"seq", seqKey:"$flowContext(payload,\"memory\")", seqKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"}, {id:"n2", type:"helper",z:"flow"}, {id:"flow", type:"tab"}]; var data_in = [ "first", "second", "third", "fourth" ];