/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ let utilGlobal; let stopStoreSubscription; add_setup(() => { // Setup required to load the module into a global that we can access, since // the test utils inject into their globalThis instead of exporting. const principal = Services.scriptSecurityManager.getSystemPrincipal(); const webnav = Services.appShell.createWindowlessBrowser(true); const docShell = webnav.docShell; docShell.createAboutBlankDocumentViewer(principal, principal); utilGlobal = webnav.document.defaultView; utilGlobal.eval(` const { store } = ChromeUtils.importESModule( "moz-src:///comm/mail/base/content/state/store.mjs", { global: "current" } ); globalThis.store = store; // Just imported for the side-effect injecting the slice. ChromeUtils.importESModule( "resource://testing-common/reduxFixtures.mjs", { global: "current" } ); ChromeUtils.importESModule( "resource://testing-common/mail/ReduxTestUtils.mjs", { global: "current" } ); `); registerCleanupFunction(() => stopStoreSubscription?.()); }); add_task(function test_exportedToGlobal() { Assert.equal( typeof utilGlobal.ReduxTestUtils, "object", "Should export a test utils object as a side-effect" ); }); add_task(async function test_waitForAction() { Assert.equal( typeof utilGlobal.ReduxTestUtils.waitForAction, "function", "Should have a waitForAction function on the utils" ); const action = { type: "foo", payload: "test" }; const actionObserved = utilGlobal.ReduxTestUtils.waitForAction( "foo", incomingAction => incomingAction === action ); utilGlobal.store.dispatch(action); const resultingAction = await actionObserved; Assert.strictEqual( resultingAction, action, "Should have seen the same action as we dispatched" ); }); add_task(async function test_waitForAction_checkFunctionThrows() { const action = { type: "foo", payload: "test" }; const testError = new Error("test"); const actionObserved = utilGlobal.ReduxTestUtils.waitForAction("foo", () => { throw testError; }); utilGlobal.store.dispatch(action); await Assert.rejects( actionObserved, error => error === testError, "Should reject with the error raised by the check function" ); }); add_task(async function test_dispatchAction() { Assert.equal( typeof utilGlobal.ReduxTestUtils.dispatchAction, "function", "Should have a dispatchAction function on the utils" ); const actionObserved = utilGlobal.ReduxTestUtils.waitForAction("test"); info("Dispatching action..."); const dispatchedAction = await utilGlobal.ReduxTestUtils.dispatchAction( "resource://testing-common/reduxFixtures.mjs", "testAction", "foo bar" ); info("Waiting for the action to be observed..."); const observedAction = await actionObserved; Assert.deepEqual( dispatchedAction, { type: "test", payload: "foo bar" }, "Should have dispatched the expected action" ); Assert.deepEqual( observedAction, dispatchedAction, "Should have observed the same action that was generated by dispatchAction" ); }); add_task(function test_subscribe() { const notifications = []; let dispatchReturned = false; stopStoreSubscription = utilGlobal.ReduxTestUtils.subscribe(() => { notifications.push({ dispatchReturned, state: utilGlobal.ReduxTestUtils.getState(), }); }); utilGlobal.store.dispatch({ type: "test-subscription" }); dispatchReturned = true; Assert.equal( notifications.length, 1, "The listener should run once for a dispatch that leaves state unchanged" ); Assert.equal( notifications[0].dispatchReturned, false, "The listener should run before dispatch returns" ); Assert.strictEqual( notifications[0].state, utilGlobal.store.getState(), "The listener should read the current store state without copying it" ); stopStoreSubscription(); stopStoreSubscription = null; utilGlobal.store.dispatch({ type: "test-subscription-stopped" }); Assert.equal( notifications.length, 1, "Removing the listener should stop later dispatch notifications" ); }); add_task(function test_getState() { Assert.strictEqual( utilGlobal.ReduxTestUtils.getState(), utilGlobal.store.getState(), "The helper should return its context's current state without copying it" ); }); add_task(async function test_selectValue() { const result = await utilGlobal.ReduxTestUtils.selectValue( "resource://testing-common/reduxFixtures.mjs", "testSelector", "foo", "bar" ); Assert.deepEqual( result, ["lorem", "foo", "bar"], "The selector should receive the current state and extra arguments" ); const state = await utilGlobal.ReduxTestUtils.selectValue( "resource://testing-common/reduxFixtures.mjs", "testStateSelector" ); Assert.strictEqual( state, utilGlobal.store.getState(), "The selector result should keep its identity in the helper's context" ); }); add_task(async function test_dispatchActionWithThunk() { const actionObserved = utilGlobal.ReduxTestUtils.waitForAction("test"); const result = await utilGlobal.ReduxTestUtils.dispatchAction( "resource://testing-common/reduxFixtures.mjs", "testThunk", "thunk value" ); const observedAction = await actionObserved; Assert.deepEqual( observedAction, { type: "test", payload: "thunk value" }, "The function action should dispatch to the helper's store" ); Assert.deepEqual( result, ["lorem", "thunk value"], "Dispatch should wait for the function action's selector result" ); }); add_task(async function test_assertSelector() { Assert.equal( typeof utilGlobal.ReduxTestUtils.assertSelector, "function", "Should have a assertSelector function on the utils" ); await utilGlobal.ReduxTestUtils.assertSelector( "resource://testing-common/reduxFixtures.mjs", "testSelector", ["lorem", "foo", "bar"], "Should have the initial value in the store and pass along the extra arguments", "foo", "bar" ); });