mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into sync-dev
This commit is contained in:
@@ -1718,9 +1718,13 @@ describe('function node', function() {
|
||||
describe("init function", function() {
|
||||
|
||||
it('should delay handling messages until init completes', function(done) {
|
||||
const timeoutMS = 200;
|
||||
// Since helper.load uses process.nextTick timers might occasionally finish
|
||||
// a couple of milliseconds too early, so give some leeway to the check.
|
||||
const timeoutCheckMargin = 5;
|
||||
var flow = [{id:"n1",type:"function",wires:[["n2"]],initialize: `
|
||||
return new Promise((resolve,reject) => {
|
||||
setTimeout(resolve,200)
|
||||
setTimeout(resolve, ${timeoutMS});
|
||||
})`,
|
||||
func:"return msg;"
|
||||
},
|
||||
@@ -1733,9 +1737,10 @@ describe('function node', function() {
|
||||
msg.delta = Date.now() - msg.payload;
|
||||
receivedMsgs.push(msg)
|
||||
if (receivedMsgs.length === 5) {
|
||||
var errors = receivedMsgs.filter(msg => msg.delta < 200)
|
||||
let deltas = receivedMsgs.map(msg => msg.delta);
|
||||
var errors = deltas.filter(delta => delta < (timeoutMS - timeoutCheckMargin))
|
||||
if (errors.length > 0) {
|
||||
done(new Error(`Message received before init completed - was ${msg.delta} expected >300`))
|
||||
done(new Error(`Message received before init completed - delta values ${JSON.stringify(deltas)} expected to be > ${timeoutMS - timeoutCheckMargin}`))
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,27 @@ describe('SPLIT node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an array on a sub-property into multiple messages', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", property:"foo", wires:[["sn2"]]},
|
||||
{id:"sn2", type:"helper"}];
|
||||
helper.load(splitNode, flow, function() {
|
||||
var sn1 = helper.getNode("sn1");
|
||||
var sn2 = helper.getNode("sn2");
|
||||
sn2.on("input", function(msg) {
|
||||
msg.should.have.property("parts");
|
||||
msg.parts.should.have.property("count",4);
|
||||
msg.parts.should.have.property("type","array");
|
||||
msg.parts.should.have.property("index");
|
||||
msg.parts.should.have.property("property","foo");
|
||||
if (msg.parts.index === 0) { msg.foo.should.equal(1); }
|
||||
if (msg.parts.index === 1) { msg.foo.should.equal(2); }
|
||||
if (msg.parts.index === 2) { msg.foo.should.equal(3); }
|
||||
if (msg.parts.index === 3) { msg.foo.should.equal(4); done(); }
|
||||
});
|
||||
sn1.receive({foo:[1,2,3,4]});
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an array into multiple messages of a specified size', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", wires:[["sn2"]], arraySplt:3, arraySpltType:"len"},
|
||||
{id:"sn2", type:"helper"}];
|
||||
@@ -108,6 +129,31 @@ describe('SPLIT node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an object sub property into pieces', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", property:"foo.bar",wires:[["sn2"]]},
|
||||
{id:"sn2", type:"helper"}];
|
||||
helper.load(splitNode, flow, function() {
|
||||
var sn1 = helper.getNode("sn1");
|
||||
var sn2 = helper.getNode("sn2");
|
||||
var count = 0;
|
||||
sn2.on("input", function(msg) {
|
||||
msg.should.have.property("foo");
|
||||
msg.foo.should.have.property("bar");
|
||||
msg.should.have.property("parts");
|
||||
msg.parts.should.have.property("type","object");
|
||||
msg.parts.should.have.property("key");
|
||||
msg.parts.should.have.property("count");
|
||||
msg.parts.should.have.property("index");
|
||||
msg.parts.should.have.property("property","foo.bar");
|
||||
msg.topic.should.equal("foo");
|
||||
if (msg.parts.index === 0) { msg.foo.bar.should.equal(1); }
|
||||
if (msg.parts.index === 1) { msg.foo.bar.should.equal("2"); }
|
||||
if (msg.parts.index === 2) { msg.foo.bar.should.equal(true); done(); }
|
||||
});
|
||||
sn1.receive({topic:"foo",foo:{bar:{a:1,b:"2",c:true}}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an object into pieces and overwrite their topics', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", addname:"topic", wires:[["sn2"]]},
|
||||
{id:"sn2", type:"helper"}];
|
||||
@@ -516,6 +562,7 @@ describe('JOIN node', function() {
|
||||
n1.receive({payload:{a:1}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should join things into an array ignoring msg.parts.index in manual mode', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",",mode:"custom"},
|
||||
{id:"n2", type:"helper"}];
|
||||
@@ -562,6 +609,32 @@ describe('JOIN node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should join things into an array on a sub property in auto mode', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",", mode:"auto"},
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(joinNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("foo");
|
||||
msg.foo.should.have.property("bar");
|
||||
msg.foo.bar.should.be.an.Array();
|
||||
msg.foo.bar[0].should.equal("A");
|
||||
msg.foo.bar[1].should.equal("B");
|
||||
//msg.payload[2].a.should.equal(1);
|
||||
done();
|
||||
}
|
||||
catch(e) {done(e);}
|
||||
});
|
||||
n1.receive({foo:{bar:"A"}, parts:{id:1, type:"array", len:1, index:0, count:4, property:"foo.bar"}});
|
||||
n1.receive({foo:{bar:"B"}, parts:{id:1, type:"array", len:1, index:1, count:4, property:"foo.bar"}});
|
||||
n1.receive({foo:{bar:"C"}, parts:{id:1, type:"array", len:1, index:2, count:4, property:"foo.bar"}});
|
||||
n1.receive({foo:{bar:"D"}, parts:{id:1, type:"array", len:1, index:3, count:4, property:"foo.bar"}});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should join strings into a buffer after a count', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:2, build:"buffer", joinerType:"bin", joiner:"", mode:"custom"},
|
||||
{id:"n2", type:"helper"}];
|
||||
@@ -639,6 +712,35 @@ describe('JOIN node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge sub property objects', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:5, property:"foo.bar", build:"merged", mode:"custom"},
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(joinNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("foo");
|
||||
msg.foo.should.have.property("bar");
|
||||
msg.foo.bar.should.have.property("a",1);
|
||||
msg.foo.bar.should.have.property("b",2);
|
||||
msg.foo.bar.should.have.property("c",3);
|
||||
msg.foo.bar.should.have.property("d",4);
|
||||
msg.foo.bar.should.have.property("e",5);
|
||||
done();
|
||||
}
|
||||
catch(e) { done(e)}
|
||||
});
|
||||
n1.receive({foo:{bar:{a:9}, topic:"f"}});
|
||||
n1.receive({foo:{bar:{a:1}, topic:"a"}});
|
||||
n1.receive({foo:{bar:{b:9}, topic:"b"}});
|
||||
n1.receive({foo:{bar:{b:2}, topic:"b"}});
|
||||
n1.receive({foo:{bar:{c:3}, topic:"c"}});
|
||||
n1.receive({foo:{bar:{d:4}, topic:"d"}});
|
||||
n1.receive({foo:{bar:{e:5}, topic:"e"}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge full msg objects', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:6, build:"merged", mode:"custom", propertyType:"full", property:""},
|
||||
{id:"n2", type:"helper"}];
|
||||
|
@@ -25,6 +25,7 @@ var NR_TEST_UTILS = require("nr-test-utils");
|
||||
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
|
||||
var registry = NR_TEST_UTILS.require("@node-red/registry/lib/index");
|
||||
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
|
||||
let pluginRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/plugins");
|
||||
const { events, exec, log, hooks } = NR_TEST_UTILS.require("@node-red/util");
|
||||
|
||||
describe('nodes/registry/installer', function() {
|
||||
@@ -66,6 +67,9 @@ describe('nodes/registry/installer', function() {
|
||||
if (typeRegistry.setModulePendingUpdated.restore) {
|
||||
typeRegistry.setModulePendingUpdated.restore();
|
||||
}
|
||||
if (pluginRegistry.removeModule.restore) {
|
||||
pluginRegistry.removeModule.restore();
|
||||
}
|
||||
if (fs.statSync.restore) {
|
||||
fs.statSync.restore();
|
||||
}
|
||||
@@ -502,6 +506,9 @@ describe('nodes/registry/installer', function() {
|
||||
var removeModule = sinon.stub(typeRegistry,"removeModule").callsFake(function(md) {
|
||||
return nodeInfo;
|
||||
});
|
||||
let removePluginModule = sinon.stub(pluginRegistry,"removeModule").callsFake(function(md) {
|
||||
return [];
|
||||
});
|
||||
var getModuleInfo = sinon.stub(registry,"getModuleInfo").callsFake(function(md) {
|
||||
return {nodes:[]};
|
||||
});
|
||||
|
@@ -115,38 +115,7 @@ test-module-config`)
|
||||
|
||||
let pluginList = plugins.getPluginList();
|
||||
JSON.stringify(pluginList).should.eql(JSON.stringify(
|
||||
[
|
||||
{
|
||||
"id": "test-module/test-set",
|
||||
"enabled": true,
|
||||
"local": false,
|
||||
"user": false,
|
||||
"plugins": [
|
||||
{
|
||||
"type": "foo",
|
||||
"id": "a-plugin",
|
||||
"module": "test-module"
|
||||
},
|
||||
{
|
||||
"type": "bar",
|
||||
"id": "a-plugin2",
|
||||
"module": "test-module"
|
||||
},
|
||||
{
|
||||
"type": "foo",
|
||||
"id": "a-plugin3",
|
||||
"module": "test-module"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "test-module/test-disabled-set",
|
||||
"enabled": false,
|
||||
"local": false,
|
||||
"user": false,
|
||||
"plugins": []
|
||||
}
|
||||
]
|
||||
[{"id":"test-module/test-set","enabled":true,"local":false,"user":false,"plugins":[{"id":"a-plugin","type":"foo","module":"test-module"},{"id":"a-plugin2","type":"bar","module":"test-module"},{"id":"a-plugin3","type":"foo","module":"test-module"}]},{"id":"test-module/test-disabled-set","enabled":false,"local":false,"user":false,"plugins":[]}]
|
||||
))
|
||||
})
|
||||
})
|
||||
|
@@ -379,10 +379,17 @@ describe("@node-red/util/util", function() {
|
||||
result = util.evaluateNodeProperty('','bool');
|
||||
result.should.be.false();
|
||||
});
|
||||
it('returns date',function() {
|
||||
it('returns date - default format',function() {
|
||||
var result = util.evaluateNodeProperty('','date');
|
||||
(Date.now() - result).should.be.approximately(0,50);
|
||||
});
|
||||
|
||||
it('returns date - iso format',function() {
|
||||
var result = util.evaluateNodeProperty('iso','date');
|
||||
// 2023-12-04T16:51:04.429Z
|
||||
/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d+Z$/.test(result).should.be.true()
|
||||
});
|
||||
|
||||
it('returns bin', function () {
|
||||
var result = util.evaluateNodeProperty('[1, 2]','bin');
|
||||
result[0].should.eql(1);
|
||||
@@ -441,9 +448,16 @@ describe("@node-red/util/util", function() {
|
||||
},{});
|
||||
result.should.eql("123");
|
||||
});
|
||||
it('returns jsonata result', function () {
|
||||
var result = util.evaluateNodeProperty('$abs(-1)','jsonata',{},{});
|
||||
result.should.eql(1);
|
||||
it('returns jsonata result', function (done) {
|
||||
util.evaluateNodeProperty('$abs(-1)','jsonata',{},{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql(1);
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
it('returns null', function() {
|
||||
var result = util.evaluateNodeProperty(null,'null');
|
||||
@@ -601,51 +615,105 @@ describe("@node-red/util/util", function() {
|
||||
});
|
||||
});
|
||||
describe('evaluateJSONataExpression', function() {
|
||||
it('evaluates an expression', function() {
|
||||
it('evaluates an expression', function(done) {
|
||||
var expr = util.prepareJSONataExpression('payload',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
result.should.eql("hello");
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("hello");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('evaluates a legacyMode expression', function() {
|
||||
var expr = util.prepareJSONataExpression('msg.payload',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
result.should.eql("hello");
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("hello");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses flow context from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$flowContext("foo")',{context:function() { return {flow:{get: function(key) { return {'foo':'bar'}[key]}}}}});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
result.should.eql("bar");
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("bar");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses undefined environment variable from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$env("UTIL_ENV")',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('');
|
||||
});
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses environment variable from an expression', function() {
|
||||
process.env.UTIL_ENV = 'foo';
|
||||
var expr = util.prepareJSONataExpression('$env("UTIL_ENV")',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('foo');
|
||||
});
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("foo");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses moment from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$moment("2020-05-27", "YYYY-MM-DD").add(7, "days").add(1, "months").format("YYYY-MM-DD")',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('2020-07-03');
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("2020-07-03");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses moment-timezone from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$moment("2013-11-18 11:55Z").tz("Asia/Taipei").format()',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('2013-11-18T19:55:00+08:00');
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("2013-11-18T19:55:00+08:00");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('handles non-existant flow context variable', function() {
|
||||
var expr = util.prepareJSONataExpression('$flowContext("nonExistant")',{context:function() { return {flow:{get: function(key) { return {'foo':'bar'}[key]}}}}});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
should.not.exist(result);
|
||||
});
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
should.not.exist(result);
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('handles non-existant global context variable', function() {
|
||||
var expr = util.prepareJSONataExpression('$globalContext("nonExistant")',{context:function() { return {global:{get: function(key) { return {'foo':'bar'}[key]}}}}});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
should.not.exist(result);
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
should.not.exist(result);
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('handles async flow context access', function(done) {
|
||||
var expr = util.prepareJSONataExpression('$flowContext("foo")',{context:function() { return {flow:{get: function(key,store,callback) { setTimeout(()=>{callback(null,{'foo':'bar'}[key])},10)}}}}});
|
||||
|
Reference in New Issue
Block a user