Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / devtools / client / shared / test / browser_autocomplete_popup_input.js
blob32fc6da25e664728a8ed874ef1b841100657db47
1 /* Any copyright is dedicated to the Public Domain.
2  * http://creativecommons.org/publicdomain/zero/1.0/ */
4 "use strict";
6 add_task(async function () {
7   // Prevent the URL Bar to steal the focus.
8   const preventUrlBarFocus = e => {
9     e.preventDefault();
10   };
11   window.gURLBar.addEventListener("beforefocus", preventUrlBarFocus);
12   registerCleanupFunction(() => {
13     window.gURLBar.removeEventListener("beforefocus", preventUrlBarFocus);
14   });
16   const AutocompletePopup = require("resource://devtools/client/shared/autocomplete-popup.js");
18   info("Create an autocompletion popup and an input that will be bound to it");
19   const { doc } = await createHost();
21   const input = doc.createElement("input");
22   const prevInput = doc.createElement("input");
23   doc.body.append(prevInput, input, doc.createElement("input"));
25   const onSelectCalled = [];
26   const onClickCalled = [];
27   const popup = new AutocompletePopup(doc, {
28     input,
29     position: "top",
30     autoSelect: true,
31     onSelect: item => onSelectCalled.push(item),
32     onClick: (e, item) => onClickCalled.push(item),
33   });
35   input.focus();
36   ok(hasFocus(input), "input has focus");
38   info(
39     "Check that Tab moves the focus out of the input when the popup isn't opened"
40   );
41   EventUtils.synthesizeKey("KEY_Tab");
42   is(onClickCalled.length, 0, "onClick wasn't called");
43   is(hasFocus(input), false, "input does not have the focus anymore");
44   info("Set the focus back to the input and open the popup");
45   input.focus();
46   await new Promise(res => setTimeout(res, 0));
47   ok(hasFocus(input), "input is focused");
49   await populateAndOpenPopup(popup);
51   const checkSelectedItem = (expected, info) =>
52     checkPopupSelectedItem(popup, input, expected, info);
54   checkSelectedItem(popupItems[0], "First item from top is selected");
55   is(
56     onSelectCalled[0].label,
57     popupItems[0].label,
58     "onSelect was called with expected param"
59   );
61   info("Check that arrow down/up navigates into the list");
62   EventUtils.synthesizeKey("KEY_ArrowDown");
63   checkSelectedItem(popupItems[1], "item-1 is selected");
64   is(
65     onSelectCalled[1].label,
66     popupItems[1].label,
67     "onSelect was called with expected param"
68   );
70   EventUtils.synthesizeKey("KEY_ArrowDown");
71   checkSelectedItem(popupItems[2], "item-2 is selected");
72   is(
73     onSelectCalled[2].label,
74     popupItems[2].label,
75     "onSelect was called with expected param"
76   );
78   EventUtils.synthesizeKey("KEY_ArrowDown");
79   checkSelectedItem(popupItems[0], "item-0 is selected");
80   is(
81     onSelectCalled[3].label,
82     popupItems[0].label,
83     "onSelect was called with expected param"
84   );
86   EventUtils.synthesizeKey("KEY_ArrowUp");
87   checkSelectedItem(popupItems[2], "item-2 is selected");
88   is(
89     onSelectCalled[4].label,
90     popupItems[2].label,
91     "onSelect was called with expected param"
92   );
94   EventUtils.synthesizeKey("KEY_ArrowUp");
95   checkSelectedItem(popupItems[1], "item-2 is selected");
96   is(
97     onSelectCalled[5].label,
98     popupItems[1].label,
99     "onSelect was called with expected param"
100   );
102   info("Check that Escape closes the popup");
103   let onPopupClosed = popup.once("popup-closed");
104   EventUtils.synthesizeKey("KEY_Escape");
105   await onPopupClosed;
106   ok(true, "popup was closed with Escape key");
107   ok(hasFocus(input), "input still has the focus");
108   is(onClickCalled.length, 0, "onClick wasn't called");
110   info("Fill the input");
111   const value = "item";
112   EventUtils.sendString(value);
113   is(input.value, value, "input has the expected value");
114   is(
115     input.selectionStart,
116     value.length,
117     "input cursor is at expected position"
118   );
119   info("Open the popup again");
120   await populateAndOpenPopup(popup);
122   info("Check that Arrow Left + Shift does not close the popup");
123   const timeoutRes = "TIMED_OUT";
124   const onRaceEnded = Promise.race([
125     // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
126     await new Promise(res => setTimeout(() => res(timeoutRes), 500)),
127     popup.once("popup-closed"),
128   ]);
129   EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
130   const raceResult = await onRaceEnded;
131   is(raceResult, timeoutRes, "popup wasn't closed");
132   ok(popup.isOpen, "popup is still open");
133   is(input.selectionEnd - input.selectionStart, 1, "text was selected");
134   ok(hasFocus(input), "input still has the focus");
136   info("Check that Arrow Left closes the popup");
137   onPopupClosed = popup.once("popup-closed");
138   EventUtils.synthesizeKey("KEY_ArrowLeft");
139   await onPopupClosed;
140   is(
141     input.selectionStart,
142     value.length - 1,
143     "input cursor was moved one char back"
144   );
145   is(input.selectionEnd, input.selectionStart, "selection was removed");
146   is(onClickCalled.length, 0, "onClick wasn't called");
147   ok(hasFocus(input), "input still has the focus");
149   info("Open the popup again");
150   await populateAndOpenPopup(popup);
152   info("Check that Arrow Right + Shift does not trigger onClick");
153   EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
154   is(onClickCalled.length, 0, "onClick wasn't called");
155   is(input.selectionEnd - input.selectionStart, 1, "input text was selected");
156   ok(hasFocus(input), "input still has the focus");
158   info("Check that Arrow Right triggers onClick");
159   EventUtils.synthesizeKey("KEY_ArrowRight");
160   is(onClickCalled.length, 1, "onClick was called");
161   is(
162     onClickCalled[0],
163     popupItems[0],
164     "onClick was called with the selected item"
165   );
166   ok(hasFocus(input), "input still has the focus");
168   info("Check that Enter triggers onClick");
169   EventUtils.synthesizeKey("KEY_Enter");
170   is(onClickCalled.length, 2, "onClick was called");
171   is(
172     onClickCalled[1],
173     popupItems[0],
174     "onClick was called with the selected item"
175   );
176   ok(hasFocus(input), "input still has the focus");
178   info("Check that Tab triggers onClick");
179   EventUtils.synthesizeKey("KEY_Tab");
180   is(onClickCalled.length, 3, "onClick was called");
181   is(
182     onClickCalled[2],
183     popupItems[0],
184     "onClick was called with the selected item"
185   );
186   ok(hasFocus(input), "input still has the focus");
188   info(
189     "Check that Shift+Tab does not trigger onClick and move the focus out of the input"
190   );
191   EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
192   is(onClickCalled.length, 3, "onClick wasn't called");
194   is(hasFocus(input), false, "input does not have the focus anymore");
195   is(hasFocus(prevInput), true, "Shift+Tab moves the focus to prevInput");
197   const onPopupClose = popup.once("popup-closed");
198   popup.hidePopup();
199   await onPopupClose;
202 const popupItems = [
203   { label: "item-0", value: "value-0" },
204   { label: "item-1", value: "value-1" },
205   { label: "item-2", value: "value-2" },
208 async function populateAndOpenPopup(popup) {
209   popup.setItems(popupItems);
210   await popup.openPopup();
214  * Returns true if the give node is currently focused.
215  */
216 function hasFocus(node) {
217   return (
218     node.ownerDocument.activeElement == node && node.ownerDocument.hasFocus()
219   );
223  * Check that the selected item in the popup is the expected one. Also check that the
224  * active descendant is properly set and that the popup has the focus.
226  * @param {AutocompletePopup} popup
227  * @param {HTMLInput} input
228  * @param {Object} expectedSelectedItem
229  * @param {String} info
230  */
231 function checkPopupSelectedItem(popup, input, expectedSelectedItem, info) {
232   is(popup.selectedItem.label, expectedSelectedItem.label, info);
233   checkActiveDescendant(popup, input);
234   ok(hasFocus(input), "input still has the focus");
237 function checkActiveDescendant(popup, input) {
238   const activeElement = input.ownerDocument.activeElement;
239   const descendantId = activeElement.getAttribute("aria-activedescendant");
240   const popupItem = popup._tooltip.panel.querySelector(`#${descendantId}`);
241   const cloneItem = input.ownerDocument.querySelector(`#${descendantId}`);
243   ok(popupItem, "Active descendant is found in the popup list");
244   ok(cloneItem, "Active descendant is found in the list clone");
245   is(
246     stripNS(popupItem.outerHTML),
247     cloneItem.outerHTML,
248     "Cloned item has the same HTML as the original element"
249   );
252 function stripNS(text) {
253   return text.replace(RegExp(' xmlns="http://www.w3.org/1999/xhtml"', "g"), "");