diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js')
-rw-r--r-- | browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js b/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js new file mode 100644 index 0000000000..bc4a60dfba --- /dev/null +++ b/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js @@ -0,0 +1,511 @@ +import { + actionCreators as ac, + actionTypes as at, +} from "common/Actions.sys.mjs"; +import { + ActivityStreamMessageChannel, + DEFAULT_OPTIONS, +} from "lib/ActivityStreamMessageChannel.jsm"; +import { addNumberReducer, GlobalOverrider } from "test/unit/utils"; +import { applyMiddleware, createStore } from "redux"; + +const OPTIONS = [ + "pageURL, outgoingMessageName", + "incomingMessageName", + "dispatch", +]; + +describe("ActivityStreamMessageChannel", () => { + let globals; + let dispatch; + let mm; + let RPmessagePorts; + beforeEach(() => { + RPmessagePorts = []; + function RP(url, isFromAboutNewTab = false) { + this.url = url; + this.messagePorts = RPmessagePorts; + this.addMessageListener = globals.sandbox.spy(); + this.removeMessageListener = globals.sandbox.spy(); + this.sendAsyncMessage = globals.sandbox.spy(); + this.destroy = globals.sandbox.spy(); + this.isFromAboutNewTab = isFromAboutNewTab; + } + globals = new GlobalOverrider(); + const overridePageListener = globals.sandbox.stub(); + overridePageListener.withArgs(true).returns(new RP("about:newtab", true)); + overridePageListener.withArgs(false).returns(null); + globals.set("AboutNewTab", { + overridePageListener, + reset: globals.sandbox.spy(), + }); + globals.set("RemotePages", RP); + globals.set("AboutHomeStartupCache", { onPreloadedNewTabMessage() {} }); + dispatch = globals.sandbox.spy(); + mm = new ActivityStreamMessageChannel({ dispatch }); + }); + + afterEach(() => globals.restore()); + + describe("portID validation", () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.spy(global.console, "error"); + }); + afterEach(() => { + sandbox.restore(); + }); + it("should log errors for an invalid portID", () => { + mm.validatePortID({}); + mm.validatePortID({}); + mm.validatePortID({}); + + assert.equal(global.console.error.callCount, 3); + }); + }); + + it("should exist", () => { + assert.ok(ActivityStreamMessageChannel); + }); + it("should apply default options", () => { + mm = new ActivityStreamMessageChannel(); + OPTIONS.forEach(o => assert.equal(mm[o], DEFAULT_OPTIONS[o], o)); + }); + it("should add options", () => { + const options = { + dispatch: () => {}, + pageURL: "FOO.html", + outgoingMessageName: "OUT", + incomingMessageName: "IN", + }; + mm = new ActivityStreamMessageChannel(options); + OPTIONS.forEach(o => assert.equal(mm[o], options[o], o)); + }); + it("should throw an error if no dispatcher was provided", () => { + mm = new ActivityStreamMessageChannel(); + assert.throws(() => mm.dispatch({ type: "FOO" })); + }); + describe("Creating/destroying the channel", () => { + describe("#createChannel", () => { + it("should create .channel with the correct URL", () => { + mm.createChannel(); + assert.ok(mm.channel); + assert.equal(mm.channel.url, mm.pageURL); + }); + it("should add 4 message listeners", () => { + mm.createChannel(); + assert.callCount(mm.channel.addMessageListener, 4); + }); + it("should add the custom message listener to the channel", () => { + mm.createChannel(); + assert.calledWith( + mm.channel.addMessageListener, + mm.incomingMessageName, + mm.onMessage + ); + }); + it("should override AboutNewTab", () => { + mm.createChannel(); + assert.calledOnce(global.AboutNewTab.overridePageListener); + }); + it("should use the channel passed by AboutNewTab on override", () => { + mm.createChannel(); + assert.ok(mm.channel.isFromAboutNewTab); + }); + it("should not override AboutNewTab if the pageURL is not about:newtab", () => { + mm = new ActivityStreamMessageChannel({ pageURL: "foo.html" }); + mm.createChannel(); + assert.notCalled(global.AboutNewTab.overridePageListener); + }); + }); + describe("#simulateMessagesForExistingTabs", () => { + beforeEach(() => { + sinon.stub(mm, "onActionFromContent"); + mm.createChannel(); + }); + it("should simulate init for existing ports", () => { + RPmessagePorts.push({ + url: "about:monkeys", + loaded: false, + portID: "inited", + simulated: true, + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + }); + RPmessagePorts.push({ + url: "about:sheep", + loaded: true, + portID: "loaded", + simulated: true, + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + }); + + mm.simulateMessagesForExistingTabs(); + + assert.calledWith(mm.onActionFromContent.firstCall, { + type: at.NEW_TAB_INIT, + data: RPmessagePorts[0], + }); + assert.calledWith(mm.onActionFromContent.secondCall, { + type: at.NEW_TAB_INIT, + data: RPmessagePorts[1], + }); + }); + it("should simulate load for loaded ports", () => { + RPmessagePorts.push({ + loaded: true, + portID: "foo", + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + }); + + mm.simulateMessagesForExistingTabs(); + + assert.calledWith( + mm.onActionFromContent, + { type: at.NEW_TAB_LOAD }, + "foo" + ); + }); + it("should set renderLayers on preloaded browsers after load", () => { + RPmessagePorts.push({ + loaded: true, + portID: "foo", + browser: { + getAttribute: () => "preloaded", + ownerGlobal: { + STATE_MAXIMIZED: 1, + STATE_MINIMIZED: 2, + STATE_NORMAL: 3, + STATE_FULLSCREEN: 4, + windowState: 3, + isFullyOccluded: false, + }, + }, + }); + mm.simulateMessagesForExistingTabs(); + assert.equal(RPmessagePorts[0].browser.renderLayers, true); + }); + }); + describe("#destroyChannel", () => { + let channel; + beforeEach(() => { + mm.createChannel(); + channel = mm.channel; + }); + it("should set .channel to null", () => { + mm.destroyChannel(); + assert.isNull(mm.channel); + }); + it("should reset AboutNewTab, and pass back its channel", () => { + mm.destroyChannel(); + assert.calledOnce(global.AboutNewTab.reset); + assert.calledWith(global.AboutNewTab.reset, channel); + }); + it("should not reset AboutNewTab if the pageURL is not about:newtab", () => { + mm = new ActivityStreamMessageChannel({ pageURL: "foo.html" }); + mm.createChannel(); + mm.destroyChannel(); + assert.notCalled(global.AboutNewTab.reset); + }); + it("should call channel.destroy() if pageURL is not about:newtab", () => { + mm = new ActivityStreamMessageChannel({ pageURL: "foo.html" }); + mm.createChannel(); + channel = mm.channel; + mm.destroyChannel(); + assert.calledOnce(channel.destroy); + }); + }); + }); + describe("Message handling", () => { + describe("#getTargetById", () => { + it("should get an id if it exists", () => { + const t = { portID: "foo:1" }; + mm.createChannel(); + mm.channel.messagePorts.push(t); + assert.equal(mm.getTargetById("foo:1"), t); + }); + it("should return null if the target doesn't exist", () => { + const t = { portID: "foo:2" }; + mm.createChannel(); + mm.channel.messagePorts.push(t); + assert.equal(mm.getTargetById("bar:3"), null); + }); + }); + describe("#getPreloadedBrowser", () => { + it("should get a preloaded browser if it exists", () => { + const port = { + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + }; + mm.createChannel(); + mm.channel.messagePorts.push(port); + assert.equal(mm.getPreloadedBrowser()[0], port); + }); + it("should get all the preloaded browsers across windows if they exist", () => { + const port = { + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + }; + mm.createChannel(); + mm.channel.messagePorts.push(port); + mm.channel.messagePorts.push(port); + assert.equal(mm.getPreloadedBrowser().length, 2); + }); + it("should return null if there is no preloaded browser", () => { + const port = { + browser: { + getAttribute: () => "consumed", + ownerGlobal: {}, + }, + }; + mm.createChannel(); + mm.channel.messagePorts.push(port); + assert.equal(mm.getPreloadedBrowser(), null); + }); + }); + describe("#onNewTabInit", () => { + it("should dispatch a NEW_TAB_INIT action", () => { + const t = { portID: "foo", url: "about:monkeys" }; + sinon.stub(mm, "onActionFromContent"); + + mm.onNewTabInit({ target: t }); + + assert.calledWith(mm.onActionFromContent, { + type: at.NEW_TAB_INIT, + data: t, + }); + }); + }); + describe("#onNewTabLoad", () => { + it("should dispatch a NEW_TAB_LOAD action", () => { + const t = { + portID: "foo", + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + }; + sinon.stub(mm, "onActionFromContent"); + mm.onNewTabLoad({ target: t }); + assert.calledWith( + mm.onActionFromContent, + { type: at.NEW_TAB_LOAD }, + "foo" + ); + }); + }); + describe("#onNewTabUnload", () => { + it("should dispatch a NEW_TAB_UNLOAD action", () => { + const t = { portID: "foo" }; + sinon.stub(mm, "onActionFromContent"); + mm.onNewTabUnload({ target: t }); + assert.calledWith( + mm.onActionFromContent, + { type: at.NEW_TAB_UNLOAD }, + "foo" + ); + }); + }); + describe("#onMessage", () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.spy(global.console, "error"); + }); + afterEach(() => sandbox.restore()); + it("should report an error if the msg.data is missing", () => { + mm.onMessage({ target: { portID: "foo" } }); + assert.calledOnce(global.console.error); + }); + it("should report an error if the msg.data.type is missing", () => { + mm.onMessage({ target: { portID: "foo" }, data: "foo" }); + assert.calledOnce(global.console.error); + }); + it("should call onActionFromContent", () => { + sinon.stub(mm, "onActionFromContent"); + const action = { + data: { data: {}, type: "FOO" }, + target: { portID: "foo" }, + }; + const expectedAction = { + type: action.data.type, + data: action.data.data, + _target: { portID: "foo" }, + }; + mm.onMessage(action); + assert.calledWith(mm.onActionFromContent, expectedAction, "foo"); + }); + }); + }); + describe("Sending and broadcasting", () => { + describe("#send", () => { + it("should send a message on the right port", () => { + const t = { portID: "foo:3", sendAsyncMessage: sinon.spy() }; + mm.createChannel(); + mm.channel.messagePorts = [t]; + const action = ac.AlsoToOneContent({ type: "HELLO" }, "foo:3"); + mm.send(action); + assert.calledWith( + t.sendAsyncMessage, + DEFAULT_OPTIONS.outgoingMessageName, + action + ); + }); + it("should not throw if the target isn't around", () => { + mm.createChannel(); + // port is not added to the channel + const action = ac.AlsoToOneContent({ type: "HELLO" }, "foo:4"); + + assert.doesNotThrow(() => mm.send(action)); + }); + }); + describe("#broadcast", () => { + it("should send a message on the channel", () => { + mm.createChannel(); + const action = ac.BroadcastToContent({ type: "HELLO" }); + mm.broadcast(action); + assert.calledWith( + mm.channel.sendAsyncMessage, + DEFAULT_OPTIONS.outgoingMessageName, + action + ); + }); + }); + describe("#preloaded browser", () => { + it("should send the message to the preloaded browser if there's data and a preloaded browser exists", () => { + const port = { + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + sendAsyncMessage: sinon.spy(), + }; + mm.createChannel(); + mm.channel.messagePorts.push(port); + const action = ac.AlsoToPreloaded({ type: "HELLO", data: 10 }); + mm.sendToPreloaded(action); + assert.calledWith( + port.sendAsyncMessage, + DEFAULT_OPTIONS.outgoingMessageName, + action + ); + }); + it("should send the message to all the preloaded browsers if there's data and they exist", () => { + const port = { + browser: { + getAttribute: () => "preloaded", + ownerGlobal: {}, + }, + sendAsyncMessage: sinon.spy(), + }; + mm.createChannel(); + mm.channel.messagePorts.push(port); + mm.channel.messagePorts.push(port); + mm.sendToPreloaded(ac.AlsoToPreloaded({ type: "HELLO", data: 10 })); + assert.calledTwice(port.sendAsyncMessage); + }); + it("should not send the message to the preloaded browser if there's no data and a preloaded browser does not exists", () => { + const port = { + browser: { + getAttribute: () => "consumed", + ownerGlobal: {}, + }, + sendAsyncMessage: sinon.spy(), + }; + mm.createChannel(); + mm.channel.messagePorts.push(port); + const action = ac.AlsoToPreloaded({ type: "HELLO" }); + mm.sendToPreloaded(action); + assert.notCalled(port.sendAsyncMessage); + }); + }); + }); + describe("Handling actions", () => { + describe("#onActionFromContent", () => { + beforeEach(() => mm.onActionFromContent({ type: "FOO" }, "foo:5")); + it("should dispatch a AlsoToMain action", () => { + assert.calledOnce(dispatch); + const [action] = dispatch.firstCall.args; + assert.equal(action.type, "FOO", "action.type"); + }); + it("should have the right fromTarget", () => { + const [action] = dispatch.firstCall.args; + assert.equal(action.meta.fromTarget, "foo:5", "meta.fromTarget"); + }); + }); + describe("#middleware", () => { + let store; + beforeEach(() => { + store = createStore(addNumberReducer, applyMiddleware(mm.middleware)); + }); + it("should just call next if no channel is found", () => { + store.dispatch({ type: "ADD", data: 10 }); + assert.equal(store.getState(), 10); + }); + it("should call .send but not affect the main store if an OnlyToOneContent action is dispatched", () => { + sinon.stub(mm, "send"); + const action = ac.OnlyToOneContent({ type: "ADD", data: 10 }, "foo"); + mm.createChannel(); + + store.dispatch(action); + + assert.calledWith(mm.send, action); + assert.equal(store.getState(), 0); + }); + it("should call .send and update the main store if an AlsoToOneContent action is dispatched", () => { + sinon.stub(mm, "send"); + const action = ac.AlsoToOneContent({ type: "ADD", data: 10 }, "foo"); + mm.createChannel(); + + store.dispatch(action); + + assert.calledWith(mm.send, action); + assert.equal(store.getState(), 10); + }); + it("should call .broadcast if the action is BroadcastToContent", () => { + sinon.stub(mm, "broadcast"); + const action = ac.BroadcastToContent({ type: "FOO" }); + + mm.createChannel(); + store.dispatch(action); + + assert.calledWith(mm.broadcast, action); + }); + it("should call .sendToPreloaded if the action is AlsoToPreloaded", () => { + sinon.stub(mm, "sendToPreloaded"); + const action = ac.AlsoToPreloaded({ type: "FOO" }); + + mm.createChannel(); + store.dispatch(action); + + assert.calledWith(mm.sendToPreloaded, action); + }); + it("should dispatch other actions normally", () => { + sinon.stub(mm, "send"); + sinon.stub(mm, "broadcast"); + sinon.stub(mm, "sendToPreloaded"); + + mm.createChannel(); + store.dispatch({ type: "ADD", data: 1 }); + + assert.equal(store.getState(), 1); + assert.notCalled(mm.send); + assert.notCalled(mm.broadcast); + assert.notCalled(mm.sendToPreloaded); + }); + }); + }); +}); |