diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/components/newtab/test/unit/content-src/lib/init-store.test.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/newtab/test/unit/content-src/lib/init-store.test.js')
-rw-r--r-- | browser/components/newtab/test/unit/content-src/lib/init-store.test.js | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/content-src/lib/init-store.test.js b/browser/components/newtab/test/unit/content-src/lib/init-store.test.js new file mode 100644 index 0000000000..5ce92d2192 --- /dev/null +++ b/browser/components/newtab/test/unit/content-src/lib/init-store.test.js @@ -0,0 +1,207 @@ +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); + }); + }); + }); +}); |