Files
firefox/devtools/client/framework/test/browser_toolbox_options.js
T
Alexandre Poirot c42905da36 Bug 1838443 - [devtools] Move preview data from redux store to react state. r=devtools-reviewers,perftest-reviewers,sparky,bomsy
This data is transient and only specific to the Editor's index react component.
There is no need to share nor persist this data in Redux,
this would avoid unecessary complexity/layers and avoid a few state updates.

Note that the target.match(:hover) was probably a poorly documented way to prevent
rending preview for token that are no longer hovered (the comment about whitespace sounds weird).

Also tweak waitForSelectedSource to also wait for CodeMirror to be updated.
Otherwise, we might try to hover tokens while CodeMirror is still udpating...
While doing that, use the L10N string instead of the final localized string.

Differential Revision: https://phabricator.services.mozilla.com/D180992
2023-06-19 17:18:27 +00:00

557 lines
15 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that changing preferences in the options panel updates the prefs
// and toggles appropriate things in the toolbox.
var doc = null,
toolbox = null,
panelWin = null,
modifiedPrefs = [];
const L10N = new LocalizationHelper(
"devtools/client/locales/toolbox.properties"
);
const { PrefObserver } = require("resource://devtools/client/shared/prefs.js");
add_task(async function () {
const URL =
"data:text/html;charset=utf8,test for dynamically registering " +
"and unregistering tools";
registerNewTool();
const tab = await addTab(URL);
toolbox = await gDevTools.showToolboxForTab(tab);
doc = toolbox.doc;
await registerNewPerToolboxTool();
await testSelectTool();
await testOptionsShortcut();
await testOptions();
await testToggleTools();
// Test that registered WebExtensions becomes entries in the
// options panel and toggling their checkbox toggle the related
// preference.
await registerNewWebExtensions();
await testToggleWebExtensions();
await cleanup();
});
function registerNewTool() {
const toolDefinition = {
id: "testTool",
isToolSupported: () => true,
visibilityswitch: "devtools.test-tool.enabled",
url: "about:blank",
label: "someLabel",
};
ok(gDevTools, "gDevTools exists");
ok(
!gDevTools.getToolDefinitionMap().has("testTool"),
"The tool is not registered"
);
gDevTools.registerTool(toolDefinition);
ok(
gDevTools.getToolDefinitionMap().has("testTool"),
"The tool is registered"
);
}
// Register a fake WebExtension to check that it is
// listed in the toolbox options.
function registerNewWebExtensions() {
// Register some fake extensions and init the related preferences
// (similarly to ext-devtools.js).
for (let i = 0; i < 2; i++) {
const extPref = `devtools.webextensions.fakeExtId${i}.enabled`;
Services.prefs.setBoolPref(extPref, true);
toolbox.registerWebExtension(`fakeUUID${i}`, {
name: `Fake WebExtension ${i}`,
pref: extPref,
});
}
}
function registerNewPerToolboxTool() {
const toolDefinition = {
id: "test-pertoolbox-tool",
isToolSupported: () => true,
visibilityswitch: "devtools.test-pertoolbox-tool.enabled",
url: "about:blank",
label: "perToolboxSomeLabel",
};
ok(gDevTools, "gDevTools exists");
ok(
!gDevTools.getToolDefinitionMap().has("test-pertoolbox-tool"),
"The per-toolbox tool is not registered globally"
);
ok(toolbox, "toolbox exists");
ok(
!toolbox.hasAdditionalTool("test-pertoolbox-tool"),
"The per-toolbox tool is not yet registered to the toolbox"
);
toolbox.addAdditionalTool(toolDefinition);
ok(
!gDevTools.getToolDefinitionMap().has("test-pertoolbox-tool"),
"The per-toolbox tool is not registered globally"
);
ok(
toolbox.hasAdditionalTool("test-pertoolbox-tool"),
"The per-toolbox tool has been registered to the toolbox"
);
}
async function testSelectTool() {
info("Checking to make sure that the options panel can be selected.");
const onceSelected = toolbox.once("options-selected");
toolbox.selectTool("options");
await onceSelected;
ok(true, "Toolbox selected via selectTool method");
}
async function testOptionsShortcut() {
info("Selecting another tool, then reselecting options panel with keyboard.");
await toolbox.selectTool("webconsole");
is(toolbox.currentToolId, "webconsole", "webconsole is selected");
synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key");
synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
is(toolbox.currentToolId, "webconsole", "webconsole is reselected");
synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key");
}
async function testOptions() {
const tool = toolbox.getPanel("options");
panelWin = tool.panelWin;
const prefNodes = tool.panelDoc.querySelectorAll(
"input[type=checkbox][data-pref]"
);
// Store modified pref names so that they can be cleared on error.
for (const node of tool.panelDoc.querySelectorAll("[data-pref]")) {
const pref = node.getAttribute("data-pref");
modifiedPrefs.push(pref);
}
for (const node of prefNodes) {
const prefValue = GetPref(node.getAttribute("data-pref"));
// Test clicking the checkbox for each options pref
await testMouseClick(node, prefValue);
// Do again with opposite values to reset prefs
await testMouseClick(node, !prefValue);
}
const prefSelects = tool.panelDoc.querySelectorAll("select[data-pref]");
for (const node of prefSelects) {
await testSelect(node);
}
}
async function testSelect(select) {
const pref = select.getAttribute("data-pref");
const options = Array.from(select.options);
info("Checking select for: " + pref);
is(
`${select.options[select.selectedIndex].value}`,
`${GetPref(pref)}`,
"select starts out selected"
);
for (const option of options) {
if (options.indexOf(option) === select.selectedIndex) {
continue;
}
const observer = new PrefObserver("devtools.");
let changeSeen = false;
const changeSeenPromise = new Promise(resolve => {
observer.once(pref, () => {
changeSeen = true;
is(
`${GetPref(pref)}`,
`${option.value}`,
"Preference been switched for " + pref
);
resolve();
});
});
select.selectedIndex = options.indexOf(option);
const changeEvent = new Event("change");
select.dispatchEvent(changeEvent);
await changeSeenPromise;
ok(changeSeen, "Correct pref was changed");
observer.destroy();
}
}
async function testMouseClick(node, prefValue) {
const observer = new PrefObserver("devtools.");
const pref = node.getAttribute("data-pref");
let changeSeen = false;
const changeSeenPromise = new Promise(resolve => {
observer.once(pref, () => {
changeSeen = true;
is(GetPref(pref), !prefValue, "New value is correct for " + pref);
resolve();
});
});
node.scrollIntoView();
// We use executeSoon here to ensure that the element is in view and
// clickable.
executeSoon(function () {
info("Click event synthesized for pref " + pref);
EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
});
await changeSeenPromise;
ok(changeSeen, "Correct pref was changed");
observer.destroy();
}
async function testToggleWebExtensions() {
const disabledExtensions = new Set();
const toggleableWebExtensions = toolbox.listWebExtensions();
function toggleWebExtension(node) {
node.scrollIntoView();
EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
}
function assertExpectedDisabledExtensions() {
for (const ext of toggleableWebExtensions) {
if (disabledExtensions.has(ext)) {
ok(
!toolbox.isWebExtensionEnabled(ext.uuid),
`The WebExtension "${ext.name}" should be disabled`
);
} else {
ok(
toolbox.isWebExtensionEnabled(ext.uuid),
`The WebExtension "${ext.name}" should be enabled`
);
}
}
}
function assertAllExtensionsDisabled() {
const enabledUUIDs = toggleableWebExtensions
.filter(ext => toolbox.isWebExtensionEnabled(ext.uuid))
.map(ext => ext.uuid);
Assert.deepEqual(
enabledUUIDs,
[],
"All the registered WebExtensions should be disabled"
);
}
function assertAllExtensionsEnabled() {
const disabledUUIDs = toolbox
.listWebExtensions()
.filter(ext => !toolbox.isWebExtensionEnabled(ext.uuid))
.map(ext => ext.uuid);
Assert.deepEqual(
disabledUUIDs,
[],
"All the registered WebExtensions should be enabled"
);
}
function getWebExtensionNodes() {
const toolNodes = panelWin.document.querySelectorAll(
"#default-tools-box input[type=checkbox]:not([data-unsupported])," +
"#additional-tools-box input[type=checkbox]:not([data-unsupported])"
);
return [...toolNodes].filter(node => {
return toggleableWebExtensions.some(
({ uuid }) => node.getAttribute("id") === `webext-${uuid}`
);
});
}
let webExtensionNodes = getWebExtensionNodes();
is(
webExtensionNodes.length,
toggleableWebExtensions.length,
"There should be a toggle checkbox for every WebExtension registered"
);
for (const ext of toggleableWebExtensions) {
ok(
toolbox.isWebExtensionEnabled(ext.uuid),
`The WebExtension "${ext.name}" is initially enabled`
);
}
// Store modified pref names so that they can be cleared on error.
for (const ext of toggleableWebExtensions) {
modifiedPrefs.push(ext.pref);
}
// Turn each registered WebExtension to disabled.
for (const node of webExtensionNodes) {
toggleWebExtension(node);
const toggledExt = toggleableWebExtensions.find(ext => {
return node.id == `webext-${ext.uuid}`;
});
ok(toggledExt, "Found a WebExtension for the checkbox element");
disabledExtensions.add(toggledExt);
assertExpectedDisabledExtensions();
}
assertAllExtensionsDisabled();
// Turn each registered WebExtension to enabled.
for (const node of webExtensionNodes) {
toggleWebExtension(node);
const toggledExt = toggleableWebExtensions.find(ext => {
return node.id == `webext-${ext.uuid}`;
});
ok(toggledExt, "Found a WebExtension for the checkbox element");
disabledExtensions.delete(toggledExt);
assertExpectedDisabledExtensions();
}
assertAllExtensionsEnabled();
// Unregister the WebExtensions one by one, and check that only the expected
// ones have been unregistered, and the remaining onea are still listed.
for (const ext of toggleableWebExtensions) {
ok(
!!toolbox.listWebExtensions().length,
"There should still be extensions registered"
);
toolbox.unregisterWebExtension(ext.uuid);
const registeredUUIDs = toolbox.listWebExtensions().map(item => item.uuid);
ok(
!registeredUUIDs.includes(ext.uuid),
`the WebExtension "${ext.name}" should have been unregistered`
);
webExtensionNodes = getWebExtensionNodes();
const checkboxEl = webExtensionNodes.find(
el => el.id === `webext-${ext.uuid}`
);
is(
checkboxEl,
undefined,
"The unregistered WebExtension checkbox should have been removed"
);
is(
registeredUUIDs.length,
webExtensionNodes.length,
"There should be the expected number of WebExtensions checkboxes"
);
}
is(
toolbox.listWebExtensions().length,
0,
"All WebExtensions have been unregistered"
);
webExtensionNodes = getWebExtensionNodes();
is(
webExtensionNodes.length,
0,
"There should not be any checkbox for the unregistered WebExtensions"
);
}
function getToolNode(id) {
return panelWin.document.getElementById(id);
}
async function testToggleTools() {
const toolNodes = panelWin.document.querySelectorAll(
"#default-tools-box input[type=checkbox]:not([data-unsupported])," +
"#additional-tools-box input[type=checkbox]:not([data-unsupported])"
);
const toolNodeIds = [...toolNodes].map(node => node.id);
const enabledToolIds = [...toolNodes]
.filter(node => node.checked)
.map(node => node.id);
const toggleableTools = gDevTools
.getDefaultTools()
.filter(tool => {
return tool.visibilityswitch;
})
.concat(gDevTools.getAdditionalTools())
.concat(toolbox.getAdditionalTools());
for (const node of toolNodes) {
const id = node.getAttribute("id");
ok(
toggleableTools.some(tool => tool.id === id),
"There should be a toggle checkbox for: " + id
);
}
// Store modified pref names so that they can be cleared on error.
for (const tool of toggleableTools) {
const pref = tool.visibilityswitch;
modifiedPrefs.push(pref);
}
// Toggle each tool
for (const id of toolNodeIds) {
await toggleTool(getToolNode(id));
}
// Toggle again to reset tool enablement state
for (const id of toolNodeIds) {
await toggleTool(getToolNode(id));
}
// Test that a tool can still be added when no tabs are present:
// Disable all tools
for (const id of enabledToolIds) {
await toggleTool(getToolNode(id));
}
// Re-enable the tools which are enabled by default
for (const id of enabledToolIds) {
await toggleTool(getToolNode(id));
}
// Toggle first, middle, and last tools to ensure that toolbox tabs are
// inserted in order
const firstToolId = toolNodeIds[0];
const middleToolId = toolNodeIds[(toolNodeIds.length / 2) | 0];
const lastToolId = toolNodeIds[toolNodeIds.length - 1];
await toggleTool(getToolNode(firstToolId));
await toggleTool(getToolNode(firstToolId));
await toggleTool(getToolNode(middleToolId));
await toggleTool(getToolNode(middleToolId));
await toggleTool(getToolNode(lastToolId));
await toggleTool(getToolNode(lastToolId));
}
/**
* Toggle tool node checkbox. Note: because toggling the checkbox will result in
* re-rendering of the tool list, we must re-query the checkboxes every time.
*/
async function toggleTool(node) {
const toolId = node.getAttribute("id");
const registeredPromise = new Promise(resolve => {
if (node.checked) {
gDevTools.once(
"tool-unregistered",
checkUnregistered.bind(null, toolId, resolve)
);
} else {
gDevTools.once(
"tool-registered",
checkRegistered.bind(null, toolId, resolve)
);
}
});
node.scrollIntoView();
EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
await registeredPromise;
}
function checkUnregistered(toolId, resolve, data) {
if (data == toolId) {
ok(true, "Correct tool removed");
// checking tab on the toolbox
ok(
!doc.getElementById("toolbox-tab-" + toolId),
"Tab removed for " + toolId
);
} else {
ok(false, "Something went wrong, " + toolId + " was not unregistered");
}
resolve();
}
async function checkRegistered(toolId, resolve, data) {
if (data == toolId) {
ok(true, "Correct tool added back");
// checking tab on the toolbox
const button = await lookupButtonForToolId(toolId);
ok(button, "Tab added back for " + toolId);
} else {
ok(false, "Something went wrong, " + toolId + " was not registered");
}
resolve();
}
function GetPref(name) {
const type = Services.prefs.getPrefType(name);
switch (type) {
case Services.prefs.PREF_STRING:
return Services.prefs.getCharPref(name);
case Services.prefs.PREF_INT:
return Services.prefs.getIntPref(name);
case Services.prefs.PREF_BOOL:
return Services.prefs.getBoolPref(name);
default:
throw new Error("Unknown type");
}
}
/**
* Find the button from specified toolId.
* Generally, button which access to the tool panel is in toolbox or
* tools menu(in the Chevron menu).
*/
async function lookupButtonForToolId(toolId) {
let button = doc.getElementById("toolbox-tab-" + toolId);
if (!button) {
// search from the tools menu.
await openChevronMenu(toolbox);
button = doc.querySelector("#tools-chevron-menupopup-" + toolId);
await closeChevronMenu(toolbox);
}
return button;
}
async function cleanup() {
gDevTools.unregisterTool("testTool");
await toolbox.destroy();
gBrowser.removeCurrentTab();
for (const pref of modifiedPrefs) {
Services.prefs.clearUserPref(pref);
}
toolbox = doc = panelWin = modifiedPrefs = null;
}