const should = require("should"); const NR_TEST_UTILS = require("nr-test-utils"); const hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks"); describe("util/hooks", function() { afterEach(function() { hooks.clear(); }) it("allows a hook to be registered", function(done) { let calledWith = null; hooks.has("onSend").should.be.false(); hooks.add("onSend", function(payload) { calledWith = payload } ) hooks.has("onSend").should.be.true(); let data = { a: 1 }; hooks.trigger("onSend",data,err => { calledWith.should.equal(data); done(err); }) }) it("rejects invalid hook id", function(done) { try { hooks.add("foo", function(payload) {}) done(new Error("Invalid hook accepted")) } catch(err) { done(); } }) it("calls hooks in the order they were registered", function(done) { hooks.add("onSend", function(payload) { payload.order.push("A") } ) hooks.add("onSend", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) done(err); }) }) it("does not allow multiple hooks with same id.label", function() { hooks.has("onSend.one").should.be.false(); hooks.has("onSend").should.be.false(); hooks.add("onSend.one", function(payload) { payload.order.push("A") } ); hooks.has("onSend.one").should.be.true(); hooks.has("onSend").should.be.true(); (function() { hooks.add("onSend.one", function(payload) { payload.order.push("B") } ) }).should.throw(); }) it("removes labelled hook", function(done) { hooks.has("onSend.A").should.be.false(); hooks.has("onSend.B").should.be.false(); hooks.has("onSend").should.be.false(); hooks.add("onSend.A", function(payload) { payload.order.push("A") } ) hooks.has("onSend.A").should.be.true(); hooks.has("onSend.B").should.be.false(); hooks.has("onSend").should.be.true(); hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) hooks.has("onSend.A").should.be.true(); hooks.has("onSend.B").should.be.true(); hooks.has("onSend").should.be.true(); hooks.remove("onSend.A"); hooks.has("onSend.A").should.be.false(); hooks.has("onSend.B").should.be.true(); hooks.has("onSend").should.be.true(); let data = { order:[] }; hooks.trigger("onSend",data,err => { try { data.order.should.eql(["B"]) hooks.remove("onSend.B"); hooks.has("onSend.A").should.be.false(); hooks.has("onSend.B").should.be.false(); hooks.has("onSend").should.be.false(); done(err); } catch(err2) { done(err2); } }) }) it("cannot remove unlabelled hook", function() { hooks.add("onSend", function(payload) { payload.order.push("A") } ); (function() { hooks.remove("onSend") }).should.throw(); }) it("removes all hooks with same label", function(done) { hooks.add("onSend.A", function(payload) { payload.order.push("A") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) hooks.add("preRoute.A", function(payload) { payload.order.push("C") } ) hooks.add("preRoute.B", function(payload) { payload.order.push("D") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) hooks.trigger("preRoute", data, err => { data.order.should.eql(["A","B","C","D"]) data.order = []; hooks.remove("*.A"); hooks.trigger("onSend",data,err => { data.order.should.eql(["B"]) hooks.trigger("preRoute", data, err => { data.order.should.eql(["B","D"]) }) done(err); }) }) }) }) it("allows a hook to remove itself whilst being called", function(done) { let data = { order: [] } hooks.add("onSend.A", function(payload) { payload.order.push("A") } ) hooks.add("onSend.B", function(payload) { hooks.remove("*.B"); }) hooks.add("onSend.C", function(payload) { payload.order.push("C") } ) hooks.add("onSend.D", function(payload) { payload.order.push("D") } ) hooks.trigger("onSend", data, err => { try { should.not.exist(err); data.order.should.eql(["A","C","D"]) done(); } catch(e) { done(e); } }) }); it("allows a hook to remove itself and others whilst being called", function(done) { let data = { order: [] } hooks.add("onSend.A", function(payload) { payload.order.push("A") } ) hooks.add("onSend.B", function(payload) { hooks.remove("*.B"); hooks.remove("*.C"); }) hooks.add("onSend.C", function(payload) { payload.order.push("C") } ) hooks.add("onSend.D", function(payload) { payload.order.push("D") } ) hooks.trigger("onSend", data, err => { try { should.not.exist(err); data.order.should.eql(["A","D"]) done(); } catch(e) { done(e); } }) }); it("halts execution on return false", function(done) { hooks.add("onSend.A", function(payload) { payload.order.push("A"); return false } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) err.should.be.false(); done(); }) }) it("halts execution on thrown error", function(done) { hooks.add("onSend.A", function(payload) { payload.order.push("A"); throw new Error("error") } ) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) should.exist(err); err.should.not.be.false() done(); }) }) it("handler can use callback function", function(done) { hooks.add("onSend.A", function(payload, done) { setTimeout(function() { payload.order.push("A") done() },30) }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) done(err); }) }) it("handler can use callback function - halt execution", function(done) { hooks.add("onSend.A", function(payload, done) { setTimeout(function() { payload.order.push("A") done(false) },30) }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) err.should.be.false() done(); }) }) it("handler can use callback function - halt on error", function(done) { hooks.add("onSend.A", function(payload, done) { setTimeout(function() { done(new Error("test error")) },30) }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql([]) should.exist(err); err.should.not.be.false() done(); }) }) it("handler be an async function", function(done) { hooks.add("onSend.A", async function(payload) { return new Promise(resolve => { setTimeout(function() { payload.order.push("A") resolve() },30) }); }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A","B"]) done(err); }) }) it("handler be an async function - halt execution", function(done) { hooks.add("onSend.A", async function(payload) { return new Promise(resolve => { setTimeout(function() { payload.order.push("A") resolve(false) },30) }); }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql(["A"]) done(err); }) }) it("handler be an async function - halt on error", function(done) { hooks.add("onSend.A", async function(payload) { return new Promise((resolve,reject) => { setTimeout(function() { reject(new Error("test error")) },30) }); }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data,err => { data.order.should.eql([]) should.exist(err); err.should.not.be.false() done(); }) }) it("handler can use callback function - promise API", function(done) { hooks.add("onSend.A", function(payload, done) { setTimeout(function() { payload.order.push("A") done() },30) }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data).then(() => { data.order.should.eql(["A","B"]) done() }).catch(done) }) it("handler can halt execution - promise API", function(done) { hooks.add("onSend.A", function(payload, done) { setTimeout(function() { payload.order.push("A") done(false) },30) }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data).then(() => { data.order.should.eql(["A"]) done() }).catch(done) }) it("handler can halt execution on error - promise API", function(done) { hooks.add("onSend.A", function(payload, done) { throw new Error("error"); }) hooks.add("onSend.B", function(payload) { payload.order.push("B") } ) let data = { order:[] }; hooks.trigger("onSend",data).then(() => { done("hooks.trigger resolved unexpectedly") }).catch(err => { done(); }) }) });