browser_styleeditor_autocomplete.js (8155B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 "use strict"; 4 5 // Test that autocompletion works as expected. 6 7 const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html"; 8 const MAX_SUGGESTIONS = 15; 9 10 // Test cases to test that autocompletion works correctly when enabled. 11 // Format: 12 // [ 13 // key, 14 // { 15 // total: Number of suggestions in the popup (-1 if popup is closed), 16 // current: Index of selected suggestion, 17 // inserted: 1 to check whether the selected suggestion is inserted into the 18 // editor or not, 19 // entered: 1 if the suggestion is inserted and finalized 20 // } 21 // ] 22 23 function getTestCases(cssProperties) { 24 const keywords = getCSSKeywords(cssProperties); 25 const getSuggestionNumberFor = suggestionNumberGetter(keywords); 26 27 return [ 28 ["VK_RIGHT"], 29 ["VK_RIGHT"], 30 ["VK_RIGHT"], 31 ["VK_RIGHT"], 32 ["Ctrl+Space", { total: 1, current: 0 }], 33 ["VK_LEFT"], 34 ["VK_RIGHT"], 35 ["VK_DOWN"], 36 ["VK_RIGHT"], 37 ["VK_RIGHT"], 38 ["VK_RIGHT"], 39 ["Ctrl+Space", { total: getSuggestionNumberFor("font"), current: 0 }], 40 ["VK_END"], 41 ["VK_RETURN"], 42 ["b", { total: getSuggestionNumberFor("b"), current: 0 }], 43 ["a", { total: getSuggestionNumberFor("ba"), current: 0 }], 44 [ 45 "VK_DOWN", 46 { total: getSuggestionNumberFor("ba"), current: 0, inserted: 1 }, 47 ], 48 [ 49 "VK_DOWN", 50 { total: getSuggestionNumberFor("ba"), current: 1, inserted: 1 }, 51 ], 52 [ 53 "VK_TAB", 54 { total: getSuggestionNumberFor("ba"), current: 2, inserted: 1 }, 55 ], 56 ["VK_RETURN", { current: 2, inserted: 1, entered: 1 }], 57 ["b", { total: getSuggestionNumberFor("background", "b"), current: 0 }], 58 ["l", { total: getSuggestionNumberFor("background", "bl"), current: 0 }], 59 [ 60 "VK_TAB", 61 { 62 total: getSuggestionNumberFor("background", "bl"), 63 current: 0, 64 inserted: 1, 65 }, 66 ], 67 [ 68 "VK_DOWN", 69 { 70 total: getSuggestionNumberFor("background", "bl"), 71 current: 1, 72 inserted: 1, 73 }, 74 ], 75 [ 76 "VK_UP", 77 { 78 total: getSuggestionNumberFor("background", "bl"), 79 current: 0, 80 inserted: 1, 81 }, 82 ], 83 [ 84 "VK_TAB", 85 { 86 total: getSuggestionNumberFor("background", "bl"), 87 current: 1, 88 inserted: 1, 89 }, 90 ], 91 [ 92 "VK_TAB", 93 { 94 total: getSuggestionNumberFor("background", "bl"), 95 current: 2, 96 inserted: 1, 97 }, 98 ], 99 [";"], 100 ["VK_RETURN"], 101 ["c", { total: getSuggestionNumberFor("c"), current: 0 }], 102 ["o", { total: getSuggestionNumberFor("co"), current: 0 }], 103 ["VK_RETURN", { current: 0, inserted: 1 }], 104 ["r", { total: getSuggestionNumberFor("color", "r"), current: 0 }], 105 ["VK_RETURN", { current: 0, inserted: 1 }], 106 [";"], 107 ["VK_LEFT"], 108 ["VK_RIGHT"], 109 ["VK_DOWN"], 110 ["VK_RETURN"], 111 ["b", { total: 2, current: 0 }], 112 ["u", { total: 1, current: 0 }], 113 ["VK_RETURN", { current: 0, inserted: 1 }], 114 ["{"], 115 ["VK_HOME"], 116 ["VK_DOWN"], 117 ["VK_DOWN"], 118 ["VK_RIGHT"], 119 ["VK_RIGHT"], 120 ["VK_RIGHT"], 121 ["VK_RIGHT"], 122 ["VK_RIGHT"], 123 ["VK_RIGHT"], 124 ["VK_RIGHT"], 125 ["VK_RIGHT"], 126 ["VK_RIGHT"], 127 ["VK_RIGHT"], 128 ["Ctrl+Space", { total: 1, current: 0 }], 129 ]; 130 } 131 132 add_task(async function () { 133 // We try to type "background" above, so backdrop-filter enabledness affects 134 // the expectations. Instead of branching on the test set the pref to true 135 // here as that is the end state, and it doesn't interact with the test in 136 // other ways. 137 await SpecialPowers.pushPrefEnv({ 138 set: [["layout.css.backdrop-filter.enabled", true]], 139 }); 140 const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); 141 const { cssProperties } = ui; 142 const testCases = getTestCases(cssProperties); 143 144 await ui.selectStyleSheet(ui.editors[1].styleSheet); 145 const editor = await ui.editors[1].getSourceEditor(); 146 147 const sourceEditor = editor.sourceEditor; 148 const popup = sourceEditor.getAutocompletionPopup(); 149 150 await SimpleTest.promiseFocus(panel.panelWindow); 151 152 for (const index in testCases) { 153 await testState(testCases, index, sourceEditor, popup, panel.panelWindow); 154 await checkState(testCases, index, sourceEditor, popup); 155 } 156 }); 157 158 function testState(testCases, index, sourceEditor, popup, panelWindow) { 159 let [key, details] = testCases[index]; 160 let entered; 161 if (details) { 162 entered = details.entered; 163 } 164 const mods = {}; 165 166 info( 167 "pressing key " + 168 key + 169 " to get result: " + 170 JSON.stringify(testCases[index]) + 171 " for index " + 172 index 173 ); 174 175 let evt = "after-suggest"; 176 177 if (key == "Ctrl+Space") { 178 key = " "; 179 mods.ctrlKey = true; 180 } else if (key == "VK_RETURN" && entered) { 181 evt = "popup-hidden"; 182 } else if ( 183 /(left|right|return|home|end)/gi.test(key) || 184 (key == "VK_DOWN" && !popup.isOpen) 185 ) { 186 evt = "cursorActivity"; 187 } else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") { 188 evt = "suggestion-entered"; 189 } 190 191 const ready = sourceEditor.once(evt); 192 EventUtils.synthesizeKey(key, mods, panelWindow); 193 194 return ready; 195 } 196 197 function checkState(testCases, index, sourceEditor, popup) { 198 return new Promise(resolve => { 199 executeSoon(() => { 200 let [, details] = testCases[index]; 201 details = details || {}; 202 const { total, current, inserted } = details; 203 204 if (total != undefined) { 205 ok(popup.isOpen, "Popup is open for index " + index); 206 is( 207 total, 208 popup.itemCount, 209 "Correct total suggestions for index " + index 210 ); 211 is( 212 current, 213 popup.selectedIndex, 214 "Correct index is selected for index " + index 215 ); 216 if (inserted) { 217 const { text } = popup.getItemAtIndex(current); 218 const { line, ch } = sourceEditor.getCursor(); 219 const lineText = sourceEditor.getText(line); 220 is( 221 lineText.substring(ch - text.length, ch), 222 text, 223 "Current suggestion from the popup is inserted into the editor." 224 ); 225 } 226 } else { 227 ok(!popup.isOpen, "Popup is closed for index " + index); 228 if (inserted) { 229 const { text } = popup.getItemAtIndex(current); 230 const { line, ch } = sourceEditor.getCursor(); 231 const lineText = sourceEditor.getText(line); 232 is( 233 lineText.substring(ch - text.length, ch), 234 text, 235 "Current suggestion from the popup is inserted into the editor." 236 ); 237 } 238 } 239 resolve(); 240 }); 241 }); 242 } 243 244 /** 245 * Returns a list of all property names and a map of property name vs possible 246 * CSS values provided by the Gecko engine. 247 * 248 * @return {object} An object with following properties: 249 * - CSSProperties {Array} Array of string containing all the possible 250 * CSS property names. 251 * - CSSValues {Object|Map} A map where key is the property name and 252 * value is an array of string containing all the possible 253 * CSS values the property can have. 254 */ 255 function getCSSKeywords(cssProperties) { 256 const props = {}; 257 const propNames = cssProperties.getNames(); 258 propNames.forEach(prop => { 259 props[prop] = cssProperties.getValues(prop).sort(); 260 }); 261 return { 262 CSSValues: props, 263 CSSProperties: propNames.sort(), 264 }; 265 } 266 267 /** 268 * Returns a function that returns the number of expected suggestions for the given 269 * property and value. If the value is not null, returns the number of values starting 270 * with `value`. Returns the number of properties starting with `property` otherwise. 271 */ 272 function suggestionNumberGetter({ CSSProperties, CSSValues }) { 273 return (property, value) => { 274 if (value == null) { 275 return CSSProperties.filter(prop => prop.startsWith(property)).slice( 276 0, 277 MAX_SUGGESTIONS 278 ).length; 279 } 280 return CSSValues[property] 281 .filter(val => val.startsWith(value)) 282 .slice(0, MAX_SUGGESTIONS).length; 283 }; 284 }