Merge branch 'dev' into pr_4387

This commit is contained in:
Nick O'Leary
2024-03-21 16:41:24 +00:00
committed by GitHub
349 changed files with 53003 additions and 4221 deletions

View File

@@ -29,7 +29,7 @@ describe("api/editor/ui", function() {
var app;
before(function() {
ui.init({
ui.init({}, {
nodes: {
getIcon: function(opts) {
return new Promise(function(resolve,reject) {

View File

@@ -33,16 +33,15 @@ describe("library api", function() {
should.not.exist(library.getExampleFlowPath('foo','bar'));
});
it('returns a valid example path', function(done) {
it('returns valid example paths', function(done) {
library.init();
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
try {
var flows = library.getExampleFlows();
flows.should.deepEqual({"test-module":{"f":["one"]}});
flows.should.deepEqual({"test-module":{"f":["1.2.3","one"]}});
var examplePath = library.getExampleFlowPath('test-module','one');
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'));
library.removeExamplesDir('test-module');
@@ -57,6 +56,5 @@ describe("library api", function() {
done(err);
}
});
})
});
});

View File

@@ -26,6 +26,7 @@ var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util");
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials");
var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
@@ -61,6 +62,7 @@ describe('Flow', function() {
this.scope = n.scope;
var node = this;
this.foo = n.foo;
this.bar = n.bar;
this.handled = 0;
this.stopped = false;
currentNodes[node.id] = node;
@@ -1235,11 +1237,12 @@ describe('Flow', function() {
})
describe("#env", function () {
afterEach(() => {
delete process.env.V0;
delete process.env.V1;
credentials.get.restore?.()
})
it("can instantiate a node with environment variable property values of group and tab", async function () {
after(function() {
delete process.env.V0;
delete process.env.V1;
})
process.env.V0 = "gv0";
process.env.V1 = "gv1";
process.env.V3 = "gv3";
@@ -1283,10 +1286,6 @@ describe('Flow', function() {
});
it("can access environment variable property using $parent", async function () {
after(function() {
delete process.env.V0;
delete process.env.V1;
})
process.env.V0 = "gv0";
process.env.V1 = "gv1";
var config = flowUtils.parseConfig([
@@ -1321,9 +1320,6 @@ describe('Flow', function() {
});
it("can define environment variable using JSONata", async function () {
after(function() {
delete process.env.V0;
})
var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[
{"name": "V0", value: "1+2", type: "jsonata"}
@@ -1346,9 +1342,6 @@ describe('Flow', function() {
});
it("can access global environment variables defined as JSONata values", async function () {
after(function() {
delete process.env.V0;
})
var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[
{"name": "V0", value: "1+2", type: "jsonata"}
@@ -1370,15 +1363,21 @@ describe('Flow', function() {
await flow.stop()
});
it("global flow can access global-config defined environment variables", async function () {
after(function() {
delete process.env.V0;
sinon.stub(credentials,"get").callsFake(function(id) {
if (id === 'gc') {
return { map: { GC_CRED: 'gc_cred' }}
}
return null
})
const config = flowUtils.parseConfig([
{id:"gc", type:"global-config", env:[
{"name": "GC0", value: "3+4", type: "jsonata"}
{"name": "GC0", value: "3+4", type: "jsonata"},
{"name": "GC_CRED", type: "cred"},
]},
{id:"t1",type:"tab" },
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}",wires:[]},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}", bar:"${GC_CRED}", wires:[]},
]);
// Two-arg call - makes this the global flow that handles global-config nodes
const globalFlow = Flow.create({getSetting:v=>process.env[v]},config);
@@ -1390,6 +1389,7 @@ describe('Flow', function() {
var activeNodes = flow.getActiveNodes();
activeNodes["1"].foo.should.equal(7);
activeNodes["1"].bar.should.equal('gc_cred');
await flow.stop()
await globalFlow.stop()

View File

@@ -183,6 +183,35 @@ describe('Node', function() {
n.receive(message);
});
it('calls parent flow handleComplete when multiple callbacks provided', function(done) {
var n = new RedNode({id:'123',type:'abc', _flow: {
handleComplete: function(node,msg) {
try {
doneCount.should.equal(2)
msg.should.deepEqual(message);
done();
} catch(err) {
done(err);
}
}
}});
var message = {payload:"hello world"};
let doneCount = 0
n.on('input',function(msg, nodeSend, nodeDone) {
doneCount++
nodeDone();
});
// Include a callback without explicit done signature
n.on('input',function(msg) { });
n.on('input',function(msg, nodeSend, nodeDone) {
doneCount++
nodeDone();
});
n.receive(message);
});
it('triggers onComplete hook when done callback provided', function(done) {
var handleCompleteCalled = false;
var hookCalled = false;

View File

@@ -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)}}}}});