summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/rules/test/browser_rules_class_panel_autocomplete.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_class_panel_autocomplete.js266
1 files changed, 266 insertions, 0 deletions
diff --git a/devtools/client/inspector/rules/test/browser_rules_class_panel_autocomplete.js b/devtools/client/inspector/rules/test/browser_rules_class_panel_autocomplete.js
new file mode 100644
index 0000000000..9cd7993244
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_class_panel_autocomplete.js
@@ -0,0 +1,266 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the autocomplete for the class panel input behaves as expected. The test also
+// checks that we're using the cache to retrieve the data when we can do so, and that the
+// cache gets cleared, and we're getting data from the server, when there's mutation on
+// the page.
+
+const TEST_URI = `${URL_ROOT}doc_class_panel_autocomplete.html`;
+
+add_task(async function () {
+ await addTab(TEST_URI);
+ const { inspector, view } = await openRuleView();
+ const { addEl: textInput } = view.classListPreviewer;
+ await selectNode("#auto-div-id-3", inspector);
+
+ info("Open the class panel");
+ view.showClassPanel();
+
+ textInput.focus();
+
+ info("Type a letter and check that the popup has the expected items");
+ const allClasses = [
+ "auto-body-class-1",
+ "auto-body-class-2",
+ "auto-bold",
+ "auto-cssom-primary-color",
+ "auto-div-class-1",
+ "auto-div-class-2",
+ "auto-html-class-1",
+ "auto-html-class-2",
+ "auto-inline-class-1",
+ "auto-inline-class-2",
+ "auto-inline-class-3",
+ "auto-inline-class-4",
+ "auto-inline-class-5",
+ "auto-stylesheet-class-1",
+ "auto-stylesheet-class-2",
+ "auto-stylesheet-class-3",
+ "auto-stylesheet-class-4",
+ "auto-stylesheet-class-5",
+ ];
+
+ const { autocompletePopup } = view.classListPreviewer;
+ let onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.synthesizeKey("a", {}, view.styleWindow);
+ await waitForClassApplied("auto-body-class-1", "#auto-div-id-3");
+ await onPopupOpened;
+ await checkAutocompleteItems(
+ autocompletePopup,
+ allClasses,
+ "The autocomplete popup has all the classes used in the DOM and in stylesheets"
+ );
+
+ info(
+ "Test that typing more letters filters the autocomplete popup and uses the cache mechanism"
+ );
+ EventUtils.sendString("uto-b", view.styleWindow);
+ await waitForClassApplied("auto-body-class-1", "#auto-div-id-3");
+
+ await checkAutocompleteItems(
+ autocompletePopup,
+ allClasses.filter(cls => cls.startsWith("auto-b")),
+ "The autocomplete popup was filtered with the content of the input"
+ );
+ ok(true, "The results were retrieved from the cache mechanism");
+
+ info("Test that autocomplete shows up-to-date results");
+ // Modify the content page and assert that the new class is displayed in the
+ // autocomplete if the user types a new letter.
+ const onNewMutation = inspector.inspectorFront.walker.once("new-mutations");
+ await ContentTask.spawn(gBrowser.selectedBrowser, null, async function () {
+ content.document.body.classList.add("auto-body-added-by-script");
+ });
+ await onNewMutation;
+ await waitForClassApplied("auto-body-added-by-script", "body");
+
+ // close & reopen the autocomplete so it picks up the added to another element while autocomplete was opened
+ let onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("KEY_Escape", {}, view.styleWindow);
+ await onPopupClosed;
+
+ // input is now auto-body
+ onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.sendString("ody", view.styleWindow);
+ await onPopupOpened;
+ await checkAutocompleteItems(
+ autocompletePopup,
+ [
+ ...allClasses.filter(cls => cls.startsWith("auto-body")),
+ "auto-body-added-by-script",
+ ].sort(),
+ "The autocomplete popup was filtered with the content of the input"
+ );
+
+ info(
+ "Test that typing a letter that won't match any of the item closes the popup"
+ );
+ // input is now auto-bodyy
+ onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("y", {}, view.styleWindow);
+ await waitForClassApplied("auto-bodyy", "#auto-div-id-3");
+ await onPopupClosed;
+ ok(true, "The popup was closed as expected");
+ await checkAutocompleteItems(autocompletePopup, [], "The popup was cleared");
+
+ info("Clear the input and try to autocomplete again");
+ textInput.select();
+ EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
+ // Wait a bit so the debounced function can be executed
+ await wait(200);
+
+ onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.synthesizeKey("a", {}, view.styleWindow);
+ await onPopupOpened;
+
+ await checkAutocompleteItems(
+ autocompletePopup,
+ [...allClasses, "auto-body-added-by-script"].sort(),
+ "The autocomplete popup was updated with the new class added to the DOM"
+ );
+
+ info("Test keyboard shortcut when the popup is displayed");
+ // Escape to hide
+ onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("KEY_Escape", {}, view.styleWindow);
+ await onPopupClosed;
+ ok(true, "The popup was closed when hitting escape");
+
+ // Ctrl + space to show again
+ onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.synthesizeKey(" ", { ctrlKey: true }, view.styleWindow);
+ await onPopupOpened;
+ ok(true, "Popup was opened again with Ctrl+Space");
+ await checkAutocompleteItems(
+ autocompletePopup,
+ [...allClasses, "auto-body-added-by-script"].sort()
+ );
+
+ // Arrow left to hide
+ onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, view.styleWindow);
+ await onPopupClosed;
+ ok(true, "The popup was closed as when hitting ArrowLeft");
+
+ // Arrow right and Ctrl + space to show again, and Arrow Right to accept
+ onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
+ EventUtils.synthesizeKey(" ", { ctrlKey: true }, view.styleWindow);
+ await onPopupOpened;
+
+ onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
+ await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3");
+ await onPopupClosed;
+ is(
+ textInput.value,
+ "auto-body-added-by-script",
+ "ArrowRight puts the selected item in the input and closes the popup"
+ );
+
+ // Backspace to show the list again
+ onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
+ await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3");
+ await onPopupOpened;
+ is(
+ textInput.value,
+ "auto-body-added-by-scrip",
+ "ArrowRight puts the selected item in the input and closes the popup"
+ );
+ await checkAutocompleteItems(
+ autocompletePopup,
+ ["auto-body-added-by-script"],
+ "The autocomplete does show the matching items after hitting backspace"
+ );
+
+ // Enter to accept
+ onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("KEY_Enter", {}, view.styleWindow);
+ await waitForClassRemoved("auto-body-added-by-scrip");
+ await onPopupClosed;
+ is(
+ textInput.value,
+ "auto-body-added-by-script",
+ "Enter puts the selected item in the input and closes the popup"
+ );
+
+ // Backspace to show again
+ onPopupOpened = autocompletePopup.once("popup-opened");
+ EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
+ await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3");
+ await onPopupOpened;
+ is(
+ textInput.value,
+ "auto-body-added-by-scrip",
+ "ArrowRight puts the selected item in the input and closes the popup"
+ );
+ await checkAutocompleteItems(
+ autocompletePopup,
+ ["auto-body-added-by-script"],
+ "The autocomplete does show the matching items after hitting backspace"
+ );
+
+ // Tab to accept
+ onPopupClosed = autocompletePopup.once("popup-closed");
+ EventUtils.synthesizeKey("KEY_Tab", {}, view.styleWindow);
+ await onPopupClosed;
+ is(
+ textInput.value,
+ "auto-body-added-by-script",
+ "Tab puts the selected item in the input and closes the popup"
+ );
+ await waitForClassRemoved("auto-body-added-by-scrip");
+});
+
+async function checkAutocompleteItems(
+ autocompletePopup,
+ expectedItems,
+ assertionMessage
+) {
+ await waitForSuccess(
+ () =>
+ getAutocompleteItems(autocompletePopup).length === expectedItems.length
+ );
+ const items = getAutocompleteItems(autocompletePopup);
+ const formatList = list => `\n${list.join("\n")}\n`;
+ is(formatList(items), formatList(expectedItems), assertionMessage);
+}
+
+function getAutocompleteItems(autocompletePopup) {
+ return Array.from(autocompletePopup._panel.querySelectorAll("li")).map(
+ el => el.textContent
+ );
+}
+
+async function waitForClassApplied(cls, selector) {
+ info("Wait for class to be applied: " + cls);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [cls, selector],
+ async (_cls, _selector) => {
+ return ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(_selector).classList.contains(_cls)
+ );
+ }
+ );
+ // Wait for debounced functions to be executed
+ await wait(200);
+}
+
+async function waitForClassRemoved(cls) {
+ info("Wait for class to be removed: " + cls);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => {
+ return ContentTaskUtils.waitForCondition(
+ () =>
+ !content.document
+ .querySelector("#auto-div-id-3")
+ .classList.contains(_cls)
+ );
+ });
+ // Wait for debounced functions to be executed
+ await wait(200);
+}