Compare commits

...

46 Commits

Author SHA1 Message Date
Nick O'Leary
74794fea09 Split node: avoid duplicate done call for buffer split
Fixes #3982
2023-01-01 22:21:49 +00:00
Nick O'Leary
b3ce0c0079 Merge pull request #3987 from kazuhitoyokoi/master-fixproject
Ignore commit error in project feature
2022-12-27 14:15:47 +00:00
Nick O'Leary
4adc6b269c Merge pull request #3980 from kazuhitoyokoi/master-fixpalette
Hide subflow category after deleting subflow
2022-12-27 13:47:58 +00:00
Nick O'Leary
920b0178ec Merge pull request #3981 from kazuhitoyokoi/master-fixtypo
Fix typo in 25-status.html
2022-12-27 13:43:35 +00:00
Nick O'Leary
7870830367 Merge pull request #3991 from hardillb/http-request-form-array
Support form-data arrays
2022-12-27 13:42:33 +00:00
Ben Hardill
4c1d7ad2d2 Merge branch 'master' into http-request-form-array 2022-12-24 20:37:33 +00:00
Ben Hardill
661b07c856 Add tests 2022-12-24 20:35:51 +00:00
Ben Hardill
5670bd8265 Support form-data arrays 2022-12-24 19:32:33 +00:00
Kazuhito Yokoi
156c3984a7 Ignore commit error in project feature 2022-12-12 01:23:00 +09:00
Kazuhito Yokoi
f91af2153a Fix typo in 25-status.html 2022-12-10 00:33:40 +09:00
Kazuhito Yokoi
805f8a5ee7 Hide subflow category after deleting subflow 2022-12-08 23:43:47 +09:00
Nick O'Leary
113d42ef35 Merge pull request #3970 from node-red/prevent-text-selection
Prevent dbl-click opening node edit dialog with text selected
2022-12-03 23:34:57 +00:00
Nick O'Leary
e804addf0a Prevent dbl-click opening node edit dialog with text selected
Fixes #3958
2022-12-03 23:13:44 +00:00
Nick O'Leary
4bb2b91ee6 Merge pull request #3966 from Steve-Mcl/fix-mqtt-single-subscription
fix single subscription mqtt node status
2022-12-03 22:47:42 +00:00
Steve-Mcl
5bb66ed7d4 fix single subscription mqtt node status 2022-12-01 13:08:48 +00:00
Nick O'Leary
14e74afb07 Merge pull request #3909 from node-red/warn-invalid-msg
Add check that node sends object rather than primitive type
2022-11-30 22:28:09 +00:00
Nick O'Leary
c4e216f839 Merge pull request #3912 from node-red/handle_username_spaces
Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname
2022-11-30 22:18:24 +00:00
Nick O'Leary
2a49e7c8ef Merge pull request #3908 from node-red/add-httpheaders
Ensure msg.req.headers is enumerable
2022-11-30 22:15:23 +00:00
Nick O'Leary
02af01d2ca Merge pull request #3867 from Steve-Mcl/improve-nodesdir-scan
Fix nodesDir scan when node package has js/html in sub dir to package.json
2022-11-30 22:15:00 +00:00
Nick O'Leary
ee4af4c7bf Merge pull request #3920 from node-red/CSV-header-props-with-quotes
CSV node check header properties for ' and "
2022-11-30 22:10:40 +00:00
Nick O'Leary
3d565e74a5 Merge pull request #3917 from kazuhitoyokoi/master-fixpermission
Fix file permissions
2022-11-30 22:10:03 +00:00
Nick O'Leary
3cb84222f8 Merge pull request #3921 from node-red/fix-group-unknown
Handle replacing unknown node inside group or subflow
2022-11-30 22:09:35 +00:00
Nick O'Leary
1b013bb73b Merge pull request #3949 from Steveorevo/master
Fix #3939, red border red-ui-typedInput-container
2022-11-30 22:07:04 +00:00
Nick O'Leary
fd54e625d5 Merge pull request #3965 from we11adam/master
fix: fix typo in catch.html
2022-11-30 21:40:34 +00:00
Adam Lau
77f6412d3b fix: fix typo in catch.html 2022-11-30 17:51:13 +08:00
Stephen J. Carnam
c81cd5450f Support for PHP syntax highlight 2022-11-09 16:45:32 -08:00
Steveorevo
0b663abe50 Fix #3939, red border red-ui-typedInput-container 2022-11-09 10:33:05 -08:00
Nick O'Leary
14c362d4ba Merge pull request #3942 from node-red-hitachi/fix-watch-test
fix watch node test on MacOS/ARM
2022-11-07 21:12:55 +00:00
Hiroyasu Nishiyama
fce43b4e1d fix condition for platform check 2022-11-05 14:50:14 +09:00
Hiroyasu Nishiyama
1d547500e8 fix watch node test on MacOS/ARM 2022-11-05 14:30:32 +09:00
Dave Conway-Jones
94690fad7a Merge branch 'CSV-header-props-with-quotes' of https://github.com/node-red/node-red into CSV-header-props-with-quotes 2022-10-29 17:36:17 +01:00
Dave Conway-Jones
d693af9615 CSV node check header properties for ' and "
and add test
to close #3919
2022-10-29 17:35:45 +01:00
Kazuhito Yokoi
8b398f49c0 Merge branch 'node-red:master' into master-fixpermission 2022-10-29 23:51:05 +09:00
Nick O'Leary
902ce68164 Merge branch 'master' into fix-group-unknown 2022-10-26 00:52:34 +01:00
Nick O'Leary
cd0474ce7b Update packages/node_modules/@node-red/editor-client/src/js/nodes.js
Co-authored-by: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com>
2022-10-26 00:51:41 +01:00
Nick O'Leary
69a575097d Remove debug 2022-10-16 22:47:34 +01:00
Nick O'Leary
b40551a8fa Handle replacing unknown node inside group or subflow 2022-10-16 22:38:11 +01:00
Dave Conway-Jones
5b27bcd781 CSV node check header properties for ' and "
and add test
to close #3919
2022-10-16 18:05:21 +01:00
Kazuhito Yokoi
75725a38df Fix file permission 2022-10-12 23:50:33 +09:00
Nick O'Leary
24b055b1b8 Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname 2022-10-04 15:44:29 +01:00
Stephen McLaughlin
6d1a12af4b remove debug/test code 2022-10-04 13:36:23 +01:00
Nick O'Leary
a40e5dbcd4 Add check that node sends object rather than primitive type
Fixes #3876
2022-10-04 11:49:49 +01:00
Nick O'Leary
7c79ca7878 Ensure msg.req.headers is enumerable
Fixes #3878
2022-10-04 11:28:26 +01:00
Steve-Mcl
e6b379358a better logging of set with no types 2022-09-29 21:28:13 +01:00
Steve-Mcl
6a19d8246c add test for specific arrangement of node package 2022-09-03 21:37:27 +01:00
Steve-Mcl
d6bb3a558f fix loading node package in nodesDir on linux
fixes #3861
2022-09-03 21:23:38 +01:00
82 changed files with 328 additions and 58 deletions

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@@ -1965,7 +1965,7 @@ RED.nodes = (function() {
}
}
} else {
const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z)
const keepNodesCurrentZ = reimport && n.z && (RED.workspaces.contains(n.z) || RED.nodes.subflow(n.z))
if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
n.z = activeWorkspace;
}
@@ -2067,7 +2067,7 @@ RED.nodes = (function() {
node.id = getID();
} else {
node.id = n.id;
const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z)
const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z))
if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
if (createMissingWorkspace) {
if (missingWorkspace === null) {
@@ -2740,6 +2740,7 @@ RED.nodes = (function() {
}
});
const nodeGroupMap = {}
var replaceNodeIds = Object.keys(replaceNodes);
if (replaceNodeIds.length > 0) {
var reimportList = [];
@@ -2750,6 +2751,12 @@ RED.nodes = (function() {
} else {
allNodes.removeNode(n);
}
if (n.g) {
// reimporting a node *without* including its group object
// will cause the g property to be cleared. Cache it
// here so we can restore it
nodeGroupMap[n.id] = n.g
}
reimportList.push(convertNode(n));
RED.events.emit('nodes:remove',n);
});
@@ -2771,6 +2778,18 @@ RED.nodes = (function() {
var newNodeMap = {};
result.nodes.forEach(function(n) {
newNodeMap[n.id] = n;
if (nodeGroupMap[n.id]) {
// This node is in a group - need to substitute the
// node reference inside the group
n.g = nodeGroupMap[n.id]
const group = RED.nodes.group(n.g)
if (group) {
var index = group.nodes.findIndex(gn => gn.id === n.id)
if (index > -1) {
group.nodes[index] = n
}
}
}
});
RED.nodes.eachLink(function(l) {
if (newNodeMap.hasOwnProperty(l.source.id)) {

View File

@@ -238,6 +238,7 @@ RED.editor = (function() {
var valid = validateNodeProperty(node, defaults, property,value);
if (((typeof valid) === "string") || !valid) {
input.addClass("input-error");
input.next(".red-ui-typedInput-container").addClass("input-error");
if ((typeof valid) === "string") {
var tooltip = input.data("tooltip");
if (tooltip) {
@@ -250,6 +251,7 @@ RED.editor = (function() {
}
} else {
input.removeClass("input-error");
input.next(".red-ui-typedInput-container").removeClass("input-error");
var tooltip = input.data("tooltip");
if (tooltip) {
input.data("tooltip", null);

0
packages/node_modules/@node-red/editor-client/src/js/ui/library.js vendored Executable file → Normal file
View File

2
packages/node_modules/@node-red/editor-client/src/js/ui/palette.js vendored Executable file → Normal file
View File

@@ -432,6 +432,7 @@ RED.palette = (function() {
categoryNode.find(".red-ui-palette-content").slideToggle();
categoryNode.find("i").toggleClass("expanded");
}
categoryNode.hide();
}
}
@@ -510,6 +511,7 @@ RED.palette = (function() {
currentCategoryNode.find(".red-ui-palette-content").slideToggle();
currentCategoryNode.find("i").toggleClass("expanded");
}
currentCategoryNode.hide();
}
}

View File

3
packages/node_modules/@node-red/editor-client/src/js/ui/view.js vendored Executable file → Normal file
View File

@@ -3369,6 +3369,9 @@ RED.view = (function() {
}
if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
mouse_mode = RED.state.DEFAULT;
// Avoid dbl click causing text selection.
d3.event.preventDefault()
document.getSelection().removeAllRanges()
if (d.type != "subflow") {
if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) {
RED.workspaces.show(d.type.substring(8));

View File

@@ -4,7 +4,7 @@
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
<select id="node-input-scope-select">
<option value="all" data-i18n="catch.scope.all"></option>
<option value="target" data-i18n="catch.scope.selected"></options>
<option value="target" data-i18n="catch.scope.selected"></option>
</select>
</div>
<div class="form-row node-input-uncaught-row">

View File

@@ -4,7 +4,7 @@
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
<select id="node-input-scope-select">
<option value="all" data-i18n="status.scope.all"></option>
<option value="target" data-i18n="status.scope.selected"></options>
<option value="target" data-i18n="status.scope.selected"></option>
</select>
</div>
<div class="form-row node-input-target-row">

View File

@@ -21,6 +21,7 @@
<option value="javascript">JavaScript</option>
<option value="css">CSS</option>
<option value="markdown">Markdown</option>
<option value="php">PHP</option>
<option value="python">Python</option>
<option value="sql">SQL</option>
<option value="yaml">YAML</option>

View File

@@ -366,6 +366,16 @@ module.exports = function(RED) {
}
}
function updateStatus(node, allNodes) {
let setStatus = setStatusDisconnected
if(node.connecting) {
setStatus = setStatusConnecting
} else if(node.connected) {
setStatus = setStatusConnected
}
setStatus(node, allNodes)
}
function setStatusDisconnected(node, allNodes) {
if(allNodes) {
for (var id in node.users) {
@@ -697,13 +707,17 @@ module.exports = function(RED) {
if (Object.keys(node.users).length === 1) {
if(node.autoConnect) {
node.connect();
//update nodes status
setTimeout(function() {
updateStatus(node, true)
}, 1)
}
}
};
node.deregister = function(mqttNode,done) {
node.deregister = function(mqttNode, done, autoDisconnect) {
delete node.users[mqttNode.id];
if (!node.closing && node.connected && Object.keys(node.users).length === 0) {
if (autoDisconnect && !node.closing && node.connected && Object.keys(node.users).length === 0) {
node.disconnect();
}
done();
@@ -1220,7 +1234,7 @@ module.exports = function(RED) {
} else {
node.brokerConn.unsubscribe(node.topic,node.id, removed);
}
node.brokerConn.deregister(node, done);
node.brokerConn.deregister(node, done, removed);
node.brokerConn = null;
} else {
done();
@@ -1283,9 +1297,9 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
}
node.brokerConn.register(node);
node.on('close', function(done) {
node.on('close', function(removed, done) {
if (node.brokerConn) {
node.brokerConn.deregister(node,done);
node.brokerConn.deregister(node, done, removed)
node.brokerConn = null;
} else {
done();

View File

@@ -46,7 +46,7 @@ module.exports = function(RED) {
isText = true;
} else if (parsedType.type !== "application") {
isText = false;
} else if ((parsedType.subtype !== "octet-stream")
} else if ((parsedType.subtype !== "octet-stream")
&& (parsedType.subtype !== "cbor")
&& (parsedType.subtype !== "x-protobuf")) {
checkUTF = true;
@@ -200,6 +200,15 @@ module.exports = function(RED) {
this.callback = function(req,res) {
var msgid = RED.util.generateId();
res._msgid = msgid;
// Since Node 15, req.headers are lazily computed and the property
// marked as non-enumerable.
// That means it doesn't show up in the Debug sidebar.
// This redefines the property causing it to be evaluated *and*
// marked as enumerable again.
Object.defineProperty(req, 'headers', {
value: req.headers,
enumerable: true
})
if (node.method.match(/^(post|delete|put|options|patch)$/)) {
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
} else if (node.method == "get") {

View File

@@ -435,6 +435,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
formData.append(opt, val);
} else if (typeof val === 'object' && val.hasOwnProperty('value')) {
formData.append(opt,val.value,val.options || {});
} else if (Array.isArray(val)) {
for (var i=0; i<val.length; i++) {
formData.append(opt, val[i])
}
} else {
formData.append(opt,JSON.stringify(val));
}

View File

@@ -135,7 +135,10 @@ module.exports = function(RED) {
ou += node.sep;
}
else {
var p = RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']");
var tt = template[t];
if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; }
else { tt = '"'+tt+'"'; }
var p = RED.util.getMessageProperty(msg,'payload["'+s+'"]['+tt+']');
/* istanbul ignore else */
if (p === undefined) { p = ""; }
// fix to honour include null values flag

View File

@@ -251,7 +251,9 @@ module.exports = function(RED) {
}
else {
node.buffer = buff.slice(p,buff.length);
node.pendingDones.push(done);
if (node.buffer.length > 0) {
node.pendingDones.push(done);
}
}
if (node.buffer.length == 0) {
done();

0
packages/node_modules/@node-red/nodes/core/storage/10-file.html vendored Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
packages/node_modules/@node-red/nodes/locales/de/messages.json vendored Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
packages/node_modules/@node-red/nodes/locales/ko/messages.json vendored Executable file → Normal file
View File

0
packages/node_modules/@node-red/nodes/locales/ru/messages.json vendored Executable file → Normal file
View File

View File

@@ -43,37 +43,40 @@ function load(disableNodePathScan) {
return loadModuleFiles(modules);
}
function splitPath(p) {
return path.posix.normalize((p || '').replace(/\\/g, path.sep)).split(path.sep)
}
function loadModuleTypeFiles(module, type) {
const things = module[type];
var first = true;
var promises = [];
for (var thingName in things) {
let first = true;
const promises = [];
for (let thingName in things) {
/* istanbul ignore else */
if (things.hasOwnProperty(thingName)) {
if (module.name != "node-red" && first) {
// Check the module directory exists
first = false;
var fn = things[thingName].file;
var parts = fn.split("/");
var i = parts.length-1;
for (;i>=0;i--) {
if (parts[i] == "node_modules") {
break;
}
let moduleFn = module.path
const fn = things[thingName].file
const parts = splitPath(fn)
const nmi = parts.indexOf('node_modules')
if(nmi > -1) {
moduleFn = parts.slice(0,nmi+2).join(path.sep);
}
if (!moduleFn) {
// shortcut - skip calling statSync on empty string
break; // Module not found, don't attempt to load its nodes
}
var moduleFn = parts.slice(0,i+2).join("/");
try {
var stat = fs.statSync(moduleFn);
const stat = fs.statSync(moduleFn);
} catch(err) {
// Module not found, don't attempt to load its nodes
break;
break; // Module not found, don't attempt to load its nodes
}
}
try {
var promise;
let promise;
if (type === "nodes") {
promise = loadNodeConfig(things[thingName]);
} else if (type === "plugins") {
@@ -82,8 +85,7 @@ function loadModuleTypeFiles(module, type) {
promises.push(
promise.then(
(function() {
var m = module.name;
var n = thingName;
const n = thingName;
return function(nodeSet) {
things[n] = nodeSet;
return nodeSet;
@@ -93,7 +95,6 @@ function loadModuleTypeFiles(module, type) {
);
} catch(err) {
console.log(err)
//
}
}
}

View File

@@ -156,6 +156,16 @@ function scanDirForNodesModules(dir,moduleName,package) {
}
}
}
// if we have found a package.json, this IS a node_module, lets see if it is a node-red node
if (!isNodeRedModule && files.indexOf('package.json') > -1) {
package = getPackageDetails(dir) // get package details
if(package && package.isNodeRedModule) {
isNodeRedModule = true
files = ['package.json'] // shortcut the file scan
}
}
for (let i=0;i<files.length;i++) {
let fn = files[i];
if (!isNodeRedModule && /^@/.test(fn)) {

View File

@@ -185,10 +185,17 @@ function loadNodeConfigs() {
function addModule(module) {
moduleNodes[module.name] = [];
moduleConfigs[module.name] = module;
// console.log("registry.js.addModule",module.name,"user?",module.user,"usedBy",module.usedBy,"dependencies",module.dependencies)
for (var setName in module.nodes) {
for (const setName in module.nodes) {
if (module.nodes.hasOwnProperty(setName)) {
var set = module.nodes[setName];
const set = module.nodes[setName];
if (!set.types) {
const err = new Error("Set has no types")
err.code = "set_has_no_types"
err.details = {
...set
}
set.err = err
}
moduleNodes[module.name].push(set.name);
nodeList.push(set.id);
if (!set.err) {

View File

@@ -161,6 +161,8 @@ function start() {
for (i=0;i<nodeErrors.length;i+=1) {
if (nodeErrors[i].err.code === "type_already_registered") {
log.warn("["+nodeErrors[i].id+"] "+log._("server.type-already-registered",{type:nodeErrors[i].err.details.type,module: nodeErrors[i].err.details.moduleA}));
} else if (nodeErrors[i].err.code === "set_has_no_types") {
log.warn("["+nodeErrors[i].id+"] "+log._("server.set-has-no-types", nodeErrors[i].err.details));
} else {
log.warn("["+nodeErrors[i].id+"] "+nodeErrors[i].err);
}

View File

@@ -373,6 +373,11 @@ Node.prototype.send = function(msg) {
if (msg === null || typeof msg === "undefined") {
return;
} else if (!util.isArray(msg)) {
// A single message has been passed in
if (typeof msg !== 'object') {
this.error(Log._("nodes.flow.non-message-returned", { type: typeof msg }));
return
}
if (this._wire) {
// A single message and a single wire on output 0
// TODO: pre-load flows.get calls - cannot do in constructor
@@ -425,27 +430,31 @@ Node.prototype.send = function(msg) {
for (k = 0; k < msgs.length; k++) {
var m = msgs[k];
if (m !== null && m !== undefined) {
if (!m._msgid) {
hasMissingIds = true;
if (typeof m !== 'object') {
this.error(Log._("nodes.flow.non-message-returned", { type: typeof m }));
} else {
if (!m._msgid) {
hasMissingIds = true;
}
/* istanbul ignore else */
if (!sentMessageId) {
sentMessageId = m._msgid;
}
sendEvents.push({
msg: m,
source: {
id: this.id,
node: this,
port: i
},
destination: {
id: wires[j],
node: undefined
},
cloneMessage: msgSent
});
msgSent = true;
}
/* istanbul ignore else */
if (!sentMessageId) {
sentMessageId = m._msgid;
}
sendEvents.push({
msg: m,
source: {
id: this.id,
node: this,
port: i
},
destination: {
id: wires[j],
node: undefined
},
cloneMessage: msgSent
});
msgSent = true;
}
}
}

View File

@@ -71,6 +71,8 @@ function runGitCommand(args,cwd,env,emit) {
err.code = "git_missing_user";
} else if (/name consists only of disallowed characters/i.test(stderr)) {
err.code = "git_missing_user";
} else if (/nothing (add )?to commit/i.test(stdout)) {
return stdout;
}
throw err;
})
@@ -106,7 +108,7 @@ function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
commandEnv.GIT_SSH = path.join(__dirname,"node-red-ssh.sh");
commandEnv.NODE_RED_KEY_FILE=auth.key_path;
// GIT_SSH_COMMAND - added in git 2.3.0
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
commandEnv.GIT_SSH_COMMAND = "ssh -i \"" + auth.key_path + "\" -F /dev/null";
// console.log('commandEnv:', commandEnv);
return runGitCommand(args,cwd,commandEnv,emit).then( result => {
rs.close();

0
packages/node_modules/@node-red/runtime/locales/de/runtime.json vendored Executable file → Normal file
View File

View File

@@ -20,6 +20,7 @@
"errors-help": "Run with -v for details",
"missing-modules": "Missing node modules:",
"node-version-mismatch": "Node module cannot be loaded on this version. Requires: __version__ ",
"set-has-no-types": "Set does not have any types. name: '__name__', module: '__module__', file: '__file__'",
"type-already-registered": "'__type__' already registered by module __module__",
"removing-modules": "Removing modules from config",
"added-types": "Added node types:",
@@ -134,7 +135,8 @@
"flow": {
"unknown-type": "Unknown type: __type__",
"missing-types": "missing types",
"error-loop": "Message exceeded maximum number of catches"
"error-loop": "Message exceeded maximum number of catches",
"non-message-returned": "Node tried to send a message of type __type__"
},
"index": {
"unrecognised-id": "Unrecognised id: __id__",

0
packages/node_modules/@node-red/runtime/locales/ko/runtime.json vendored Executable file → Normal file
View File

View File

@@ -2334,4 +2334,38 @@ describe('HTTP Request Node', function() {
});
}
});
describe('multipart form posts', function() {
it('should send arrays as multiple entries', function (done) {
const flow = [
{
id: 'n1', type: 'http request', wires: [['n2']], method: 'POST', ret: 'obj', url: getTestURL('/file-upload'), headers: [
]
},
{ id: "n2", type: "helper" }
];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on('input', function(msg){
try {
msg.payload.body.should.have.property('foo')
msg.payload.body.list.should.deepEqual(['a','b','c'])
done()
} catch (e) {
done(e)
}
});
n1.receive({
headers: {
'content-type': 'multipart/form-data'
},
payload: {
foo: 'bar',
list: [ 'a', 'b', 'c' ]
}
});
})
});
})
});

View File

@@ -766,6 +766,33 @@ describe('CSV node', function() {
});
});
it('should handle a template with quotes in the property names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"all", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload', 'a"a,b\'b\nA1,B1\nA2,B2\n');
done();
}
catch(e) { done(e); }
});
var testJson = [
{
"a\"a": "A1",
"b'b": "B1"
},
{
"a\"a": "A2",
"b'b": "B2"
}
]
n1.emit("input", {payload:testJson});
});
});
it('should convert an array of objects to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,d,c,b", wires:[["n2"]] },
{id:"n2", type:"helper"} ];

View File

@@ -15,11 +15,14 @@
**/
var fs = require("fs-extra");
var os = require("os");
var path = require("path");
var should = require("should");
var helper = require("node-red-node-test-helper");
var watchNode = require("nr-test-utils").require("@node-red/nodes/core/storage/23-watch.js");
var arch = os.arch();
var platform = os.platform();
describe('watch Node', function() {
this.timeout(5000);
@@ -89,7 +92,10 @@ describe('watch Node', function() {
msg.should.have.property('payload', result.payload);
msg.should.have.property('type', result.type);
if('size' in result) {
msg.should.have.property('size', result.size);
if (!((arch === "arm64") && (platform === "darwin"))) {
// On OSX/ARM, two change events occur and first event do not reflect file size change. So ignore size field in the case.
msg.should.have.property('size', result.size);
}
}
count++;
if(count === len) {

View File

@@ -329,17 +329,36 @@ describe("red/nodes/registry/localfilesystem",function() {
localfilesystem.init({nodesDir:[nodesDir2]});
const nodeModule = localfilesystem.getModuleFiles();
const loaded = Object.keys(nodeModule)
loaded.should.have.a.property("length", 3)
loaded.indexOf('@test/testnode').should.greaterThan(-1, "Should load @test/testnode")
loaded.indexOf('lower-case').should.greaterThan(-1, "Should load lower-case")
loaded.indexOf('@lowercase/lower-case2').should.greaterThan(-1, "Should load @lowercase/lower-case2")
loaded.indexOf('testnode2').should.greaterThan(-1, "Should load testnode2")
loaded.indexOf('test-theme2').should.greaterThan(-1, "Should load test-theme2")
loaded.should.have.a.property("length", 5)
// scoped module with nodes in same dir as package.json
nodeModule['@test/testnode'].should.have.a.property('name','@test/testnode');
nodeModule['@test/testnode'].should.have.a.property('version','1.0.0');
nodeModule['@test/testnode'].should.have.a.property('nodes');
nodeModule['@test/testnode'].should.have.a.property('path');
nodeModule['@test/testnode'].should.have.a.property('user', false);
// node-red module with nodes in sub dir
nodeModule['@lowercase/lower-case2'].should.have.a.property('name','@lowercase/lower-case2');
nodeModule['@lowercase/lower-case2'].should.have.a.property('version','2.0.0');
nodeModule['@lowercase/lower-case2'].should.have.a.property('nodes');
nodeModule['@lowercase/lower-case2'].nodes.should.have.a.property('lower-case');
nodeModule['@lowercase/lower-case2'].should.have.a.property('path');
nodeModule['@lowercase/lower-case2'].should.have.a.property('user', false);
// scoped module with nodes in sub dir
nodeModule['lower-case'].should.have.a.property('name', 'lower-case');
nodeModule['lower-case'].should.have.a.property('version','1.0.0');
nodeModule['lower-case'].should.have.a.property('nodes');
nodeModule['lower-case'].nodes.should.have.a.property('lower-case');
nodeModule['lower-case'].should.have.a.property('path');
nodeModule['lower-case'].should.have.a.property('user', false);
nodeModule['testnode2'].should.have.a.property('name','testnode2');
nodeModule['testnode2'].should.have.a.property('version','1.0.0');
nodeModule['testnode2'].should.have.a.property('nodes');

View File

@@ -0,0 +1,26 @@
<script type="text/javascript">
RED.nodes.registerType('lower-case2',{
category: 'function',
color: '#a6bbcf',
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"lower-case2";
}
});
</script>
<script type="text/html" data-template-name="lower-case2">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="lower-case2">
<p>A simple node that converts the message payloads into all lower-case2 characters</p>
</script>

View File

@@ -0,0 +1,11 @@
module.exports = function(RED) {
function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
var node = this;
node.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case2",LowerCaseNode);
}

View File

@@ -0,0 +1,9 @@
{
"name" : "@lowercase/lower-case2",
"node-red" : {
"nodes": {
"lower-case": "lower-case2/lower-case.js"
}
},
"version": "2.0.0"
}

View File

@@ -0,0 +1,26 @@
<script type="text/javascript">
RED.nodes.registerType('lower-case',{
category: 'function',
color: '#a6bbcf',
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"lower-case";
}
});
</script>
<script type="text/html" data-template-name="lower-case">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="lower-case">
<p>A simple node that converts the message payloads into all lower-case characters</p>
</script>

View File

@@ -0,0 +1,11 @@
module.exports = function(RED) {
function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
var node = this;
node.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case",LowerCaseNode);
}

View File

@@ -0,0 +1,9 @@
{
"name" : "lower-case",
"node-red" : {
"nodes": {
"lower-case": "lower-case/lower-case.js"
}
},
"version": "1.0.0"
}