/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /* eslint max-len: ["error", 80] */ "use strict"; const { AddonTestUtils } = ChromeUtils.importESModule( "resource://testing-common/AddonTestUtils.sys.mjs" ); AddonTestUtils.initMochitest(this); function makeResult({ guid, type }) { return { addon: { authors: [{ name: "Some author" }], current_version: { files: [{ platform: "all", url: "data:," }], }, url: "data:,", guid, type, }, }; } function mockResults() { let types = ["extension", "theme", "extension", "extension", "theme"]; return { results: types.map((type, i) => makeResult({ guid: `${type}${i}@mochi.test`, type, }) ), }; } add_setup(async function () { let results = btoa(JSON.stringify(mockResults())); await SpecialPowers.pushPrefEnv({ set: [ // Disable personalized recommendations, they will break the data URI. ["browser.discovery.enabled", false], ["extensions.getAddons.discovery.api_url", `data:;base64,${results}`], [ "extensions.recommendations.themeRecommendationUrl", "https://example.com/theme", ], ], }); }); function checkExtraContents(doc, type, opts = {}) { let { showThemeRecommendationFooter = type === "theme" } = opts; let footer = doc.querySelector("footer"); let amoButton = footer.querySelector('[action="open-amo"]'); let privacyPolicyLink = footer.querySelector(".privacy-policy-link"); let themeRecommendationFooter = footer.querySelector(".theme-recommendation"); let themeRecommendationLink = themeRecommendationFooter && themeRecommendationFooter.querySelector("a"); let taarNotice = doc.querySelector("taar-notice"); is_element_visible(footer, "The footer is visible"); if (type == "extension") { ok(taarNotice, "There is a TAAR notice"); is_element_visible(amoButton, "The AMO button is shown"); is_element_visible(privacyPolicyLink, "The privacy policy is visible"); } else if (type == "theme") { ok(!taarNotice, "There is no TAAR notice"); ok(amoButton, "AMO button is shown"); ok(!privacyPolicyLink, "There is no privacy policy"); } else { throw new Error(`Unknown type ${type}`); } if (showThemeRecommendationFooter) { is_element_visible( themeRecommendationFooter, "There's a theme recommendation footer" ); is_element_visible(themeRecommendationLink, "There's a link to the theme"); is(themeRecommendationLink.target, "_blank", "The link opens in a new tab"); is( themeRecommendationLink.href, "https://example.com/theme", "The link goes to the pref's URL" ); is( doc.l10n.getAttributes(themeRecommendationFooter).id, "recommended-theme-1", "The recommendation has the right l10n-id" ); } else { ok( !themeRecommendationFooter || themeRecommendationFooter.hidden, "There's no theme recommendation" ); } } async function installAddon({ card, recommendedList, manifestExtra = {} }) { // Install an add-on to hide the card. let hidden = BrowserTestUtils.waitForEvent( recommendedList, "card-hidden", false, e => e.detail.card == card ); let extension = ExtensionTestUtils.loadExtension({ manifest: { browser_specific_settings: { gecko: { id: card.addonId } }, ...manifestExtra, }, useAddonManager: "temporary", }); await extension.startup(); await hidden; return extension; } async function testListRecommendations({ type, manifestExtra = {} }) { let win = await loadInitialView(type); let doc = win.document; // Wait for the list to render, rendering is tested with the discovery pane. let recommendedList = doc.querySelector("recommended-addon-list"); await recommendedList.cardsReady; checkExtraContents(doc, type); // Check that the cards are all for the right type. let cards = doc.querySelectorAll("recommended-addon-card"); ok(!!cards.length, "There were some cards found"); for (let card of cards) { is(card.discoAddon.type, type, `The card is for a ${type}`); is_element_visible(card, "The card is visible"); } // Install an add-on for the first card, verify it is hidden. let { addonId } = cards[0]; ok(addonId, "The card has an addonId"); // Installing the add-on will fail since the URL doesn't point to a valid // XPI. let installButton = cards[0].querySelector('[action="install-addon"]'); let { panel } = PopupNotifications; let popupId = "addon-install-failed-notification"; let failPromise = TestUtils.topicObserved("addon-install-failed"); installButton.click(); await failPromise; // Wait for the installing popup to be hidden and leave just the error popup. await BrowserTestUtils.waitForCondition(() => { return panel.children.length == 1 && panel.firstElementChild.id == popupId; }); // Dismiss the popup. panel.firstElementChild.button.click(); await BrowserTestUtils.waitForPopupEvent(panel, "hidden"); let extension = await installAddon({ card: cards[0], recommendedList }); is_element_hidden(cards[0], "The card is now hidden"); // Switch away and back, there should still be a hidden card. await closeView(win); win = await loadInitialView(type); doc = win.document; recommendedList = doc.querySelector("recommended-addon-list"); await recommendedList.cardsReady; cards = Array.from(doc.querySelectorAll("recommended-addon-card")); let hiddenCard = cards.pop(); is(hiddenCard.addonId, addonId, "The expected card was found"); is_element_hidden(hiddenCard, "The card is still hidden"); ok(!!cards.length, "There are still some visible cards"); for (let card of cards) { is(card.discoAddon.type, type, `The card is for a ${type}`); is_element_visible(card, "The card is visible"); } // Uninstall the add-on, verify the card is shown again. let shown = BrowserTestUtils.waitForEvent(recommendedList, "card-shown"); await extension.unload(); await shown; is_element_visible(hiddenCard, "The card is now shown"); await closeView(win); } add_task(async function testExtensionList() { await testListRecommendations({ type: "extension" }); }); add_task(async function testThemeList() { await testListRecommendations({ type: "theme", manifestExtra: { theme: {} }, }); }); add_task(async function testInstallAllExtensions() { let type = "extension"; let win = await loadInitialView(type); let doc = win.document; // Wait for the list to render, rendering is tested with the discovery pane. let recommendedList = doc.querySelector("recommended-addon-list"); await recommendedList.cardsReady; // Find more button is shown. checkExtraContents(doc, type); let cards = Array.from(doc.querySelectorAll("recommended-addon-card")); is(cards.length, 3, "We found some cards"); let extensions = await Promise.all( cards.map(card => installAddon({ card, recommendedList })) ); // The find more on AMO button is shown. checkExtraContents(doc, type); // Uninstall one of the extensions, the button should still be shown. let extension = extensions.pop(); let shown = BrowserTestUtils.waitForEvent(recommendedList, "card-shown"); await extension.unload(); await shown; // The find more on AMO button is shown. checkExtraContents(doc, type); await Promise.all(extensions.map(extension => extension.unload())); await closeView(win); }); add_task(async function testError() { await SpecialPowers.pushPrefEnv({ set: [["extensions.getAddons.discovery.api_url", "data:,"]], }); let win = await loadInitialView("extension"); let doc = win.document; // Wait for the list to render, rendering is tested with the discovery pane. let recommendedList = doc.querySelector("recommended-addon-list"); await recommendedList.cardsReady; checkExtraContents(doc, "extension"); await closeView(win); await SpecialPowers.popPrefEnv(); }); add_task(async function testThemesNoRecommendationUrl() { await SpecialPowers.pushPrefEnv({ set: [["extensions.recommendations.themeRecommendationUrl", ""]], }); let win = await loadInitialView("theme"); let doc = win.document; // Wait for the list to render, rendering is tested with the discovery pane. let recommendedList = doc.querySelector("recommended-addon-list"); await recommendedList.cardsReady; checkExtraContents(doc, "theme", { showThemeRecommendationFooter: false }); await closeView(win); await SpecialPowers.popPrefEnv(); }); add_task(async function testRecommendationsDisabled() { await SpecialPowers.pushPrefEnv({ set: [["extensions.htmlaboutaddons.recommendations.enabled", false]], }); let types = ["extension", "theme"]; for (let type of types) { let win = await loadInitialView(type); let doc = win.document; let recommendedList = doc.querySelector("recommended-addon-list"); ok(!recommendedList, `There are no recommendations on the ${type} page`); await closeView(win); } await SpecialPowers.popPrefEnv(); });