Source code

Revision control

Copy as Markdown

Other Tools

import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { addNumberReducer, GlobalOverrider } from "test/unit/utils";
import {
INCOMING_MESSAGE_NAME,
initStore,
MERGE_STORE_ACTION,
OUTGOING_MESSAGE_NAME,
rehydrationMiddleware,
} from "content-src/lib/init-store";
describe("initStore", () => {
let globals;
let store;
beforeEach(() => {
globals = new GlobalOverrider();
globals.set("RPMSendAsyncMessage", globals.sandbox.spy());
globals.set("RPMAddMessageListener", globals.sandbox.spy());
store = initStore({ number: addNumberReducer });
});
afterEach(() => globals.restore());
it("should create a store with the provided reducers", () => {
assert.ok(store);
assert.property(store.getState(), "number");
});
it("should add a listener that dispatches actions", () => {
assert.calledWith(global.RPMAddMessageListener, INCOMING_MESSAGE_NAME);
const [, listener] = global.RPMAddMessageListener.firstCall.args;
globals.sandbox.spy(store, "dispatch");
const message = { name: INCOMING_MESSAGE_NAME, data: { type: "FOO" } };
listener(message);
assert.calledWith(store.dispatch, message.data);
});
it("should not throw if RPMAddMessageListener is not defined", () => {
// Note: this is being set/restored by GlobalOverrider
delete global.RPMAddMessageListener;
assert.doesNotThrow(() => initStore({ number: addNumberReducer }));
});
it("should log errors from failed messages", () => {
const [, callback] = global.RPMAddMessageListener.firstCall.args;
globals.sandbox.stub(global.console, "error");
globals.sandbox.stub(store, "dispatch").throws(Error("failed"));
const message = {
name: INCOMING_MESSAGE_NAME,
data: { type: MERGE_STORE_ACTION },
};
callback(message);
assert.calledOnce(global.console.error);
});
it("should replace the state if a MERGE_STORE_ACTION is dispatched", () => {
store.dispatch({ type: MERGE_STORE_ACTION, data: { number: 42 } });
assert.deepEqual(store.getState(), { number: 42 });
});
it("should call .send and update the local store if an AlsoToMain action is dispatched", () => {
const subscriber = sinon.spy();
const action = ac.AlsoToMain({ type: "FOO" });
store.subscribe(subscriber);
store.dispatch(action);
assert.calledWith(
global.RPMSendAsyncMessage,
OUTGOING_MESSAGE_NAME,
action
);
assert.calledOnce(subscriber);
});
it("should call .send but not update the local store if an OnlyToMain action is dispatched", () => {
const subscriber = sinon.spy();
const action = ac.OnlyToMain({ type: "FOO" });
store.subscribe(subscriber);
store.dispatch(action);
assert.calledWith(
global.RPMSendAsyncMessage,
OUTGOING_MESSAGE_NAME,
action
);
assert.notCalled(subscriber);
});
it("should not send out other types of actions", () => {
store.dispatch({ type: "FOO" });
assert.notCalled(global.RPMSendAsyncMessage);
});
describe("rehydrationMiddleware", () => {
it("should allow NEW_TAB_STATE_REQUEST to go through", () => {
const action = ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST });
const next = sinon.spy();
rehydrationMiddleware(store)(next)(action);
assert.calledWith(next, action);
});
it("should dispatch an additional NEW_TAB_STATE_REQUEST if INIT was received after a request", () => {
const requestAction = ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST });
const next = sinon.spy();
const dispatch = rehydrationMiddleware(store)(next);
dispatch(requestAction);
next.resetHistory();
dispatch({ type: at.INIT });
assert.calledWith(next, requestAction);
});
it("should allow MERGE_STORE_ACTION to go through", () => {
const action = { type: MERGE_STORE_ACTION };
const next = sinon.spy();
rehydrationMiddleware(store)(next)(action);
assert.calledWith(next, action);
});
it("should not allow actions from main to go through before MERGE_STORE_ACTION was received", () => {
const next = sinon.spy();
const dispatch = rehydrationMiddleware(store)(next);
dispatch(ac.BroadcastToContent({ type: "FOO" }));
dispatch(ac.AlsoToOneContent({ type: "FOO" }, 123));
assert.notCalled(next);
});
it("should allow all local actions to go through", () => {
const action = { type: "FOO" };
const next = sinon.spy();
rehydrationMiddleware(store)(next)(action);
assert.calledWith(next, action);
});
it("should allow actions from main to go through after MERGE_STORE_ACTION has been received", () => {
const next = sinon.spy();
const dispatch = rehydrationMiddleware(store)(next);
dispatch({ type: MERGE_STORE_ACTION });
next.resetHistory();
const action = ac.AlsoToOneContent({ type: "FOO" }, 123);
dispatch(action);
assert.calledWith(next, action);
});
it("should not let startup actions go through for the preloaded about:home document", () => {
globals.set("__FROM_STARTUP_CACHE__", true);
const next = sinon.spy();
const dispatch = rehydrationMiddleware(store)(next);
const action = ac.BroadcastToContent(
{ type: "FOO", meta: { isStartup: true } },
123
);
dispatch(action);
assert.notCalled(next);
});
});
});