diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/shared/test/browser_autocomplete_popup_input.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/shared/test/browser_autocomplete_popup_input.js')
-rw-r--r-- | devtools/client/shared/test/browser_autocomplete_popup_input.js | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/devtools/client/shared/test/browser_autocomplete_popup_input.js b/devtools/client/shared/test/browser_autocomplete_popup_input.js new file mode 100644 index 0000000000..a7d04f1c9c --- /dev/null +++ b/devtools/client/shared/test/browser_autocomplete_popup_input.js @@ -0,0 +1,251 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function () { + // Prevent the URL Bar to steal the focus. + const preventUrlBarFocus = e => { + e.preventDefault(); + }; + window.gURLBar.addEventListener("beforefocus", preventUrlBarFocus); + registerCleanupFunction(() => { + window.gURLBar.removeEventListener("beforefocus", preventUrlBarFocus); + }); + + const AutocompletePopup = require("resource://devtools/client/shared/autocomplete-popup.js"); + + info("Create an autocompletion popup and an input that will be bound to it"); + const { doc } = await createHost(); + + const input = doc.createElement("input"); + doc.body.append(input, doc.createElement("input")); + + const onSelectCalled = []; + const onClickCalled = []; + const popup = new AutocompletePopup(doc, { + input, + position: "top", + autoSelect: true, + onSelect: item => onSelectCalled.push(item), + onClick: (e, item) => onClickCalled.push(item), + }); + + input.focus(); + ok(hasFocus(input), "input has focus"); + + info( + "Check that Tab moves the focus out of the input when the popup isn't opened" + ); + EventUtils.synthesizeKey("KEY_Tab"); + is(onClickCalled.length, 0, "onClick wasn't called"); + is(hasFocus(input), false, "input does not have the focus anymore"); + info("Set the focus back to the input and open the popup"); + input.focus(); + await new Promise(res => setTimeout(res, 0)); + ok(hasFocus(input), "input is focused"); + + await populateAndOpenPopup(popup); + + const checkSelectedItem = (expected, info) => + checkPopupSelectedItem(popup, input, expected, info); + + checkSelectedItem(popupItems[0], "First item from top is selected"); + is( + onSelectCalled[0].label, + popupItems[0].label, + "onSelect was called with expected param" + ); + + info("Check that arrow down/up navigates into the list"); + EventUtils.synthesizeKey("KEY_ArrowDown"); + checkSelectedItem(popupItems[1], "item-1 is selected"); + is( + onSelectCalled[1].label, + popupItems[1].label, + "onSelect was called with expected param" + ); + + EventUtils.synthesizeKey("KEY_ArrowDown"); + checkSelectedItem(popupItems[2], "item-2 is selected"); + is( + onSelectCalled[2].label, + popupItems[2].label, + "onSelect was called with expected param" + ); + + EventUtils.synthesizeKey("KEY_ArrowDown"); + checkSelectedItem(popupItems[0], "item-0 is selected"); + is( + onSelectCalled[3].label, + popupItems[0].label, + "onSelect was called with expected param" + ); + + EventUtils.synthesizeKey("KEY_ArrowUp"); + checkSelectedItem(popupItems[2], "item-2 is selected"); + is( + onSelectCalled[4].label, + popupItems[2].label, + "onSelect was called with expected param" + ); + + EventUtils.synthesizeKey("KEY_ArrowUp"); + checkSelectedItem(popupItems[1], "item-2 is selected"); + is( + onSelectCalled[5].label, + popupItems[1].label, + "onSelect was called with expected param" + ); + + info("Check that Escape closes the popup"); + let onPopupClosed = popup.once("popup-closed"); + EventUtils.synthesizeKey("KEY_Escape"); + await onPopupClosed; + ok(true, "popup was closed with Escape key"); + ok(hasFocus(input), "input still has the focus"); + is(onClickCalled.length, 0, "onClick wasn't called"); + + info("Fill the input"); + const value = "item"; + EventUtils.sendString(value); + is(input.value, value, "input has the expected value"); + is( + input.selectionStart, + value.length, + "input cursor is at expected position" + ); + info("Open the popup again"); + await populateAndOpenPopup(popup); + + info("Check that Arrow Left + Shift does not close the popup"); + const timeoutRes = "TIMED_OUT"; + const onRaceEnded = Promise.race([ + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(res => setTimeout(() => res(timeoutRes), 500)), + popup.once("popup-closed"), + ]); + EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }); + const raceResult = await onRaceEnded; + is(raceResult, timeoutRes, "popup wasn't closed"); + ok(popup.isOpen, "popup is still open"); + is(input.selectionEnd - input.selectionStart, 1, "text was selected"); + ok(hasFocus(input), "input still has the focus"); + + info("Check that Arrow Left closes the popup"); + onPopupClosed = popup.once("popup-closed"); + EventUtils.synthesizeKey("KEY_ArrowLeft"); + await onPopupClosed; + is( + input.selectionStart, + value.length - 1, + "input cursor was moved one char back" + ); + is(input.selectionEnd, input.selectionStart, "selection was removed"); + is(onClickCalled.length, 0, "onClick wasn't called"); + ok(hasFocus(input), "input still has the focus"); + + info("Open the popup again"); + await populateAndOpenPopup(popup); + + info("Check that Arrow Right + Shift does not trigger onClick"); + EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }); + is(onClickCalled.length, 0, "onClick wasn't called"); + is(input.selectionEnd - input.selectionStart, 1, "input text was selected"); + ok(hasFocus(input), "input still has the focus"); + + info("Check that Arrow Right triggers onClick"); + EventUtils.synthesizeKey("KEY_ArrowRight"); + is(onClickCalled.length, 1, "onClick was called"); + is( + onClickCalled[0], + popupItems[0], + "onClick was called with the selected item" + ); + ok(hasFocus(input), "input still has the focus"); + + info("Check that Enter triggers onClick"); + EventUtils.synthesizeKey("KEY_Enter"); + is(onClickCalled.length, 2, "onClick was called"); + is( + onClickCalled[1], + popupItems[0], + "onClick was called with the selected item" + ); + ok(hasFocus(input), "input still has the focus"); + + info("Check that Tab triggers onClick"); + EventUtils.synthesizeKey("KEY_Tab"); + is(onClickCalled.length, 3, "onClick was called"); + is( + onClickCalled[2], + popupItems[0], + "onClick was called with the selected item" + ); + ok(hasFocus(input), "input still has the focus"); + + info( + "Check that Shift+Tab does not trigger onClick and move the focus out of the input" + ); + EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); + is(onClickCalled.length, 3, "onClick wasn't called"); + is(hasFocus(input), false, "input does not have the focus anymore"); + + const onPopupClose = popup.once("popup-closed"); + popup.hidePopup(); + await onPopupClose; +}); + +const popupItems = [ + { label: "item-0", value: "value-0" }, + { label: "item-1", value: "value-1" }, + { label: "item-2", value: "value-2" }, +]; + +async function populateAndOpenPopup(popup) { + popup.setItems(popupItems); + await popup.openPopup(); +} + +/** + * Returns true if the give node is currently focused. + */ +function hasFocus(node) { + return ( + node.ownerDocument.activeElement == node && node.ownerDocument.hasFocus() + ); +} + +/** + * Check that the selected item in the popup is the expected one. Also check that the + * active descendant is properly set and that the popup has the focus. + * + * @param {AutocompletePopup} popup + * @param {HTMLInput} input + * @param {Object} expectedSelectedItem + * @param {String} info + */ +function checkPopupSelectedItem(popup, input, expectedSelectedItem, info) { + is(popup.selectedItem.label, expectedSelectedItem.label, info); + checkActiveDescendant(popup, input); + ok(hasFocus(input), "input still has the focus"); +} + +function checkActiveDescendant(popup, input) { + const activeElement = input.ownerDocument.activeElement; + const descendantId = activeElement.getAttribute("aria-activedescendant"); + const popupItem = popup._tooltip.panel.querySelector(`#${descendantId}`); + const cloneItem = input.ownerDocument.querySelector(`#${descendantId}`); + + ok(popupItem, "Active descendant is found in the popup list"); + ok(cloneItem, "Active descendant is found in the list clone"); + is( + stripNS(popupItem.outerHTML), + cloneItem.outerHTML, + "Cloned item has the same HTML as the original element" + ); +} + +function stripNS(text) { + return text.replace(RegExp(' xmlns="http://www.w3.org/1999/xhtml"', "g"), ""); +} |