633 lines
20 KiB
JavaScript
633 lines
20 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
ChromeUtils.defineESModuleGetters(this, {
|
|
featureEngineIdToFluentId: "chrome://global/content/ml/Utils.sys.mjs",
|
|
});
|
|
|
|
const RED_ICON_DATA =
|
|
"iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QYGEgw1XkM0ygAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAYSURBVCjPY/zPQA5gYhjVNqptVNsg1wYAItkBI/GNR3YAAAAASUVORK5CYII=";
|
|
const IMAGE_ARRAYBUFFER_RED = imageBufferFromDataURI(RED_ICON_DATA);
|
|
const FEATURE_ICON = "chrome://branding/content/icon64.png";
|
|
|
|
function imageBufferFromDataURI(encodedImageData) {
|
|
let decodedImageData = atob(encodedImageData);
|
|
return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
|
|
}
|
|
|
|
async function getTestExtension({ id, withIcon }) {
|
|
const testExt = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
manifest: {
|
|
name: `name-${id}`,
|
|
icons: withIcon ? { 16: "test-icon.png" } : undefined,
|
|
browser_specific_settings: {
|
|
gecko: { id },
|
|
},
|
|
},
|
|
files: withIcon ? { "test-icon.png": IMAGE_ARRAYBUFFER_RED } : undefined,
|
|
});
|
|
await testExt.startup();
|
|
return testExt;
|
|
}
|
|
|
|
let mockProvider;
|
|
let promptService;
|
|
add_setup(async function () {
|
|
mockProvider = new MockProvider(["mlmodel"]);
|
|
promptService = mockPromptService();
|
|
});
|
|
|
|
/**
|
|
* Test that model hub provider is conditionally shown in the sidebar.
|
|
*/
|
|
add_task(async function testModelHubProvider() {
|
|
let win = await loadInitialView("extension");
|
|
let modelHubCategory = AboutAddonsTestUtils.getCategoryButton(win, "mlmodel");
|
|
|
|
await TestUtils.waitForCondition(async () => {
|
|
return modelHubCategory.hidden;
|
|
}, "Wait for the mlmodel category button to be hidden");
|
|
|
|
ok(modelHubCategory.hidden, "Model hub category is hidden");
|
|
|
|
await closeView(win);
|
|
mockProvider.createAddons([
|
|
{
|
|
id: "mockmodel1@tests.mozilla.org",
|
|
name: "Model Mock 1",
|
|
permissions: AddonManager.PERM_CAN_UNINSTALL,
|
|
type: "mlmodel",
|
|
usedByFirefoxFeatures: [],
|
|
usedByAddonIds: [],
|
|
},
|
|
{
|
|
id: "mockmodel2@tests.mozilla.org",
|
|
name: "Model Mock 2",
|
|
permissions: AddonManager.PERM_CAN_UNINSTALL,
|
|
type: "mlmodel",
|
|
usedByFirefoxFeatures: [],
|
|
usedByAddonIds: [],
|
|
},
|
|
]);
|
|
win = await loadInitialView("extension");
|
|
let doc = win.document;
|
|
modelHubCategory = AboutAddonsTestUtils.getCategoryButton(win, "mlmodel");
|
|
await TestUtils.waitForCondition(async () => {
|
|
return !modelHubCategory.hidden;
|
|
}, "Wait for the mlmodel category button to not be hidden");
|
|
|
|
ok(!modelHubCategory.hidden, "Model hub category is shown");
|
|
|
|
let mlmodelLoaded = waitForViewLoad(win);
|
|
AboutAddonsTestUtils.clickCategoryButton(win, "mlmodel");
|
|
await mlmodelLoaded;
|
|
|
|
let enabledSection = getSection(doc, "mlmodel-enabled-section");
|
|
is(
|
|
enabledSection.children.length,
|
|
2,
|
|
"Got the expected number of mlmodel entries"
|
|
);
|
|
ok(
|
|
!enabledSection.querySelector(".list-section-heading"),
|
|
"No heading for mlmodel"
|
|
);
|
|
is(
|
|
enabledSection.previousSibling.localName,
|
|
"mlmodel-list-intro",
|
|
"Model hub custom section is shown"
|
|
);
|
|
|
|
let promiseListUpdated = BrowserTestUtils.waitForEvent(
|
|
enabledSection.closest("addon-list"),
|
|
"remove"
|
|
);
|
|
info("Uninstall one of the mlmodel entries");
|
|
const mlmodelmock2 = await AddonManager.getAddonByID(
|
|
"mockmodel2@tests.mozilla.org"
|
|
);
|
|
|
|
await mlmodelmock2.uninstall();
|
|
info("Wait for the list of mlmodel entries to be updated");
|
|
await promiseListUpdated;
|
|
|
|
enabledSection = getSection(doc, "mlmodel-enabled-section");
|
|
is(
|
|
enabledSection.children.length,
|
|
1,
|
|
"Got the expected number of mlmodel entries"
|
|
);
|
|
|
|
promiseListUpdated = BrowserTestUtils.waitForEvent(
|
|
enabledSection.closest("addon-list"),
|
|
"remove"
|
|
);
|
|
info("Uninstall the last one of the mlmodel entries");
|
|
const mlmodelmock1 = await AddonManager.getAddonByID(
|
|
"mockmodel1@tests.mozilla.org"
|
|
);
|
|
|
|
await mlmodelmock1.uninstall();
|
|
info("Wait for the list of mlmodel entries to be updated");
|
|
await promiseListUpdated;
|
|
|
|
enabledSection = getSection(doc, "mlmodel-enabled-section");
|
|
is(
|
|
enabledSection.children.length,
|
|
0,
|
|
"Expect mlmodel add-ons list view to be empty"
|
|
);
|
|
|
|
let emptyMessageFindModeOnAMO = doc.querySelector("#empty-addons-message");
|
|
is(
|
|
emptyMessageFindModeOnAMO,
|
|
null,
|
|
"Expect no #empty-addons-message element in the empty mlmodel list view"
|
|
);
|
|
|
|
await closeView(win);
|
|
});
|
|
|
|
/**
|
|
* Test model hub card in the list view.
|
|
*/
|
|
add_task(async function testModelHubCard() {
|
|
const expectedTelemetryCount = 1;
|
|
Services.fog.testResetFOG();
|
|
const extWithIcon = await getTestExtension({
|
|
id: "addon-with-icon@test-extension",
|
|
withIcon: true,
|
|
});
|
|
const extWithoutIcon = await getTestExtension({
|
|
id: "addon-without-icon@test-extension",
|
|
withIcon: false,
|
|
});
|
|
const id1 = "mockmodel1-without-size@tests.mozilla.org";
|
|
const id2 = "mockmodel2-with-size@tests.mozilla.org";
|
|
|
|
mockProvider.createAddons([
|
|
{
|
|
id: id1,
|
|
name: "Model Mock 1",
|
|
permissions: AddonManager.PERM_CAN_UNINSTALL,
|
|
type: "mlmodel",
|
|
totalSize: undefined,
|
|
// Testing a model using one of the expected Firefox features.
|
|
usedByFirefoxFeatures: ["about-inference"],
|
|
// Testing extension using the models (one with its own icon and
|
|
// one without any icon).
|
|
usedByAddonIds: [extWithIcon.id, extWithoutIcon.id],
|
|
},
|
|
{
|
|
id: id2,
|
|
name: "Model Mock 2",
|
|
permissions: AddonManager.PERM_CAN_UNINSTALL,
|
|
type: "mlmodel",
|
|
totalSize: 5 * 1024 * 1024,
|
|
// Testing that a Firefox feature that is mistakenly missing a
|
|
// corresponding fluent id is omitted.
|
|
usedByFirefoxFeatures: [
|
|
"non-existing-feature",
|
|
"smart-tab-embedding-engine",
|
|
],
|
|
// Testing that a non existing extension is omitted.
|
|
usedByAddonIds: ["non-existing@test-extension", extWithIcon.id],
|
|
},
|
|
]);
|
|
|
|
let win = await loadInitialView("mlmodel");
|
|
|
|
info("Test list view telemetry");
|
|
let listViewEvent = Glean.modelManagement.listView.testGetValue() || [];
|
|
Assert.equal(
|
|
listViewEvent.length,
|
|
expectedTelemetryCount,
|
|
"Got the expected listView telemetry"
|
|
);
|
|
|
|
// Card No Size
|
|
let card1 = getAddonCard(win, id1);
|
|
ok(card1, `Found addon card for model ${id1}`);
|
|
verifyAddonCard(card1, {
|
|
expectedTotalSize: "0 bytes",
|
|
expectedUsedBy: [
|
|
{
|
|
iconURL: FEATURE_ICON,
|
|
fluentId: featureEngineIdToFluentId("about-inference"),
|
|
},
|
|
{
|
|
iconURL: /\/test-icon\.png$/,
|
|
fluentId: "mlmodel-extension-label",
|
|
fluentArgs: { extensionName: `name-${extWithIcon.id}` },
|
|
},
|
|
{
|
|
iconURL: /\/extensionGeneric.svg$/,
|
|
fluentId: "mlmodel-extension-label",
|
|
fluentArgs: { extensionName: `name-${extWithoutIcon.id}` },
|
|
},
|
|
],
|
|
});
|
|
|
|
// Card With Size
|
|
let card2 = getAddonCard(win, id2);
|
|
ok(card2, `Found addon card for model ${id2}`);
|
|
verifyAddonCard(card2, {
|
|
expectedTotalSize: "5.0 MB",
|
|
expectedUsedBy: [
|
|
{
|
|
iconURL: FEATURE_ICON,
|
|
fluentId: featureEngineIdToFluentId("smart-tab-embedding-engine"),
|
|
},
|
|
{
|
|
iconURL: /\/test-icon\.png$/,
|
|
fluentId: "mlmodel-extension-label",
|
|
fluentArgs: { extensionName: `name-${extWithIcon.id}` },
|
|
},
|
|
],
|
|
});
|
|
|
|
await closeView(win);
|
|
await extWithIcon.unload();
|
|
await extWithoutIcon.unload();
|
|
|
|
function verifyAddonCard(card, { expectedTotalSize, expectedUsedBy }) {
|
|
ok(!card.hasAttribute("expanded"), "The list card is not expanded");
|
|
|
|
let mlmodelTotalSizeBubble = card.querySelector(
|
|
".mlmodel-total-size-bubble"
|
|
);
|
|
ok(mlmodelTotalSizeBubble, "Expect to see the mlmodel total size bubble");
|
|
|
|
is(
|
|
mlmodelTotalSizeBubble?.textContent.trim(),
|
|
expectedTotalSize,
|
|
"Got the expected total size text"
|
|
);
|
|
|
|
let mlmodelRemoveAddonButton = card.querySelector(
|
|
".mlmodel-remove-addon-button"
|
|
);
|
|
|
|
ok(
|
|
!mlmodelRemoveAddonButton,
|
|
"Expect to not see the mlmodel remove addon button"
|
|
);
|
|
|
|
ok(
|
|
BrowserTestUtils.isVisible(card.optionsButton),
|
|
"Expect the card options button to be visible in the list view"
|
|
);
|
|
|
|
const listAdditionEl = card.querySelector("mlmodel-card-list-additions");
|
|
ok(listAdditionEl, "Found mlmodel-card-list-additions element");
|
|
const usedByEls =
|
|
listAdditionEl.shadowRoot.querySelectorAll(".mlmodel-used-by");
|
|
for (const [idx, usedBy] of expectedUsedBy.entries()) {
|
|
info(`Verifying usedBy entry: ${JSON.stringify(usedBy)}\n`);
|
|
const img = usedByEls[idx].querySelector("img");
|
|
ok(img, "Found img tag for the icon url");
|
|
if (usedBy.iconURL instanceof RegExp) {
|
|
ok(
|
|
usedBy.iconURL.test(img.src),
|
|
`Expected icon url ${img.src} to match ${usedBy.iconURL}`
|
|
);
|
|
} else {
|
|
Assert.equal(img.src, usedBy.iconURL, "Got the expected icon url");
|
|
}
|
|
const label = usedByEls[idx].querySelector("label");
|
|
const fluentAttrs = label.ownerDocument.l10n.getAttributes(label);
|
|
Assert.equal(
|
|
fluentAttrs.id,
|
|
usedBy.fluentId,
|
|
"Got the expected fluent id"
|
|
);
|
|
if (usedBy.fluentArgs) {
|
|
Assert.deepEqual(
|
|
fluentAttrs.args,
|
|
usedBy.fluentArgs,
|
|
"Got the expected fluent args"
|
|
);
|
|
}
|
|
}
|
|
Assert.equal(
|
|
usedByEls.length,
|
|
expectedUsedBy.length,
|
|
"Got the expected number of model usedBy elements"
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Test model hub expanded details.
|
|
*/
|
|
add_task(async function testModelHubDetails() {
|
|
const extWithIcon = await getTestExtension({
|
|
id: "addon-with-icon@test-extension",
|
|
withIcon: true,
|
|
});
|
|
const extWithoutIcon = await getTestExtension({
|
|
id: "addon-without-icon@test-extension",
|
|
withIcon: false,
|
|
});
|
|
const id1 = "mockmodel1-without-size@tests.mozilla.org";
|
|
const id2 = "mockmodel2-with-size@tests.mozilla.org";
|
|
|
|
const mockModel1 = {
|
|
id: id1,
|
|
model: "hostname/org/model-mock-1",
|
|
permissions: AddonManager.PERM_CAN_UNINSTALL,
|
|
type: "mlmodel",
|
|
totalSize: undefined,
|
|
lastUsed: new Date("2023-10-01T12:00:00Z"),
|
|
updateDate: new Date("2023-10-01T12:00:00Z"),
|
|
modelHomepageURL: "https://huggingface.co/org/model-mock-1",
|
|
modelIconURL: "chrome://mozapps/skin/extensions/extensionGeneric.svg",
|
|
// Testing a model using one of the expected Firefox features.
|
|
usedByFirefoxFeatures: ["about-inference"],
|
|
// Testing extension using the models (one with its own icon and
|
|
// one without any icon).
|
|
usedByAddonIds: [extWithIcon.id, extWithoutIcon.id],
|
|
engineIds: [],
|
|
};
|
|
const mockModel2 = {
|
|
id: id2,
|
|
model: "hostname/org/model-mock-2",
|
|
permissions: AddonManager.PERM_CAN_UNINSTALL,
|
|
type: "mlmodel",
|
|
totalSize: 5 * 1024 * 1024,
|
|
lastUsed: new Date("2023-10-01T12:00:00Z"),
|
|
updateDate: new Date("2023-10-01T12:00:00Z"),
|
|
modelHomepageURL: "https://huggingface.co/org/model-mock-2",
|
|
modelIconURL: "", // testing that empty icon sets to defult svg
|
|
// Testing that a Firefox feature that is mistakenly missing a
|
|
// corresponding fluent id is omitted.
|
|
usedByFirefoxFeatures: [
|
|
"non-existing-feature",
|
|
"smart-tab-embedding-engine",
|
|
],
|
|
// Testing that a non existing extension is omitted.
|
|
usedByAddonIds: ["non-existing@test-extension", extWithIcon.id],
|
|
engineIds: [],
|
|
};
|
|
|
|
mockProvider.createAddons([mockModel1, mockModel2]);
|
|
|
|
await verifyAddonCardDetails({
|
|
id: id1,
|
|
expectedTotalSize: "0 bytes",
|
|
expectedLastUsed: mockModel1.lastUsed,
|
|
expectedModelHomepageURL: mockModel1.modelHomepageURL,
|
|
expectedModelIconURL:
|
|
"chrome://mozapps/skin/extensions/extensionGeneric.svg",
|
|
expectedUsedBy: [
|
|
{
|
|
iconURL: FEATURE_ICON,
|
|
fluentId: featureEngineIdToFluentId("about-inference"),
|
|
},
|
|
{
|
|
iconURL: /\/test-icon\.png$/,
|
|
fluentId: "mlmodel-extension-label",
|
|
fluentArgs: { extensionName: `name-${extWithIcon.id}` },
|
|
},
|
|
{
|
|
iconURL: /\/extensionGeneric.svg$/,
|
|
fluentId: "mlmodel-extension-label",
|
|
fluentArgs: { extensionName: `name-${extWithoutIcon.id}` },
|
|
},
|
|
],
|
|
expectedGleanExtraSize: undefined,
|
|
});
|
|
await verifyAddonCardDetails({
|
|
id: id2,
|
|
expectedTotalSize: "5.0 MB",
|
|
expectedLastUsed: mockModel2.lastUsed,
|
|
expectedModelHomepageURL: mockModel2.modelHomepageURL,
|
|
expectedModelIconURL:
|
|
"chrome://mozapps/skin/extensions/extensionGeneric.svg",
|
|
expectedUsedBy: [
|
|
{
|
|
iconURL: FEATURE_ICON,
|
|
fluentId: featureEngineIdToFluentId("smart-tab-embedding-engine"),
|
|
},
|
|
{
|
|
iconURL: /\/test-icon\.png$/,
|
|
fluentId: "mlmodel-extension-label",
|
|
fluentArgs: { extensionName: `name-${extWithIcon.id}` },
|
|
},
|
|
],
|
|
expectedGleanExtraSize: "5242880",
|
|
});
|
|
|
|
async function verifyAddonCardDetails({
|
|
id,
|
|
expectedTotalSize,
|
|
expectedLastUsed,
|
|
expectedModelHomepageURL,
|
|
expectedModelIconURL,
|
|
expectedUsedBy,
|
|
expectedGleanExtraSize,
|
|
}) {
|
|
Services.fog.testResetFOG();
|
|
let win = await loadInitialView("mlmodel");
|
|
|
|
// Get the list view card DOM element for the given addon id.
|
|
let card = getAddonCard(win, id);
|
|
ok(card, `Found addon card for model ${id}`);
|
|
ok(!card.hasAttribute("expanded"), "list view card is not expanded");
|
|
|
|
// Load the detail view.
|
|
let loaded = waitForViewLoad(win);
|
|
card.querySelector('[action="expand"]').click();
|
|
await loaded;
|
|
|
|
info("Test detials view telemetry");
|
|
let detailsViewEvent =
|
|
Glean.modelManagement.detailsView.testGetValue() || [];
|
|
Assert.equal(
|
|
detailsViewEvent.length,
|
|
1,
|
|
"Got the expected detailsView telemetry"
|
|
);
|
|
|
|
info("Test list management button click telemetry");
|
|
let listItemManageEvent =
|
|
Glean.modelManagement.listItemManage.testGetValue() || [];
|
|
Assert.equal(
|
|
listItemManageEvent.length,
|
|
1,
|
|
"Got the expected listItemManage telemetry"
|
|
);
|
|
|
|
// Get the detail view card DOM element for the given addon id.
|
|
card = getAddonCard(win, id);
|
|
ok(card.hasAttribute("expanded"), "detail view card is expanded");
|
|
|
|
let mlmodelTotalSizeBubble = card.querySelector(
|
|
".mlmodel-total-size-bubble"
|
|
);
|
|
ok(
|
|
!mlmodelTotalSizeBubble,
|
|
"Expect to not see the mlmodel total size bubble"
|
|
);
|
|
|
|
let mlmodelRemoveAddonButton = card.querySelector(
|
|
".mlmodel-remove-addon-button"
|
|
);
|
|
ok(
|
|
mlmodelRemoveAddonButton,
|
|
"Expect to see the mlmodel remove addon button"
|
|
);
|
|
|
|
// Set response to cancel & Fire off Delete click
|
|
promptService._response = 1;
|
|
EventUtils.sendMouseEvent({ type: "click" }, mlmodelRemoveAddonButton);
|
|
await BrowserTestUtils.waitForEvent(card, "remove-cancelled");
|
|
info("Test cancel remove prompt telemetry");
|
|
let cancelEvent =
|
|
Glean.modelManagement.removeConfirmation.testGetValue() || [];
|
|
Assert.equal(
|
|
"cancel",
|
|
cancelEvent[0].extra.action,
|
|
"Got the expected removeConfirmation telemetry"
|
|
);
|
|
|
|
// Set response to confirm & Fire off Delete click
|
|
promptService._response = 0;
|
|
EventUtils.sendMouseEvent({ type: "click" }, mlmodelRemoveAddonButton);
|
|
await BrowserTestUtils.waitForEvent(card, "remove");
|
|
info("Test confirm remove prompt telemetry");
|
|
let confirmEvent =
|
|
Glean.modelManagement.removeConfirmation.testGetValue() || [];
|
|
Assert.equal(
|
|
"remove",
|
|
confirmEvent[1].extra.action,
|
|
"Got the expected removeConfirmation telemetry"
|
|
);
|
|
|
|
info("Test how many removes iniated telemetry");
|
|
let removeInitiatedEvents =
|
|
Glean.modelManagement.removeInitiated.testGetValue() || [];
|
|
Assert.equal(
|
|
removeInitiatedEvents.length,
|
|
2,
|
|
"Got the expected removeInitiated telemetry"
|
|
);
|
|
|
|
removeInitiatedEvents.forEach(({ extra }, i) => {
|
|
Assert.equal(
|
|
extra.size,
|
|
expectedGleanExtraSize,
|
|
`Got the expected size for remove initiated event ${i}`
|
|
);
|
|
});
|
|
|
|
ok(
|
|
!card.querySelector(".addon-detail-mlmodel").hidden,
|
|
"Expect to see the mlmodel details"
|
|
);
|
|
|
|
ok(
|
|
BrowserTestUtils.isHidden(card.optionsButton),
|
|
"Expect the card options button to be hidden in the detail view"
|
|
);
|
|
|
|
// Check the model total size
|
|
let totalsizeEl = card.querySelector(".addon-detail-row-mlmodel-totalsize");
|
|
ok(totalsizeEl, "Expect to see the total size");
|
|
is(
|
|
totalsizeEl?.querySelector("span")?.textContent.trim(),
|
|
expectedTotalSize,
|
|
"Got the expected total size text"
|
|
);
|
|
|
|
// Check the last used date
|
|
let lastUsedEl = card.querySelector(".addon-detail-row-mlmodel-lastused");
|
|
ok(lastUsedEl, "Expect to see the last used date");
|
|
is(
|
|
lastUsedEl?.querySelector("span")?.textContent.trim(),
|
|
expectedLastUsed.toLocaleDateString(undefined, {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
}),
|
|
"Got the expected last used date text"
|
|
);
|
|
|
|
// Check the model card link
|
|
let modelCardEl = card.querySelector(".addon-detail-row-mlmodel-modelcard");
|
|
ok(modelCardEl, "Expect to see the model card link");
|
|
let modelHomepageURL = modelCardEl.querySelector("a");
|
|
|
|
EventUtils.sendMouseEvent({ type: "click" }, modelHomepageURL);
|
|
info("Test model card link telemetry");
|
|
let modelCardLinkEvent =
|
|
Glean.modelManagement.modelCardLink.testGetValue() || [];
|
|
Assert.equal(
|
|
modelCardLinkEvent.length,
|
|
1,
|
|
"Got the expected extensionModelLink telemetry"
|
|
);
|
|
|
|
ok(modelHomepageURL, "Expect to see the model card link element");
|
|
is(
|
|
modelHomepageURL?.href,
|
|
expectedModelHomepageURL,
|
|
"Got the expected model card link"
|
|
);
|
|
|
|
// Check the model version
|
|
let versionEl = card.querySelector(".addon-detail-row-version");
|
|
ok(versionEl, "Expect to see the model version");
|
|
|
|
// Check the model image
|
|
let iconSrc = card.querySelector(".card-heading-icon").getAttribute("src");
|
|
ok(iconSrc, "Expected to see model card image src");
|
|
is(iconSrc, expectedModelIconURL, "Got expected model card icon value");
|
|
|
|
const detailsEl = card.querySelector("addon-mlmodel-details");
|
|
ok(detailsEl, "Found mlmodel-card-list-additions element");
|
|
const usedByEls = detailsEl.querySelectorAll(".mlmodel-used-by");
|
|
info(`found usedByEls: ${usedByEls.length}`);
|
|
for (const [idx, usedBy] of expectedUsedBy.entries()) {
|
|
info(`Verifying usedBy entry: ${JSON.stringify(usedBy)}\n`);
|
|
const img = usedByEls[idx].querySelector("img");
|
|
ok(img, "Found img tag for the icon url");
|
|
if (usedBy.iconURL instanceof RegExp) {
|
|
ok(
|
|
usedBy.iconURL.test(img.src),
|
|
`Expected icon url ${img.src} to match ${usedBy.iconURL}`
|
|
);
|
|
} else {
|
|
Assert.equal(img.src, usedBy.iconURL, "Got the expected icon url");
|
|
}
|
|
const label = usedByEls[idx].querySelector("label");
|
|
const fluentAttrs = label.ownerDocument.l10n.getAttributes(label);
|
|
Assert.equal(
|
|
fluentAttrs.id,
|
|
usedBy.fluentId,
|
|
"Got the expected fluent id"
|
|
);
|
|
if (usedBy.fluentArgs) {
|
|
Assert.deepEqual(
|
|
fluentAttrs.args,
|
|
usedBy.fluentArgs,
|
|
"Got the expected fluent args"
|
|
);
|
|
}
|
|
}
|
|
Assert.equal(
|
|
usedByEls.length,
|
|
expectedUsedBy.length,
|
|
"Got the expected number of model usedBy elements"
|
|
);
|
|
|
|
await closeView(win);
|
|
}
|
|
|
|
await extWithIcon.unload();
|
|
await extWithoutIcon.unload();
|
|
});
|