forked from mirrors/gecko-dev
Depends on D165820 Differential Revision: https://phabricator.services.mozilla.com/D165821
207 lines
7.2 KiB
JavaScript
207 lines
7.2 KiB
JavaScript
import {
|
|
actionCreators as ac,
|
|
actionTypes as at,
|
|
} from "common/Actions.sys.mjs";
|
|
import { addNumberReducer, GlobalOverrider } from "test/unit/utils";
|
|
import {
|
|
EARLY_QUEUED_ACTIONS,
|
|
INCOMING_MESSAGE_NAME,
|
|
initStore,
|
|
MERGE_STORE_ACTION,
|
|
OUTGOING_MESSAGE_NAME,
|
|
queueEarlyMessageMiddleware,
|
|
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);
|
|
});
|
|
});
|
|
describe("queueEarlyMessageMiddleware", () => {
|
|
it("should allow all local actions to go through", () => {
|
|
const action = { type: "FOO" };
|
|
const next = sinon.spy();
|
|
|
|
queueEarlyMessageMiddleware(store)(next)(action);
|
|
|
|
assert.calledWith(next, action);
|
|
});
|
|
it("should allow action to main that does not belong to EARLY_QUEUED_ACTIONS to go through", () => {
|
|
const action = ac.AlsoToMain({ type: "FOO" });
|
|
const next = sinon.spy();
|
|
|
|
queueEarlyMessageMiddleware(store)(next)(action);
|
|
|
|
assert.calledWith(next, action);
|
|
});
|
|
it(`should line up EARLY_QUEUED_ACTIONS only let them go through after it receives the action from main`, () => {
|
|
EARLY_QUEUED_ACTIONS.forEach(actionType => {
|
|
const testStore = initStore({ number: addNumberReducer });
|
|
const next = sinon.spy();
|
|
const dispatch = queueEarlyMessageMiddleware(testStore)(next);
|
|
const action = ac.AlsoToMain({ type: actionType });
|
|
const fromMainAction = ac.AlsoToOneContent({ type: "FOO" }, 123);
|
|
|
|
// Early actions should be added to the queue
|
|
dispatch(action);
|
|
dispatch(action);
|
|
|
|
assert.notCalled(next);
|
|
assert.equal(testStore.getState.earlyActionQueue.length, 2);
|
|
next.resetHistory();
|
|
|
|
// Receiving action from main would empty the queue
|
|
dispatch(fromMainAction);
|
|
|
|
assert.calledThrice(next);
|
|
assert.equal(next.firstCall.args[0], fromMainAction);
|
|
assert.equal(next.secondCall.args[0], action);
|
|
assert.equal(next.thirdCall.args[0], action);
|
|
assert.equal(testStore.getState.earlyActionQueue.length, 0);
|
|
next.resetHistory();
|
|
|
|
// New action should go through immediately
|
|
dispatch(action);
|
|
assert.calledOnce(next);
|
|
assert.calledWith(next, action);
|
|
});
|
|
});
|
|
});
|
|
});
|