123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
- 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 override = globals.sandbox.stub();
- override.withArgs(true).returns(new RP("about:newtab", true));
- override.withArgs(false).returns(null);
- globals.set("AboutNewTab", {
- override,
- reset: globals.sandbox.spy(),
- });
- globals.set("RemotePages", RP);
- dispatch = globals.sandbox.spy();
- mm = new ActivityStreamMessageChannel({dispatch});
- });
- afterEach(() => globals.restore());
- describe("portID validation", () => {
- let sandbox;
- beforeEach(() => {
- sandbox = sinon.createSandbox();
- sandbox.spy(global.Cu, "reportError");
- });
- afterEach(() => {
- sandbox.restore();
- });
- it("should log errors for an invalid portID", () => {
- mm.validatePortID({});
- mm.validatePortID({});
- mm.validatePortID({});
- assert.equal(global.Cu.reportError.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.override);
- });
- 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.override);
- });
- });
- 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"},
- });
- RPmessagePorts.push({
- url: "about:sheep",
- loaded: true,
- portID: "loaded",
- simulated: true,
- browser: {getAttribute: () => "preloaded"},
- });
- 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"}});
- 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"}});
- 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() {
- return "preloaded";
- },
- },
- };
- 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() {
- return "preloaded";
- },
- },
- };
- 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() {
- return "consumed";
- },
- },
- };
- 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"}};
- 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.Cu, "reportError");
- });
- afterEach(() => sandbox.restore());
- it("should report an error if the msg.data is missing", () => {
- mm.onMessage({target: {portID: "foo"}});
- assert.calledOnce(global.Cu.reportError);
- });
- it("should report an error if the msg.data.type is missing", () => {
- mm.onMessage({target: {portID: "foo"}, data: "foo"});
- assert.calledOnce(global.Cu.reportError);
- });
- 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() {
- return "preloaded";
- },
- },
- 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() {
- return "preloaded";
- },
- },
- 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() {
- return "consumed";
- },
- },
- 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);
- });
- });
- });
- });
|