diff --git a/toolkit/mozapps/extensions/content/aboutaddons.js b/toolkit/mozapps/extensions/content/aboutaddons.js
index a717d91a9cc0..d4188856c2dd 100644
--- a/toolkit/mozapps/extensions/content/aboutaddons.js
+++ b/toolkit/mozapps/extensions/content/aboutaddons.js
@@ -3693,6 +3693,12 @@ class AddonCard extends HTMLElement {
customElements.define("addon-card", AddonCard);
class ColorwayClosetCard extends HTMLElement {
+ connectedCallback() {
+ if (this.childElementCount === 0) {
+ this.render();
+ }
+ }
+
render() {
let card = importTemplate("card").firstElementChild;
let heading = card.querySelector(".addon-name-container");
@@ -4144,7 +4150,11 @@ class AddonList extends HTMLElement {
}
createSectionHeading(headingIndex) {
- let { headingId, subheadingId } = this.sections[headingIndex];
+ let {
+ headingId,
+ subheadingId,
+ sectionPreambleCustomElement,
+ } = this.sections[headingIndex];
let frag = document.createDocumentFragment();
let heading = document.createElement("h2");
heading.classList.add("list-section-heading");
@@ -4154,11 +4164,19 @@ class AddonList extends HTMLElement {
if (subheadingId) {
let subheading = document.createElement("h3");
subheading.classList.add("list-section-subheading");
- heading.className = "header-name";
document.l10n.setAttributes(subheading, subheadingId);
+ // Preserve the old colorway section header styling
+ // while the colorway closet section is not yet ready to be enabled
+ if (!COLORWAY_CLOSET_ENABLED) {
+ heading.className = "header-name";
+ }
frag.append(subheading);
}
+ if (sectionPreambleCustomElement) {
+ frag.append(document.createElement(sectionPreambleCustomElement));
+ }
+
return frag;
}
@@ -4190,9 +4208,12 @@ class AddonList extends HTMLElement {
}
updateSectionIfEmpty(section) {
- // The header is added before any add-on cards, so if there's only one
- // child then it's the header. In that case we should empty out the section.
- if (section.children.length == 1) {
+ // We should empty out the section if there are no more cards to display,
+ // (unless the section is configured to stay visible and rendered even when
+ // there is no addon listed, e.g. the "Colorways Closet" section).
+ const sectionIndex = parseInt(section.getAttribute("section"));
+ const { shouldRenderIfEmpty } = this.sections[sectionIndex];
+ if (!this.getCards(section).length && !shouldRenderIfEmpty) {
section.textContent = "";
}
}
@@ -4201,8 +4222,12 @@ class AddonList extends HTMLElement {
let section = this.getSection(sectionIndex);
let sectionCards = this.getCards(section);
- // If this is the first card in the section, create the heading.
- if (!sectionCards.length) {
+ const { shouldRenderIfEmpty } = this.sections[sectionIndex];
+
+ // If this is the first card in the section, and the section
+ // isn't configure to render the headers even when empty,
+ // we have to create the section heading first.
+ if (!shouldRenderIfEmpty && !sectionCards.length) {
section.appendChild(this.createSectionHeading(sectionIndex));
}
@@ -4392,7 +4417,7 @@ class AddonList extends HTMLElement {
}
renderSection(addons, index) {
- const { sectionClass, sectionPreambleCustomElement } = this.sections[index];
+ const { sectionClass, shouldRenderIfEmpty } = this.sections[index];
let section = document.createElement("section");
section.setAttribute("section", index);
@@ -4401,20 +4426,15 @@ class AddonList extends HTMLElement {
}
// Render the heading and add-ons if there are any.
- if (addons.length) {
- if (sectionPreambleCustomElement) {
- section.appendChild(
- document.createElement(sectionPreambleCustomElement)
- );
- }
+ if (shouldRenderIfEmpty || addons.length) {
section.appendChild(this.createSectionHeading(index));
+ }
- for (let addon of addons) {
- let card = document.createElement("addon-card");
- card.setAddon(addon);
- card.render();
- section.appendChild(card);
- }
+ for (let addon of addons) {
+ let card = document.createElement("addon-card");
+ card.setAddon(addon);
+ card.render();
+ section.appendChild(card);
}
return section;
@@ -4517,22 +4537,6 @@ class AddonList extends HTMLElement {
}
customElements.define("addon-list", AddonList);
-class ColorwayClosetList extends HTMLElement {
- connectedCallback() {
- this.appendChild(importTemplate(this.template));
- let frag = document.createDocumentFragment();
- let card = document.createElement("colorways-card");
- card.render();
- frag.append(card);
- this.append(frag);
- }
-
- get template() {
- return "colorways-list";
- }
-}
-customElements.define("colorways-list", ColorwayClosetList);
-
class RecommendedAddonList extends HTMLElement {
connectedCallback() {
if (this.isConnected) {
@@ -4828,15 +4832,12 @@ gViewController.defineView("list", async type => {
return null;
}
- // If monochromatic themes are enabled and any are builtin to Firefox, we
- // display those themes together in a separate subsection.
const areColorwayThemesInstalled = async () =>
(await AddonManager.getAllAddons()).some(
addon =>
BuiltInThemes.isMonochromaticTheme(addon.id) &&
!BuiltInThemes.themeIsExpired(addon.id)
);
-
let frag = document.createDocumentFragment();
let list = document.createElement("addon-list");
list.type = type;
@@ -4848,43 +4849,68 @@ gViewController.defineView("list", async type => {
filterFn: addon =>
!addon.hidden && addon.isActive && !isPending(addon, "uninstall"),
},
- {
- headingId: getL10nIdMapping(`${type}-disabled-heading`),
- sectionClass: `${type}-disabled-section`,
+ ];
+
+ if (type == "theme" && COLORWAY_CLOSET_ENABLED) {
+ MozXULElement.insertFTLIfNeeded("preview/colorwaycloset.ftl");
+
+ const hasActiveColorways = !!BuiltInThemes.findActiveColorwayCollection?.();
+ sections.push({
+ headingId: "theme-monochromatic-heading",
+ subheadingId: "theme-monochromatic-subheading",
+ sectionClass: "colorways-section",
+ // Insert colorway closet card as the first element in the colorways
+ // section so that it is above any retained colorway themes.
+ sectionPreambleCustomElement: hasActiveColorways
+ ? "colorways-card"
+ : null,
+ // This section should also be rendered when there is no addons that
+ // match the filterFn, because we still want to show the headers and
+ // colorways-card. But, we only expect the colorways-card to be visible
+ // when there is an active colorway collection.
+ shouldRenderIfEmpty: hasActiveColorways,
filterFn: addon =>
!addon.hidden &&
!addon.isActive &&
!isPending(addon, "uninstall") &&
// For performance related details about this check see the
// documentation for themeIsExpired in BuiltInThemeConfig.jsm.
- (!BuiltInThemes.isMonochromaticTheme(addon.id) ||
- BuiltInThemes.isRetainedExpiredTheme(addon.id)),
- },
- ];
-
- let colorwaysThemeInstalled;
- if (type == "theme") {
- colorwaysThemeInstalled = await areColorwayThemesInstalled();
- if (colorwaysThemeInstalled && COLORWAY_CLOSET_ENABLED) {
- // Avoid inserting colorway closet fluent strings in aboutaddons.html,
- // considering that there is a dependency on a browser-only resource.
- const fluentResourceId = "preview/colorwaycloset.ftl";
- if (!document.head.querySelector(`link[href='${fluentResourceId}']`)) {
- const fluentLink = document.createElement("link");
- fluentLink.setAttribute("rel", "localization");
- fluentLink.setAttribute("href", fluentResourceId);
- document.head.appendChild(fluentLink);
- }
- // Insert colorway closet card as the first element so that
- // it is positioned between the enabled and disabled sections.
- sections[1].sectionPreambleCustomElement = "colorways-list";
- }
+ BuiltInThemes.isMonochromaticTheme(addon.id) &&
+ BuiltInThemes.isRetainedExpiredTheme(addon.id),
+ });
}
+ const disabledAddonsFilterFn = addon =>
+ !addon.hidden && !addon.isActive && !isPending(addon, "uninstall");
+
+ const disabledThemesFilterFn = addon =>
+ disabledAddonsFilterFn(addon) &&
+ ((BuiltInThemes.isRetainedExpiredTheme(addon.id) &&
+ !COLORWAY_CLOSET_ENABLED) ||
+ !BuiltInThemes.isMonochromaticTheme(addon.id));
+
+ sections.push({
+ headingId: getL10nIdMapping(`${type}-disabled-heading`),
+ sectionClass: `${type}-disabled-section`,
+ filterFn: addon => {
+ if (addon.type === "theme") {
+ return disabledThemesFilterFn(addon);
+ }
+ return disabledAddonsFilterFn(addon);
+ },
+ });
+
list.setSections(sections);
frag.appendChild(list);
- if (type == "theme" && colorwaysThemeInstalled) {
+ // Add old colorways section if the new colorway closet is not enabled.
+ // If monochromatic themes are enabled and any are builtin to Firefox, we
+ // display those themes together in a separate subsection.
+ if (
+ type == "theme" &&
+ !COLORWAY_CLOSET_ENABLED &&
+ (await areColorwayThemesInstalled())
+ ) {
let monochromaticList = document.createElement("addon-list");
monochromaticList.classList.add("monochromatic-addon-list");
monochromaticList.type = type;
diff --git a/toolkit/mozapps/extensions/test/browser/browser_colorwaycloset_aboutaddons.js b/toolkit/mozapps/extensions/test/browser/browser_colorwaycloset_aboutaddons.js
index c4fab48f5ac3..24b8ba6101be 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_colorwaycloset_aboutaddons.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_colorwaycloset_aboutaddons.js
@@ -11,10 +11,14 @@ const { BuiltInThemeConfig } = ChromeUtils.import(
const { ColorwayClosetOpener } = ChromeUtils.import(
"resource:///modules/ColorwayClosetOpener.jsm"
);
+const { BuiltInThemes } = ChromeUtils.import(
+ "resource:///modules/BuiltInThemes.jsm"
+);
AddonTestUtils.initMochitest(this);
const kTestThemeId = "test-colorway@mozilla.org";
+const kTestExpiredThemeId = `expired-${kTestThemeId}`;
// Return a mock expiry date set 1 year ahead from the current date.
function getMockExpiry() {
@@ -23,6 +27,66 @@ function getMockExpiry() {
return expireDate;
}
+function getMockThemeXpi(id) {
+ return AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ name: "Monochromatic Theme",
+ applications: { gecko: { id } },
+ theme: {},
+ },
+ });
+}
+
+function setMockThemeToExpired(id) {
+ let yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+ yesterday = yesterday.toISOString().split("T")[0];
+ // Add the test theme to our list of built-in themes so that aboutaddons.js
+ // will think this theme is expired.
+ BuiltInThemes.builtInThemeMap.set(id, {
+ version: "1.0",
+ expiry: yesterday,
+ // We use the manifest from Light theme since we know it will be in-tree
+ // indefinitely.
+ path: "resource://builtin-themes/light/",
+ });
+}
+
+function setBuiltInThemeConfigMock(...args) {
+ info("Mocking BuiltInThemeConfig.findActiveColorwaysCollection");
+ BuiltInThemeConfig.findActiveColorwayCollection = () => {
+ // Return no active collection
+ if (!args || !args.length) {
+ info("Return no active collection");
+ return null;
+ }
+
+ const { mockExpiry, mockL10nId } = args[0];
+ info(
+ `Return mock active colorway collection with expiry set to: ${
+ mockExpiry.toUTCString().split("T")[0]
+ }`
+ );
+ return {
+ id: "colorway-test-collection",
+ expiry: mockExpiry,
+ l10nId: {
+ title: mockL10nId,
+ },
+ };
+ };
+}
+
+function clearBuiltInThemeConfigMock(originalFindActiveCollection) {
+ info("Cleaning up BuiltInThemeConfigMock");
+ if (
+ BuiltInThemeConfig.findActiveColorwayCollection !==
+ originalFindActiveCollection
+ ) {
+ BuiltInThemeConfig.findActiveColorwayCollection = originalFindActiveCollection;
+ }
+}
+
add_setup(async function() {
info("Register mock fluent locale strings");
@@ -89,15 +153,9 @@ add_task(async function testColorwayClosetPrefEnabled() {
// Mock BuiltInThemeConfig.findActiveColorwaysCollection with test colorways.
const originalFindActiveCollection =
BuiltInThemeConfig.findActiveColorwayCollection;
- const clearBuiltInThemeConfigMock = () => {
- if (
- BuiltInThemeConfig.findActiveColorwayCollection !==
- originalFindActiveCollection
- ) {
- BuiltInThemeConfig.findActiveColorwayCollection = originalFindActiveCollection;
- }
- };
- registerCleanupFunction(clearBuiltInThemeConfigMock);
+ registerCleanupFunction(() => {
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
+ });
// Mock collection l10n part of the mocked fluent resources.
const mockL10nId = "colorway-collection-test-mock";
@@ -118,27 +176,9 @@ add_task(async function testColorwayClosetPrefEnabled() {
);
}
- info("Now mocking BuiltInThemeConfig.findActiveColorwayCollection");
- BuiltInThemeConfig.findActiveColorwayCollection = () => {
- info(
- `Return mock active colorway collection with expiry set to: ${
- mockExpiry.toUTCString().split("T")[0]
- }`
- );
- return {
- id: "colorway-test-collection",
- expiry: mockExpiry,
- l10nId: { title: mockL10nId },
- };
- };
+ setBuiltInThemeConfigMock({ mockExpiry, mockL10nId });
- const themeXpi = AddonTestUtils.createTempWebExtensionFile({
- manifest: {
- name: "Monochromatic Theme",
- applications: { gecko: { id: kTestThemeId } },
- theme: {},
- },
- });
+ const themeXpi = getMockThemeXpi(kTestThemeId);
const { addon } = await AddonTestUtils.promiseInstallFile(themeXpi);
let win = await loadInitialView("theme");
@@ -151,28 +191,27 @@ add_task(async function testColorwayClosetPrefEnabled() {
// Add mocked fluent resources for the mocked active colorway collection.
doc.l10n.addResourceIds(["mock-colorwaycloset.ftl"]);
- let colorwayClosetList = doc.querySelector("colorways-list");
+ let colorwaySection = getSection(doc, "colorways-section");
+ ok(colorwaySection, "colorway section was found");
// Make sure fluent strings have all been translated before
// asserting the expected element to not have empty textContent.
- await doc.l10n.translateFragment(colorwayClosetList);
+ await doc.l10n.translateFragment(colorwaySection);
info("Verifying colorway closet list contents");
- ok(colorwayClosetList, "colorway closet list was found");
ok(
- colorwayClosetList.querySelector("#colorways-section-heading"),
+ colorwaySection.querySelector(".list-section-heading"),
"colorway closet heading was found"
);
ok(
- colorwayClosetList.querySelector("#colorways-section-subheading"),
+ colorwaySection.querySelector(".list-section-subheading"),
"colorway closet subheading was found"
);
- let cards = colorwayClosetList.querySelectorAll("colorways-card");
- ok(cards.length, "At least one colorway closet card was found");
+ let card = colorwaySection.querySelector("colorways-card");
+ ok(card, "colorway closet card was found");
info("Verifying colorway closet card contents");
- let card = cards[0];
ok(
card.querySelector("#colorways-preview-text-container"),
"Preview text container found"
@@ -210,7 +249,8 @@ add_task(async function testColorwayClosetPrefEnabled() {
await closeView(win);
await addon.uninstall(true);
- clearBuiltInThemeConfigMock();
+ await SpecialPowers.popPrefEnv();
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
});
/**
@@ -221,23 +261,17 @@ add_task(async function testColorwayClosetSectionPrefDisabled() {
await SpecialPowers.pushPrefEnv({
set: [["browser.theme.colorway-closet", false]],
});
- const themeXpi = AddonTestUtils.createTempWebExtensionFile({
- manifest: {
- name: "Monochromatic Theme",
- applications: { gecko: { id: kTestThemeId } },
- theme: {},
- },
- });
+ const themeXpi = getMockThemeXpi(kTestThemeId);
const { addon } = await AddonTestUtils.promiseInstallFile(themeXpi);
let win = await loadInitialView("theme");
let doc = win.document;
- let colorwayClosetList = doc.querySelector("colorways-list");
-
- ok(!colorwayClosetList, "colorway closet list should not be found");
+ let colorwaySection = getSection(doc, "colorways-section");
+ ok(!colorwaySection, "colorway section should not be found");
await closeView(win);
await addon.uninstall(true);
+ await SpecialPowers.popPrefEnv();
});
/**
@@ -249,6 +283,20 @@ add_task(async function testButtonOpenModal() {
set: [["browser.theme.colorway-closet", true]],
});
+ // Mock BuiltInThemeConfig.findActiveColorwaysCollection with test colorways.
+ const originalFindActiveCollection =
+ BuiltInThemeConfig.findActiveColorwayCollection;
+ registerCleanupFunction(() => {
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
+ });
+
+ // Mock collection l10n part of the mocked fluent resources.
+ const mockL10nId = "colorway-collection-test-mock";
+
+ // Mock expiry date string and BuiltInThemeConfig.findActiveColorwayCollection()
+ const mockExpiry = getMockExpiry();
+ setBuiltInThemeConfigMock({ mockExpiry, mockL10nId });
+
let originalOpenModal = ColorwayClosetOpener.openModal;
const clearOpenModalMock = () => {
if (originalOpenModal) {
@@ -258,25 +306,19 @@ add_task(async function testButtonOpenModal() {
};
registerCleanupFunction(clearOpenModalMock);
- const themeXpi = AddonTestUtils.createTempWebExtensionFile({
- manifest: {
- name: "Monochromatic Theme",
- applications: { gecko: { id: kTestThemeId } },
- theme: {},
- },
- });
+ const themeXpi = getMockThemeXpi(kTestThemeId);
const { addon } = await AddonTestUtils.promiseInstallFile(themeXpi);
let win = await loadInitialView("theme");
let doc = win.document;
- let colorwayClosetList = doc.querySelector("colorways-list");
+ let colorwaySection = getSection(doc, "colorways-section");
- ok(colorwayClosetList, "colorway closet list was found");
+ ok(colorwaySection, "colorway section was found");
- let cards = colorwayClosetList.querySelectorAll("colorways-card");
- ok(cards.length, "At least one colorway closet card was found");
+ let card = colorwaySection.querySelector("colorways-card");
+ ok(card, "colorway closet card was found");
- let colorwaysButton = cards[0].querySelector("#colorways-button");
+ let colorwaysButton = card.querySelector("#colorways-button");
ok(colorwaysButton, "colorway collection button found");
let colorwayOpenerPromise = new Promise(resolve => {
ColorwayClosetOpener.openModal = () => {
@@ -291,4 +333,191 @@ add_task(async function testButtonOpenModal() {
await closeView(win);
await addon.uninstall(true);
clearOpenModalMock();
+ await SpecialPowers.popPrefEnv();
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
+});
+
+/**
+ * Tests that disabled retained expired colorways appear in the list of retained
+ * colorway themes, while disabled unexpired ones do not.
+ */
+add_task(async function testColorwayClosetSectionOneRetainedOneUnexpired() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.theme.colorway-closet", true]],
+ });
+
+ // Mock BuiltInThemeConfig.findActiveColorwaysCollection with test colorways.
+ const originalFindActiveCollection =
+ BuiltInThemeConfig.findActiveColorwayCollection;
+ registerCleanupFunction(() => {
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
+ });
+
+ // Mock collection l10n part of the mocked fluent resources.
+ const mockL10nId = "colorway-collection-test-mock";
+
+ // Mock expiry date string and BuiltInThemeConfig.findActiveColorwayCollection()
+ const mockExpiry = getMockExpiry();
+ setBuiltInThemeConfigMock({ mockExpiry, mockL10nId });
+
+ // Set expired theme as a retained colorway theme
+ const retainedThemePrefName = "browser.theme.retainedExpiredThemes";
+ await SpecialPowers.pushPrefEnv({
+ set: [[retainedThemePrefName, JSON.stringify([kTestExpiredThemeId])]],
+ });
+ const themeXpiExpiredAddon = getMockThemeXpi(kTestExpiredThemeId);
+ const expiredAddon = (
+ await AddonTestUtils.promiseInstallFile(themeXpiExpiredAddon)
+ ).addon;
+
+ // Set up a valid addon that acts as a colorway theme that is not yet expired
+ const validThemeId = `valid-${kTestThemeId}`;
+ const themeXpiValidAddon = getMockThemeXpi(validThemeId);
+ const validAddon = (
+ await AddonTestUtils.promiseInstallFile(themeXpiValidAddon)
+ ).addon;
+
+ await expiredAddon.disable();
+ await validAddon.disable();
+
+ // Make the test theme appear expired.
+ setMockThemeToExpired(kTestExpiredThemeId);
+ registerCleanupFunction(() => {
+ BuiltInThemes.builtInThemeMap.delete(kTestExpiredThemeId);
+ });
+
+ let win = await loadInitialView("theme");
+ let doc = win.document;
+ let colorwaySection = getSection(doc, "colorways-section");
+
+ info("Verifying colorway section order of elements");
+ ok(
+ colorwaySection.children.length,
+ "colorway section should have at least 1 element"
+ );
+ is(
+ colorwaySection.children[0].classList[0],
+ "list-section-heading",
+ "colorway section header should be first"
+ );
+ is(
+ colorwaySection.children[1].classList[0],
+ "list-section-subheading",
+ "colorway section subheader should be second"
+ );
+ is(
+ colorwaySection.children[2].tagName.toLowerCase(),
+ "colorways-card",
+ "colorway closet list should be third"
+ );
+ is(
+ colorwaySection.children[3].tagName.toLowerCase(),
+ "addon-card",
+ "addon theme card should be fourth"
+ );
+
+ info("Verifying cards in list of retained colorway themes");
+ let expiredAddonCard = colorwaySection.querySelector(
+ `addon-card[addon-id='${kTestExpiredThemeId}']`
+ );
+ ok(
+ colorwaySection.contains(expiredAddonCard),
+ "Colorways section contains the expired theme."
+ );
+ let disabledSection = getSection(doc, "theme-disabled-section");
+ expiredAddonCard = disabledSection.querySelector(
+ `addon-card[addon-id='${kTestExpiredThemeId}']`
+ );
+ ok(
+ !disabledSection.contains(expiredAddonCard),
+ "The regular, non-Colorways 'Disabled' section does not contain the expired theme."
+ );
+
+ let validAddonCard = colorwaySection.querySelector(
+ `addon-card[addon-id='${validThemeId}']`
+ );
+ ok(
+ !colorwaySection.contains(validAddonCard),
+ "Colorways section does not contain valid theme."
+ );
+
+ await closeView(win);
+ await expiredAddon.uninstall(true);
+ await validAddon.uninstall(true);
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
+});
+
+/**
+ * Tests that the Colorway Closet does not appear when there is no active
+ * collection, and that retained themes are still visible.
+ */
+add_task(async function testColorwayNoActiveCollection() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.theme.colorway-closet", true]],
+ });
+
+ // Mock BuiltInThemeConfig.findActiveColorwaysCollection with test colorways.
+ const originalFindActiveCollection =
+ BuiltInThemeConfig.findActiveColorwayCollection;
+ registerCleanupFunction(() => {
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
+ });
+
+ setBuiltInThemeConfigMock();
+
+ // Set expired theme as a retained colorway theme
+ const retainedThemePrefName = "browser.theme.retainedExpiredThemes";
+ await SpecialPowers.pushPrefEnv({
+ set: [[retainedThemePrefName, JSON.stringify([kTestExpiredThemeId])]],
+ });
+ const themeXpiExpiredAddon = getMockThemeXpi(kTestExpiredThemeId);
+ const expiredAddon = (
+ await AddonTestUtils.promiseInstallFile(themeXpiExpiredAddon)
+ ).addon;
+
+ await expiredAddon.disable();
+
+ // Make the test theme appear expired.
+ setMockThemeToExpired(kTestExpiredThemeId);
+ registerCleanupFunction(() => {
+ BuiltInThemes.builtInThemeMap.delete(kTestExpiredThemeId);
+ });
+
+ let win = await loadInitialView("theme");
+ let doc = win.document;
+ let colorwaySection = getSection(doc, "colorways-section");
+ ok(colorwaySection, "colorway section was found");
+
+ ok(
+ !colorwaySection.querySelector("colorways-card"),
+ "colorway closet card was not found"
+ );
+
+ info("Verifying that header and subheader are still visible");
+ is(
+ colorwaySection.children[0].classList[0],
+ "list-section-heading",
+ "colorway section header should be first"
+ );
+ is(
+ colorwaySection.children[1].classList[0],
+ "list-section-subheading",
+ "colorway section subheader should be second"
+ );
+
+ let expiredAddonCard = colorwaySection.querySelector(
+ `addon-card[addon-id='${kTestExpiredThemeId}']`
+ );
+ ok(
+ colorwaySection.contains(expiredAddonCard),
+ "Colorways section contains the expired theme."
+ );
+
+ await closeView(win);
+ await expiredAddon.uninstall(true);
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ clearBuiltInThemeConfigMock(originalFindActiveCollection);
});