|
1 /* vim: set ts=2 et sw=2 tw=80: */ |
|
2 /* Any copyright is dedicated to the Public Domain. |
|
3 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
4 |
|
5 const TESTCASE_URI = TEST_BASE + "autocomplete.html"; |
|
6 const MAX_SUGGESTIONS = 15; |
|
7 |
|
8 // Pref which decides if CSS autocompletion is enabled in Style Editor or not. |
|
9 const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled"; |
|
10 |
|
11 const {CSSProperties, CSSValues} = getCSSKeywords(); |
|
12 |
|
13 // Test cases to test that autocompletion works correctly when enabled. |
|
14 // Format: |
|
15 // [ |
|
16 // key, |
|
17 // { |
|
18 // total: Number of suggestions in the popup (-1 if popup is closed), |
|
19 // current: Index of selected suggestion, |
|
20 // inserted: 1 to check whether the selected suggestion is inserted into the editor or not, |
|
21 // entered: 1 if the suggestion is inserted and finalized |
|
22 // } |
|
23 // ] |
|
24 let TEST_CASES = [ |
|
25 ['VK_RIGHT'], |
|
26 ['VK_RIGHT'], |
|
27 ['VK_RIGHT'], |
|
28 ['VK_RIGHT'], |
|
29 ['Ctrl+Space', {total: 1, current: 0}], |
|
30 ['VK_LEFT'], |
|
31 ['VK_RIGHT'], |
|
32 ['VK_DOWN'], |
|
33 ['VK_RIGHT'], |
|
34 ['VK_RIGHT'], |
|
35 ['VK_RIGHT'], |
|
36 ['Ctrl+Space', { total: getSuggestionNumberFor("font"), current: 0}], |
|
37 ['VK_END'], |
|
38 ['VK_RETURN'], |
|
39 ['b', {total: getSuggestionNumberFor("b"), current: 0}], |
|
40 ['a', {total: getSuggestionNumberFor("ba"), current: 0}], |
|
41 ['VK_DOWN', {total: getSuggestionNumberFor("ba"), current: 0, inserted: 1}], |
|
42 ['VK_TAB', {total: getSuggestionNumberFor("ba"), current: 1, inserted: 1}], |
|
43 ['VK_RETURN', {current: 1, inserted: 1, entered: 1}], |
|
44 ['b', {total: getSuggestionNumberFor("background", "b"), current: 0}], |
|
45 ['l', {total: getSuggestionNumberFor("background", "bl"), current: 0}], |
|
46 ['VK_TAB', {total: getSuggestionNumberFor("background", "bl"), current: 0, inserted: 1}], |
|
47 ['VK_DOWN', {total: getSuggestionNumberFor("background", "bl"), current: 1, inserted: 1}], |
|
48 ['VK_UP', {total: getSuggestionNumberFor("background", "bl"), current: 0, inserted: 1}], |
|
49 ['VK_TAB', {total: getSuggestionNumberFor("background", "bl"), current: 1, inserted: 1}], |
|
50 ['VK_TAB', {total: getSuggestionNumberFor("background", "bl"), current: 2, inserted: 1}], |
|
51 [';'], |
|
52 ['VK_RETURN'], |
|
53 ['c', {total: getSuggestionNumberFor("c"), current: 0}], |
|
54 ['o', {total: getSuggestionNumberFor("co"), current: 0}], |
|
55 ['VK_RETURN', {current: 0, inserted: 1}], |
|
56 ['r', {total: getSuggestionNumberFor("color", "r"), current: 0}], |
|
57 ['VK_RETURN', {current: 0, inserted: 1}], |
|
58 [';'], |
|
59 ['VK_LEFT'], |
|
60 ['VK_RIGHT'], |
|
61 ['VK_DOWN'], |
|
62 ['VK_RETURN'], |
|
63 ['b', {total: 2, current: 0}], |
|
64 ['u', {total: 1, current: 0}], |
|
65 ['VK_RETURN', {current: 0, inserted: 1}], |
|
66 ['{'], |
|
67 ['VK_HOME'], |
|
68 ['VK_DOWN'], |
|
69 ['VK_DOWN'], |
|
70 ['VK_RIGHT'], |
|
71 ['VK_RIGHT'], |
|
72 ['VK_RIGHT'], |
|
73 ['VK_RIGHT'], |
|
74 ['VK_RIGHT'], |
|
75 ['VK_RIGHT'], |
|
76 ['VK_RIGHT'], |
|
77 ['VK_RIGHT'], |
|
78 ['VK_RIGHT'], |
|
79 ['VK_RIGHT'], |
|
80 ['Ctrl+Space', {total: 1, current: 0}], |
|
81 ]; |
|
82 |
|
83 let gEditor; |
|
84 let gPopup; |
|
85 let index = 0; |
|
86 |
|
87 function test() |
|
88 { |
|
89 waitForExplicitFinish(); |
|
90 |
|
91 addTabAndOpenStyleEditors(1, testEditorAdded); |
|
92 |
|
93 content.location = TESTCASE_URI; |
|
94 } |
|
95 |
|
96 function testEditorAdded(panel) { |
|
97 info("Editor added, getting the source editor and starting tests"); |
|
98 panel.UI.editors[0].getSourceEditor().then(editor => { |
|
99 info("source editor found, starting tests."); |
|
100 gEditor = editor.sourceEditor; |
|
101 gPopup = gEditor.getAutocompletionPopup(); |
|
102 waitForFocus(testState, gPanelWindow); |
|
103 }); |
|
104 } |
|
105 |
|
106 function testState() { |
|
107 if (index == TEST_CASES.length) { |
|
108 testAutocompletionDisabled(); |
|
109 return; |
|
110 } |
|
111 |
|
112 let [key, details] = TEST_CASES[index]; |
|
113 let entered; |
|
114 if (details) { |
|
115 entered = details.entered; |
|
116 } |
|
117 let mods = {}; |
|
118 |
|
119 info("pressing key " + key + " to get result: " + |
|
120 JSON.stringify(TEST_CASES[index]) + " for index " + index); |
|
121 |
|
122 let evt = "after-suggest"; |
|
123 |
|
124 if (key == 'Ctrl+Space') { |
|
125 key = " "; |
|
126 mods.accelKey = true; |
|
127 } |
|
128 else if (key == "VK_RETURN" && entered) { |
|
129 evt = "popup-hidden"; |
|
130 } |
|
131 else if (/(left|right|return|home|end)/ig.test(key) || |
|
132 (key == "VK_DOWN" && !gPopup.isOpen)) { |
|
133 evt = "cursorActivity"; |
|
134 } |
|
135 else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") { |
|
136 evt = "suggestion-entered"; |
|
137 } |
|
138 |
|
139 gEditor.once(evt, checkState); |
|
140 EventUtils.synthesizeKey(key, mods, gPanelWindow); |
|
141 } |
|
142 |
|
143 function checkState() { |
|
144 executeSoon(() => { |
|
145 let [key, details] = TEST_CASES[index]; |
|
146 details = details || {}; |
|
147 let {total, current, inserted} = details; |
|
148 |
|
149 if (total != undefined) { |
|
150 ok(gPopup.isOpen, "Popup is open for index " + index); |
|
151 is(total, gPopup.itemCount, |
|
152 "Correct total suggestions for index " + index); |
|
153 is(current, gPopup.selectedIndex, |
|
154 "Correct index is selected for index " + index); |
|
155 if (inserted) { |
|
156 let { preLabel, label, text } = gPopup.getItemAtIndex(current); |
|
157 let { line, ch } = gEditor.getCursor(); |
|
158 let lineText = gEditor.getText(line); |
|
159 is(lineText.substring(ch - text.length, ch), text, |
|
160 "Current suggestion from the popup is inserted into the editor."); |
|
161 } |
|
162 } |
|
163 else { |
|
164 ok(!gPopup.isOpen, "Popup is closed for index " + index); |
|
165 if (inserted) { |
|
166 let { preLabel, label, text } = gPopup.getItemAtIndex(current); |
|
167 let { line, ch } = gEditor.getCursor(); |
|
168 let lineText = gEditor.getText(line); |
|
169 is(lineText.substring(ch - text.length, ch), text, |
|
170 "Current suggestion from the popup is inserted into the editor."); |
|
171 } |
|
172 } |
|
173 index++; |
|
174 testState(); |
|
175 }); |
|
176 } |
|
177 |
|
178 function testAutocompletionDisabled() { |
|
179 gBrowser.removeCurrentTab(); |
|
180 |
|
181 index = 0; |
|
182 info("Starting test to check if autocompletion is disabled correctly.") |
|
183 Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false); |
|
184 |
|
185 addTabAndOpenStyleEditors(1, testEditorAddedDisabled); |
|
186 |
|
187 content.location = TESTCASE_URI; |
|
188 } |
|
189 |
|
190 function testEditorAddedDisabled(panel) { |
|
191 info("Editor added, getting the source editor and starting tests"); |
|
192 panel.UI.editors[0].getSourceEditor().then(editor => { |
|
193 ok(!editor.sourceEditor.getAutocompletionPopup, |
|
194 "Autocompletion popup does not exist"); |
|
195 cleanup(); |
|
196 }); |
|
197 } |
|
198 |
|
199 function cleanup() { |
|
200 Services.prefs.clearUserPref(AUTOCOMPLETION_PREF); |
|
201 gEditor = null; |
|
202 gPopup = null; |
|
203 finish(); |
|
204 } |
|
205 |
|
206 /** |
|
207 * Returns a list of all property names and a map of property name vs possible |
|
208 * CSS values provided by the Gecko engine. |
|
209 * |
|
210 * @return {Object} An object with following properties: |
|
211 * - CSSProperties {Array} Array of string containing all the possible |
|
212 * CSS property names. |
|
213 * - CSSValues {Object|Map} A map where key is the property name and |
|
214 * value is an array of string containing all the possible |
|
215 * CSS values the property can have. |
|
216 */ |
|
217 function getCSSKeywords() { |
|
218 let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"] |
|
219 .getService(Ci.inIDOMUtils); |
|
220 let props = {}; |
|
221 let propNames = domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES); |
|
222 propNames.forEach(prop => { |
|
223 props[prop] = domUtils.getCSSValuesForProperty(prop).sort(); |
|
224 }); |
|
225 return { |
|
226 CSSValues: props, |
|
227 CSSProperties: propNames.sort() |
|
228 }; |
|
229 } |
|
230 |
|
231 /** |
|
232 * Returns the number of expected suggestions for the given property and value. |
|
233 * If the value is not null, returns the number of values starting with `value`. |
|
234 * Returns the number of properties starting with `property` otherwise. |
|
235 */ |
|
236 function getSuggestionNumberFor(property, value) { |
|
237 if (value == null) { |
|
238 return CSSProperties.filter(prop => prop.startsWith(property)) |
|
239 .slice(0, MAX_SUGGESTIONS).length; |
|
240 } |
|
241 return CSSValues[property].filter(val => val.startsWith(value)) |
|
242 .slice(0, MAX_SUGGESTIONS).length; |
|
243 } |