Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { sinon } = ChromeUtils.importESModule(
);
const { UIState } = ChromeUtils.importESModule(
);
const ENTRY_POINT = "remote-tabs-app-menu-history";
const VARIANTS = {
signin: {
heading: "appmenu-sync-promo-signin",
cta: "appmenu-sync-promo-signin-cta",
image: "chrome://browser/skin/fxa/sync-promo-signin.svg",
pref: "browser.promo.syncPromo.history.signin.dismissed",
},
turnonsync: {
heading: "appmenu-sync-promo-turnonsync",
cta: "appmenu-sync-promo-turnonsync-cta",
image: "chrome://browser/skin/fxa/sync-promo-turnonsync.svg",
pref: "browser.promo.syncPromo.history.turnonsync.dismissed",
},
connectdevice: {
heading: "appmenu-sync-promo-connectdevice",
cta: "appmenu-sync-promo-connectdevice-cta",
image: "chrome://browser/skin/fxa/sync-promo-connectdevice.svg",
pref: "browser.promo.syncPromo.history.connectdevice.dismissed",
},
};
function synthesizeClick(el) {
el.scrollIntoView({ block: "center" });
EventUtils.synthesizeMouseAtCenter(el, {}, el.ownerGlobal);
}
async function openHistoryView() {
await gCUITestUtils.openMainMenu();
let historyView = PanelMultiView.getViewNode(document, "PanelUI-history");
synthesizeClick(document.getElementById("appMenu-history-button"));
await BrowserTestUtils.waitForEvent(historyView, "ViewShown");
return historyView;
}
async function getPromo(historyView) {
let promo = historyView.querySelector("#PanelUI-history-sync-promo");
await customElements.whenDefined("sync-promo");
await customElements.whenDefined("moz-promo");
await customElements.whenDefined("moz-button");
await promo.updateComplete;
return promo;
}
function clearDismissals() {
for (let { pref } of Object.values(VARIANTS)) {
Services.prefs.clearUserPref(pref);
}
}
registerCleanupFunction(clearDismissals);
add_task(async function test_variants() {
let sandbox = sinon.createSandbox();
let promoState = sandbox.stub(gSync, "getSyncPromoState");
let handleAction = sandbox.stub(gSync, "handleSyncPromoAction");
for (let [state, expected] of Object.entries(VARIANTS)) {
clearDismissals();
promoState.returns(state);
let historyView = await openHistoryView();
let promo = await getPromo(historyView);
let manageHistory = historyView.querySelector("#PanelUI-historyMore");
let mozPromo = promo.shadowRoot.querySelector("moz-promo");
let cta = promo.shadowRoot.querySelector("a[slot='support-link']");
await mozPromo.updateComplete;
ok(!promo.hidden, `${state}: promo is shown`);
Assert.deepEqual(
promoState.lastCall.args,
[],
`${state}: eligibility is not gated on any individual engine`
);
is(
manageHistory.nextElementSibling,
promo,
`${state}: promo is below Manage history`
);
is(
mozPromo.getAttribute("data-l10n-id"),
expected.heading,
`${state}: heading matches variant`
);
is(
cta.getAttribute("data-l10n-id"),
expected.cta,
`${state}: CTA matches variant`
);
is(
mozPromo.getAttribute("imagesrc"),
expected.image,
`${state}: illustration matches variant`
);
handleAction.resetHistory();
let popupHidden = BrowserTestUtils.waitForEvent(
document.getElementById("appMenu-popup"),
"popuphidden"
);
synthesizeClick(cta);
ok(handleAction.calledOnce, `${state}: CTA triggers the promo action`);
Assert.deepEqual(
handleAction.firstCall.args,
[state, ENTRY_POINT],
`${state}: action receives the variant and app menu entry point`
);
ok(
!Services.prefs.getBoolPref(expected.pref, false),
`${state}: CTA does not dismiss the promo`
);
await popupHidden;
}
sandbox.restore();
clearDismissals();
});
add_task(async function test_per_variant_dismissal() {
let sandbox = sinon.createSandbox();
let promoState = sandbox.stub(gSync, "getSyncPromoState");
sandbox.stub(gSync, "handleSyncPromoAction");
clearDismissals();
promoState.returns("signin");
let historyView = await openHistoryView();
let promo = await getPromo(historyView);
let mozPromo = promo.shadowRoot.querySelector("moz-promo");
synthesizeClick(mozPromo.closeButton);
await promo.updateComplete;
ok(promo.hidden, "Dismissing hides the promo");
ok(
Services.prefs.getBoolPref(VARIANTS.signin.pref, false),
"Dismissal is persisted for the signin variant"
);
await gCUITestUtils.hideMainMenu();
historyView = await openHistoryView();
promo = await getPromo(historyView);
ok(promo.hidden, "Dismissed signin variant stays hidden");
promoState.returns("connectdevice");
Services.obs.notifyObservers(null, "sync-ui-state:update");
await promo.updateComplete;
mozPromo = promo.shadowRoot.querySelector("moz-promo");
ok(!promo.hidden, "A different variant is not suppressed by the dismissal");
is(
mozPromo.getAttribute("data-l10n-id"),
VARIANTS.connectdevice.heading,
"The connectdevice variant is shown after dismissing signin"
);
await gCUITestUtils.hideMainMenu();
sandbox.restore();
clearDismissals();
});
add_task(async function test_state_transitions() {
let sandbox = sinon.createSandbox();
let promoState = sandbox.stub(gSync, "getSyncPromoState");
sandbox.stub(gSync, "handleSyncPromoAction");
clearDismissals();
promoState.returns("signin");
let historyView = await openHistoryView();
let promo = await getPromo(historyView);
ok(!promo.hidden, "Promo shown for eligible user");
promoState.returns("turnonsync");
Services.obs.notifyObservers(null, "sync-ui-state:update");
await promo.updateComplete;
let mozPromo = promo.shadowRoot.querySelector("moz-promo");
is(
mozPromo.getAttribute("data-l10n-id"),
VARIANTS.turnonsync.heading,
"Promo updates to the new eligible variant"
);
promoState.returns(null);
Services.obs.notifyObservers(null, "sync-ui-state:update");
await promo.updateComplete;
ok(promo.hidden, "Promo disappears when the user becomes ineligible");
await gCUITestUtils.hideMainMenu();
sandbox.restore();
clearDismissals();
});
add_task(async function test_sync_eligibility_ignores_engines() {
let sandbox = sinon.createSandbox();
let syncEnabled = true;
sandbox.stub(UIState, "get").callsFake(() => ({
status: UIState.STATUS_SIGNED_IN,
syncEnabled,
}));
sandbox
.stub(fxAccounts.device, "recentDeviceList")
.get(() => [{ isCurrentDevice: true }, { isCurrentDevice: false }]);
// Stub this to prevent error spam on tests trying to fetch attached clients
sandbox.stub(gSync, "updateAllUI");
clearDismissals();
await SpecialPowers.pushPrefEnv({
set: [
["services.sync.engine.tabs", false],
["services.sync.engine.history", false],
],
});
let historyView = await openHistoryView();
let promo = await getPromo(historyView);
ok(promo.hidden, "Promo hidden when only its engines are disabled");
syncEnabled = false;
Services.obs.notifyObservers(null, "sync-ui-state:update");
await promo.updateComplete;
ok(!promo.hidden, "Promo shown while Sync is disabled on this installation");
is(
promo.shadowRoot.querySelector("moz-promo").getAttribute("data-l10n-id"),
VARIANTS.turnonsync.heading,
"Disabled Sync shows the turnonsync variant"
);
syncEnabled = true;
Services.obs.notifyObservers(null, "sync-ui-state:update");
await promo.updateComplete;
ok(promo.hidden, "Promo removed once Sync is enabled on this installation");
await SpecialPowers.popPrefEnv();
await gCUITestUtils.hideMainMenu();
sandbox.restore();
clearDismissals();
});
add_task(async function test_action_dispatch() {
let sandbox = sinon.createSandbox();
let signin = sandbox.stub(gSync, "openFxAEmailFirstPage");
let turnon = sandbox.stub(gSync, "openSyncSetupForEntryPoint");
let connect = sandbox.stub(gSync, "openConnectAnotherDevice");
gSync.handleSyncPromoAction("signin", "entry");
ok(signin.calledOnceWith("entry"), "signin opens the email-first page");
gSync.handleSyncPromoAction("turnonsync", "entry");
ok(turnon.calledOnceWith("entry"), "turnonsync opens the sync setup flow");
gSync.handleSyncPromoAction("connectdevice", "entry");
ok(
connect.calledOnceWith("entry"),
"connectdevice opens connect-another-device"
);
sandbox.restore();
});
add_task(async function test_menubar_action_dispatch() {
let sandbox = sinon.createSandbox();
let handleAction = sandbox.stub(gSync, "handleSyncPromoAction");
let promo = document.getElementById("historyRemoteTabsPromo");
let historyMenu = document.getElementById("history-menu");
let placesView = historyMenu._placesView;
historyMenu._placesView = { _onCommand() {} };
promo.dataset.action = "signin";
try {
promo.dispatchEvent(new CustomEvent("command", { bubbles: true }));
ok(
handleAction.calledOnceWith("signin", "remote-tabs-top-menu-history"),
"The menu-bar promo dispatches its action with the menu-bar entry point"
);
} finally {
historyMenu._placesView = placesView;
sandbox.restore();
}
});
// The History view is also shown in a standalone panel when its widget lives on
// the toolbar, so the CTA must close whichever panel it is actually in.
add_task(async function test_cta_closes_standalone_panel() {
let sandbox = sinon.createSandbox();
sandbox.stub(gSync, "getSyncPromoState").returns("signin");
sandbox.stub(gSync, "handleSyncPromoAction");
clearDismissals();
CustomizableUI.addWidgetToArea(
"history-panelmenu",
CustomizableUI.AREA_NAVBAR
);
let button = document.getElementById("history-panelmenu");
let historyView = PanelMultiView.getViewNode(document, "PanelUI-history");
let shown = BrowserTestUtils.waitForEvent(historyView, "ViewShown");
// Same call CustomizableUI makes for a view widget anchored in the toolbar.
PanelUI.showSubView("PanelUI-history", button);
await shown;
let promo = await getPromo(historyView);
let panel = promo.closest("panel");
isnot(
panel.id,
"appMenu-popup",
"History view is shown outside the app menu panel"
);
let hidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
synthesizeClick(promo.shadowRoot.querySelector("a[slot='support-link']"));
await hidden;
ok(true, "Activating the CTA closes the containing panel");
CustomizableUI.reset();
sandbox.restore();
clearDismissals();
});
// Closing a window tears its document down without running
// disconnectedCallback, so the promo has to drop its observers on unload or the
// observer service keeps the whole window alive until shutdown.
add_task(async function test_observers_removed_when_window_closes() {
let countObservers = () =>
[...Services.obs.enumerateObservers("sync-ui-state:update")].length;
let before = countObservers();
let win = await BrowserTestUtils.openNewBrowserWindow();
await new CustomizableUITestUtils(win).openMainMenu();
let historyView = PanelMultiView.getViewNode(win.document, "PanelUI-history");
let shown = BrowserTestUtils.waitForEvent(historyView, "ViewShown");
win.document.getElementById("appMenu-history-button").click();
await shown;
let promo = historyView.querySelector("#PanelUI-history-sync-promo");
await promo.updateComplete;
Assert.greater(
countObservers(),
before,
"The promo in the new window registered its observers"
);
await BrowserTestUtils.closeWindow(win);
is(
countObservers(),
before,
"Closing the window removed the promo's observers"
);
});