diff options
Diffstat (limited to 'browser/components/shopping/tests/browser/browser_shopping_message_triggers.js')
-rw-r--r-- | browser/components/shopping/tests/browser/browser_shopping_message_triggers.js | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/browser/components/shopping/tests/browser/browser_shopping_message_triggers.js b/browser/components/shopping/tests/browser/browser_shopping_message_triggers.js new file mode 100644 index 0000000000..47e4e2a1a7 --- /dev/null +++ b/browser/components/shopping/tests/browser/browser_shopping_message_triggers.js @@ -0,0 +1,315 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs", +}); + +const { ASRouter } = ChromeUtils.importESModule( + "resource:///modules/asrouter/ASRouter.sys.mjs" +); + +const { FeatureCalloutMessages } = ChromeUtils.importESModule( + "resource:///modules/asrouter/FeatureCalloutMessages.sys.mjs" +); + +const OPTED_IN_PREF = "browser.shopping.experience2023.optedIn"; +const ACTIVE_PREF = "browser.shopping.experience2023.active"; +const CFR_ENABLED_PREF = + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features"; + +const CONTENT_PAGE = "https://example.com"; +const PRODUCT_PAGE = "https://example.com/product/B09TJGHL5F"; + +add_setup(async function setup() { + // disable auto-activation to prevent interference with the tests + ShoppingUtils.handledAutoActivate = true; + // clean up all the prefs/states modified by this test + registerCleanupFunction(() => { + ShoppingUtils.handledAutoActivate = false; + }); +}); + +/** Test that the correct callouts show for opted-in users */ +add_task(async function test_fakespot_callouts_opted_in_flow() { + // Re-enable feature callouts for this test. This has to be done in each task + // because they're disabled in browser.ini. + await SpecialPowers.pushPrefEnv({ set: [[CFR_ENABLED_PREF, true]] }); + let sandbox = sinon.createSandbox(); + let routeCFRMessageStub = sandbox + .stub(ASRouter, "routeCFRMessage") + .withArgs( + sinon.match.any, + sinon.match.any, + sinon.match({ id: "shoppingProductPageWithSidebarClosed" }) + ); + await ASRouter._updateMessageProviders(); + await ASRouter.loadMessagesFromAllProviders( + ASRouter.state.providers.filter(p => p.id === "onboarding") + ); + + // Reset opt-in but make the sidebar active so it appears on PDP. + await SpecialPowers.pushPrefEnv({ + set: [ + [ACTIVE_PREF, true], + [OPTED_IN_PREF, 0], + ], + }); + + // Visit a product page and wait for the sidebar to open. + let pdpTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + PRODUCT_PAGE + ); + let pdpBrowser = pdpTab.linkedBrowser; + let pdpBrowserPanel = gBrowser.getPanel(pdpBrowser); + let isSidebarVisible = () => { + let sidebar = pdpBrowserPanel.querySelector("shopping-sidebar"); + return sidebar && BrowserTestUtils.isVisible(sidebar); + }; + await BrowserTestUtils.waitForMutationCondition( + pdpBrowserPanel, + { childList: true, attributeFilter: ["hidden"] }, + isSidebarVisible + ); + ok(isSidebarVisible(), "Shopping sidebar should be open on a product page"); + + // Visiting the PDP should not cause shoppingProductPageWithSidebarClosed to + // fire in this case, because the sidebar is active. + ok( + routeCFRMessageStub.neverCalledWithMatch( + sinon.match.any, + sinon.match.any, + sinon.match({ id: "shoppingProductPageWithSidebarClosed" }) + ), + "shoppingProductPageWithSidebarClosed should not fire when sidebar is active" + ); + + // Now opt in... + let prefChanged = TestUtils.waitForPrefChange( + OPTED_IN_PREF, + value => value === 1 + ); + await SpecialPowers.pushPrefEnv({ set: [[OPTED_IN_PREF, 1]] }); + await prefChanged; + + // Close the sidebar by deactivating the global toggle, and wait for the + // shoppingProductPageWithSidebarClosed trigger to fire. + let shoppingProductPageWithSidebarClosedMsg = new Promise(resolve => { + routeCFRMessageStub.callsFake((message, browser, trigger) => { + if ( + trigger.id === "shoppingProductPageWithSidebarClosed" && + trigger.context.isSidebarClosing + ) { + resolve(message?.id); + } + }); + }); + await SpecialPowers.pushPrefEnv({ set: [[ACTIVE_PREF, false]] }); + // Assert that the message is the one we expect. + is( + await shoppingProductPageWithSidebarClosedMsg, + "FAKESPOT_CALLOUT_CLOSED_OPTED_IN_DEFAULT", + "Should route the expected message: FAKESPOT_CALLOUT_CLOSED_OPTED_IN_DEFAULT" + ); + BrowserTestUtils.removeTab(pdpTab); + + // Now, having seen the on-closed callout, we should expect to see the on-PDP + // callout on the next PDP visit, provided it's been at least 24 hours. + // + // Of course we can't really do that in an automated test, so we'll override + // the message impression date to simulate that. + // + // But first, try opening a PDP so we can test that it _doesn't_ fire if less + // than 24hrs has passed. + + // Visit a product page and wait for routeCFRMessage to fire, expecting the + // message to be null due to targeting. + shoppingProductPageWithSidebarClosedMsg = new Promise(resolve => { + routeCFRMessageStub.callsFake((message, browser, trigger) => { + if ( + trigger.id === "shoppingProductPageWithSidebarClosed" && + !trigger.context.isSidebarClosing + ) { + resolve(message?.id); + } + }); + }); + pdpTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PRODUCT_PAGE); + // Assert that the on-PDP message is not matched, due to targeting. + isnot( + await shoppingProductPageWithSidebarClosedMsg, + "FAKESPOT_CALLOUT_PDP_OPTED_IN_DEFAULT", + "Should not route the on-PDP message because the on-close message was seen recently" + ); + BrowserTestUtils.removeTab(pdpTab); + + // Now override the state so it looks like we closed the sidebar 25 hours ago. + let lastClosedDate = Date.now() - 25 * 60 * 60 * 1000; // 25 hours ago + await ASRouter.setState(state => { + const messageImpressions = { ...state.messageImpressions }; + messageImpressions.FAKESPOT_CALLOUT_CLOSED_OPTED_IN_DEFAULT = [ + lastClosedDate, + ]; + ASRouter._storage.set("messageImpressions", messageImpressions); + return { messageImpressions }; + }); + + // And open a new PDP, expecting the on-PDP message to be routed. + shoppingProductPageWithSidebarClosedMsg = new Promise(resolve => { + routeCFRMessageStub.callsFake((message, browser, trigger) => { + if ( + trigger.id === "shoppingProductPageWithSidebarClosed" && + !trigger.context.isSidebarClosing + ) { + resolve(message?.id); + } + }); + }); + pdpTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PRODUCT_PAGE); + // Assert that the on-PDP message is now matched, due to targeting. + is( + await shoppingProductPageWithSidebarClosedMsg, + "FAKESPOT_CALLOUT_PDP_OPTED_IN_DEFAULT", + "Should route the on-PDP message" + ); + BrowserTestUtils.removeTab(pdpTab); + + // Clean up. + await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); + await ASRouter.setState(state => { + const messageImpressions = { ...state.messageImpressions }; + delete messageImpressions.FAKESPOT_CALLOUT_CLOSED_OPTED_IN_DEFAULT; + ASRouter._storage.set("messageImpressions", messageImpressions); + return { messageImpressions }; + }); + sandbox.restore(); + await ASRouter._updateMessageProviders(); + await ASRouter.loadMessagesFromAllProviders( + ASRouter.state.providers.filter(p => p.id === "onboarding") + ); +}); + +/** Test that the correct callouts show for not-opted-in users */ +add_task(async function test_fakespot_callouts_not_opted_in_flow() { + await SpecialPowers.pushPrefEnv({ set: [[CFR_ENABLED_PREF, true]] }); + let sandbox = sinon.createSandbox(); + let routeCFRMessageStub = sandbox + .stub(ASRouter, "routeCFRMessage") + .withArgs( + sinon.match.any, + sinon.match.any, + sinon.match({ id: "shoppingProductPageWithSidebarClosed" }) + ); + await ASRouter._updateMessageProviders(); + await ASRouter.loadMessagesFromAllProviders( + ASRouter.state.providers.filter(p => p.id === "onboarding") + ); + + // Reset opt-in but make the sidebar active so it appears on PDP. + await SpecialPowers.pushPrefEnv({ + set: [ + [ACTIVE_PREF, true], + [OPTED_IN_PREF, 0], + ], + }); + + // Visit a product page and wait for the sidebar to open. + let pdpTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + PRODUCT_PAGE + ); + let pdpBrowser = pdpTab.linkedBrowser; + let pdpBrowserPanel = gBrowser.getPanel(pdpBrowser); + let isSidebarVisible = () => { + let sidebar = pdpBrowserPanel.querySelector("shopping-sidebar"); + return sidebar && BrowserTestUtils.isVisible(sidebar); + }; + await BrowserTestUtils.waitForMutationCondition( + pdpBrowserPanel, + { childList: true, attributeFilter: ["hidden"] }, + isSidebarVisible + ); + ok(isSidebarVisible(), "Shopping sidebar should be open on a product page"); + + // Close the sidebar by deactivating the global toggle, and wait for the + // shoppingProductPageWithSidebarClosed trigger to fire. + let shoppingProductPageWithSidebarClosedMsg = new Promise(resolve => { + routeCFRMessageStub.callsFake((message, browser, trigger) => { + if ( + trigger.id === "shoppingProductPageWithSidebarClosed" && + trigger.context.isSidebarClosing + ) { + resolve(message?.id); + } + }); + }); + await SpecialPowers.pushPrefEnv({ set: [[ACTIVE_PREF, false]] }); + // Assert that the message is the one we expect. + is( + await shoppingProductPageWithSidebarClosedMsg, + "FAKESPOT_CALLOUT_CLOSED_NOT_OPTED_IN_DEFAULT", + "Should route the expected message: FAKESPOT_CALLOUT_CLOSED_NOT_OPTED_IN_DEFAULT" + ); + BrowserTestUtils.removeTab(pdpTab); + + // Unlike the opted-in flow, at this point we should not expect to see any + // more callouts, because the flow ends after the on-closed callout. So we can + // test that FAKESPOT_CALLOUT_CLOSED_NOT_OPTED_IN_DEFAULT's targeting excludes us + // even if it's been 25 hours since the sidebar was closed. + + // As with the opted-in flow, override the state so it looks like we closed + // the sidebar 25 hours ago. + let lastClosedDate = Date.now() - 25 * 60 * 60 * 1000; // 25 hours ago + await ASRouter.setState(state => { + const messageImpressions = { ...state.messageImpressions }; + messageImpressions.FAKESPOT_CALLOUT_CLOSED_NOT_OPTED_IN_DEFAULT = [ + lastClosedDate, + ]; + ASRouter._storage.set("messageImpressions", messageImpressions); + return { messageImpressions }; + }); + + // Visit a product page and wait for routeCFRMessage to fire, expecting the + // message to be null due to targeting. + shoppingProductPageWithSidebarClosedMsg = new Promise(resolve => { + routeCFRMessageStub.callsFake((message, browser, trigger) => { + if ( + trigger.id === "shoppingProductPageWithSidebarClosed" && + !trigger.context.isSidebarClosing + ) { + resolve(message?.id); + } + }); + }); + pdpTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PRODUCT_PAGE); + // Assert that the on-PDP message is not matched, due to targeting. + isnot( + await shoppingProductPageWithSidebarClosedMsg, + "FAKESPOT_CALLOUT_PDP_OPTED_IN_DEFAULT", + "Should not route the on-PDP message because the user is not opted in" + ); + BrowserTestUtils.removeTab(pdpTab); + + // Clean up. We don't need to verify that the frequency caps work, since + // that's a generic ASRouter feature. + await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); + await ASRouter.setState(state => { + const messageImpressions = { ...state.messageImpressions }; + delete messageImpressions.FAKESPOT_CALLOUT_CLOSED_NOT_OPTED_IN_DEFAULT; + ASRouter._storage.set("messageImpressions", messageImpressions); + return { messageImpressions }; + }); + sandbox.restore(); + await ASRouter._updateMessageProviders(); + await ASRouter.loadMessagesFromAllProviders( + ASRouter.state.providers.filter(p => p.id === "onboarding") + ); +}); |