browser_rules_class_panel_autocomplete.js (9613B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test that the autocomplete for the class panel input behaves as expected. The test also 7 // checks that we're using the cache to retrieve the data when we can do so, and that the 8 // cache gets cleared, and we're getting data from the server, when there's mutation on 9 // the page. 10 11 const TEST_URI = `${URL_ROOT}doc_class_panel_autocomplete.html`; 12 13 add_task(async function () { 14 await addTab(TEST_URI); 15 const { inspector, view } = await openRuleView(); 16 const { addEl: textInput } = view.classListPreviewer; 17 await selectNode("#auto-div-id-3", inspector); 18 19 info("Open the class panel"); 20 view.showClassPanel(); 21 22 textInput.focus(); 23 24 info("Type a letter and check that the popup has the expected items"); 25 const allClasses = [ 26 "auto-body-class-1", 27 "auto-body-class-2", 28 "auto-bold", 29 "auto-cssom-primary-color", 30 "auto-div-class-1", 31 "auto-div-class-2", 32 "auto-html-class-1", 33 "auto-html-class-2", 34 "auto-inline-class-1", 35 "auto-inline-class-2", 36 "auto-inline-class-3", 37 "auto-inline-class-4", 38 "auto-inline-class-5", 39 "auto-inline-nested-class-1", 40 "auto-inline-nested-class-2", 41 "auto-inline-nested-class-3", 42 "auto-inline-nested-class-4", 43 "auto-inline-nested-class-5", 44 "auto-inline-nested-class-6", 45 "auto-stylesheet-class-1", 46 "auto-stylesheet-class-2", 47 "auto-stylesheet-class-3", 48 "auto-stylesheet-class-4", 49 "auto-stylesheet-class-5", 50 "auto-stylesheet-nested-class-1", 51 "auto-stylesheet-nested-class-2", 52 "auto-stylesheet-nested-class-3", 53 "auto-stylesheet-nested-class-4", 54 "auto-stylesheet-nested-class-5", 55 "auto-stylesheet-nested-class-6", 56 ]; 57 58 const { autocompletePopup } = view.classListPreviewer; 59 let onPopupOpened = autocompletePopup.once("popup-opened"); 60 EventUtils.synthesizeKey("a", {}, view.styleWindow); 61 await waitForClassApplied("auto-body-class-1", "#auto-div-id-3"); 62 await onPopupOpened; 63 await checkAutocompleteItems( 64 autocompletePopup, 65 allClasses, 66 "The autocomplete popup has all the classes used in the DOM and in stylesheets" 67 ); 68 69 info( 70 "Test that typing more letters filters the autocomplete popup and uses the cache mechanism" 71 ); 72 EventUtils.sendString("uto-b", view.styleWindow); 73 await waitForClassApplied("auto-body-class-1", "#auto-div-id-3"); 74 75 await checkAutocompleteItems( 76 autocompletePopup, 77 allClasses.filter(cls => cls.startsWith("auto-b")), 78 "The autocomplete popup was filtered with the content of the input" 79 ); 80 ok(true, "The results were retrieved from the cache mechanism"); 81 82 info("Test that autocomplete shows up-to-date results"); 83 // Modify the content page and assert that the new class is displayed in the 84 // autocomplete if the user types a new letter. 85 const onNewMutation = inspector.inspectorFront.walker.once("new-mutations"); 86 await ContentTask.spawn(gBrowser.selectedBrowser, null, async function () { 87 content.document.body.classList.add("auto-body-added-by-script"); 88 }); 89 await onNewMutation; 90 await waitForClassApplied("auto-body-added-by-script", "body"); 91 92 // close & reopen the autocomplete so it picks up the added to another element while autocomplete was opened 93 let onPopupClosed = autocompletePopup.once("popup-closed"); 94 EventUtils.synthesizeKey("KEY_Escape", {}, view.styleWindow); 95 await onPopupClosed; 96 97 // input is now auto-body 98 onPopupOpened = autocompletePopup.once("popup-opened"); 99 EventUtils.sendString("ody", view.styleWindow); 100 await onPopupOpened; 101 await checkAutocompleteItems( 102 autocompletePopup, 103 [ 104 ...allClasses.filter(cls => cls.startsWith("auto-body")), 105 "auto-body-added-by-script", 106 ].sort(), 107 "The autocomplete popup was filtered with the content of the input" 108 ); 109 110 info( 111 "Test that typing a letter that won't match any of the item closes the popup" 112 ); 113 // input is now auto-bodyy 114 onPopupClosed = autocompletePopup.once("popup-closed"); 115 EventUtils.synthesizeKey("y", {}, view.styleWindow); 116 await waitForClassApplied("auto-bodyy", "#auto-div-id-3"); 117 await onPopupClosed; 118 ok(true, "The popup was closed as expected"); 119 await checkAutocompleteItems(autocompletePopup, [], "The popup was cleared"); 120 121 info("Clear the input and try to autocomplete again"); 122 textInput.select(); 123 EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow); 124 // Wait a bit so the debounced function can be executed 125 await wait(200); 126 127 onPopupOpened = autocompletePopup.once("popup-opened"); 128 EventUtils.synthesizeKey("a", {}, view.styleWindow); 129 await onPopupOpened; 130 131 await checkAutocompleteItems( 132 autocompletePopup, 133 [...allClasses, "auto-body-added-by-script"].sort(), 134 "The autocomplete popup was updated with the new class added to the DOM" 135 ); 136 137 info("Test keyboard shortcut when the popup is displayed"); 138 // Escape to hide 139 onPopupClosed = autocompletePopup.once("popup-closed"); 140 EventUtils.synthesizeKey("KEY_Escape", {}, view.styleWindow); 141 await onPopupClosed; 142 ok(true, "The popup was closed when hitting escape"); 143 144 // Ctrl + space to show again 145 onPopupOpened = autocompletePopup.once("popup-opened"); 146 EventUtils.synthesizeKey(" ", { ctrlKey: true }, view.styleWindow); 147 await onPopupOpened; 148 ok(true, "Popup was opened again with Ctrl+Space"); 149 await checkAutocompleteItems( 150 autocompletePopup, 151 [...allClasses, "auto-body-added-by-script"].sort() 152 ); 153 154 // Arrow left to hide 155 onPopupClosed = autocompletePopup.once("popup-closed"); 156 EventUtils.synthesizeKey("KEY_ArrowLeft", {}, view.styleWindow); 157 await onPopupClosed; 158 ok(true, "The popup was closed as when hitting ArrowLeft"); 159 160 // Arrow right and Ctrl + space to show again, and Arrow Right to accept 161 onPopupOpened = autocompletePopup.once("popup-opened"); 162 EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow); 163 EventUtils.synthesizeKey(" ", { ctrlKey: true }, view.styleWindow); 164 await onPopupOpened; 165 166 onPopupClosed = autocompletePopup.once("popup-closed"); 167 EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow); 168 await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3"); 169 await onPopupClosed; 170 is( 171 textInput.value, 172 "auto-body-added-by-script", 173 "ArrowRight puts the selected item in the input and closes the popup" 174 ); 175 176 // Backspace to show the list again 177 onPopupOpened = autocompletePopup.once("popup-opened"); 178 EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow); 179 await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3"); 180 await onPopupOpened; 181 is( 182 textInput.value, 183 "auto-body-added-by-scrip", 184 "ArrowRight puts the selected item in the input and closes the popup" 185 ); 186 await checkAutocompleteItems( 187 autocompletePopup, 188 ["auto-body-added-by-script"], 189 "The autocomplete does show the matching items after hitting backspace" 190 ); 191 192 // Enter to accept 193 onPopupClosed = autocompletePopup.once("popup-closed"); 194 EventUtils.synthesizeKey("KEY_Enter", {}, view.styleWindow); 195 await waitForClassRemoved("auto-body-added-by-scrip"); 196 await onPopupClosed; 197 is( 198 textInput.value, 199 "auto-body-added-by-script", 200 "Enter puts the selected item in the input and closes the popup" 201 ); 202 203 // Backspace to show again 204 onPopupOpened = autocompletePopup.once("popup-opened"); 205 EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow); 206 await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3"); 207 await onPopupOpened; 208 is( 209 textInput.value, 210 "auto-body-added-by-scrip", 211 "ArrowRight puts the selected item in the input and closes the popup" 212 ); 213 await checkAutocompleteItems( 214 autocompletePopup, 215 ["auto-body-added-by-script"], 216 "The autocomplete does show the matching items after hitting backspace" 217 ); 218 219 // Tab to accept 220 onPopupClosed = autocompletePopup.once("popup-closed"); 221 EventUtils.synthesizeKey("KEY_Tab", {}, view.styleWindow); 222 await onPopupClosed; 223 is( 224 textInput.value, 225 "auto-body-added-by-script", 226 "Tab puts the selected item in the input and closes the popup" 227 ); 228 await waitForClassRemoved("auto-body-added-by-scrip"); 229 }); 230 231 async function checkAutocompleteItems( 232 autocompletePopup, 233 expectedItems, 234 assertionMessage 235 ) { 236 await waitForSuccess( 237 () => 238 getAutocompleteItems(autocompletePopup).length === expectedItems.length 239 ); 240 const items = getAutocompleteItems(autocompletePopup); 241 Assert.deepEqual(items, expectedItems, assertionMessage); 242 } 243 244 function getAutocompleteItems(autocompletePopup) { 245 return Array.from(autocompletePopup.tooltip.panel.querySelectorAll("li")).map( 246 el => el.textContent 247 ); 248 } 249 250 async function waitForClassApplied(cls, selector) { 251 info("Wait for class to be applied: " + cls); 252 await SpecialPowers.spawn( 253 gBrowser.selectedBrowser, 254 [cls, selector], 255 async (_cls, _selector) => { 256 return ContentTaskUtils.waitForCondition(() => 257 content.document.querySelector(_selector).classList.contains(_cls) 258 ); 259 } 260 ); 261 // Wait for debounced functions to be executed 262 await wait(200); 263 } 264 265 async function waitForClassRemoved(cls) { 266 info("Wait for class to be removed: " + cls); 267 await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => { 268 return ContentTaskUtils.waitForCondition( 269 () => 270 !content.document 271 .querySelector("#auto-div-id-3") 272 .classList.contains(_cls) 273 ); 274 }); 275 // Wait for debounced functions to be executed 276 await wait(200); 277 }