|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 // See browser/components/search/test/browser_*_behavior.js for tests of actual |
|
5 // searches. |
|
6 |
|
7 const ENGINE_LOGO = "searchEngineLogo.xml"; |
|
8 const ENGINE_NO_LOGO = "searchEngineNoLogo.xml"; |
|
9 |
|
10 const SERVICE_EVENT_NAME = "ContentSearchService"; |
|
11 |
|
12 const LOGO_LOW_DPI_SIZE = [65, 26]; |
|
13 const LOGO_HIGH_DPI_SIZE = [130, 52]; |
|
14 |
|
15 // The test has an expected search event queue and a search event listener. |
|
16 // Search events that are expected to happen are added to the queue, and the |
|
17 // listener consumes the queue and ensures that each event it receives is at |
|
18 // the head of the queue. |
|
19 // |
|
20 // Each item in the queue is an object { type, deferred }. type is the |
|
21 // expected search event type. deferred is a Promise.defer() value that is |
|
22 // resolved when the event is consumed. |
|
23 var gExpectedSearchEventQueue = []; |
|
24 |
|
25 var gNewEngines = []; |
|
26 |
|
27 function runTests() { |
|
28 let oldCurrentEngine = Services.search.currentEngine; |
|
29 |
|
30 yield addNewTabPageTab(); |
|
31 yield whenSearchInitDone(); |
|
32 |
|
33 // The tab is removed at the end of the test, so there's no need to remove |
|
34 // this listener at the end of the test. |
|
35 info("Adding search event listener"); |
|
36 getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener); |
|
37 |
|
38 let panel = searchPanel(); |
|
39 is(panel.state, "closed", "Search panel should be closed initially"); |
|
40 |
|
41 // The panel's animation often is not finished when the test clicks on panel |
|
42 // children, which makes the test click the wrong children, so disable it. |
|
43 panel.setAttribute("animate", "false"); |
|
44 |
|
45 // Add the two test engines. |
|
46 let logoEngine = null; |
|
47 yield promiseNewSearchEngine(true).then(engine => { |
|
48 logoEngine = engine; |
|
49 TestRunner.next(); |
|
50 }); |
|
51 ok(!!logoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE), |
|
52 "Sanity check: engine should have 1x logo"); |
|
53 ok(!!logoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE), |
|
54 "Sanity check: engine should have 2x logo"); |
|
55 |
|
56 let noLogoEngine = null; |
|
57 yield promiseNewSearchEngine(false).then(engine => { |
|
58 noLogoEngine = engine; |
|
59 TestRunner.next(); |
|
60 }); |
|
61 ok(!noLogoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE), |
|
62 "Sanity check: engine should not have 1x logo"); |
|
63 ok(!noLogoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE), |
|
64 "Sanity check: engine should not have 2x logo"); |
|
65 |
|
66 // Use the search service to change the current engine to the logo engine. |
|
67 Services.search.currentEngine = logoEngine; |
|
68 yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); |
|
69 checkCurrentEngine(ENGINE_LOGO); |
|
70 |
|
71 // Click the logo to open the search panel. |
|
72 yield Promise.all([ |
|
73 promisePanelShown(panel), |
|
74 promiseClick(logoImg()), |
|
75 ]).then(TestRunner.next); |
|
76 |
|
77 // In the search panel, click the no-logo engine. It should become the |
|
78 // current engine. |
|
79 let noLogoBox = null; |
|
80 for (let box of panel.childNodes) { |
|
81 if (box.getAttribute("engine") == noLogoEngine.name) { |
|
82 noLogoBox = box; |
|
83 break; |
|
84 } |
|
85 } |
|
86 ok(noLogoBox, "Search panel should contain the no-logo engine"); |
|
87 yield Promise.all([ |
|
88 promiseSearchEvents(["CurrentEngine"]), |
|
89 promiseClick(noLogoBox), |
|
90 ]).then(TestRunner.next); |
|
91 |
|
92 checkCurrentEngine(ENGINE_NO_LOGO); |
|
93 |
|
94 // Switch back to the logo engine. |
|
95 Services.search.currentEngine = logoEngine; |
|
96 yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); |
|
97 checkCurrentEngine(ENGINE_LOGO); |
|
98 |
|
99 // Open the panel again. |
|
100 yield Promise.all([ |
|
101 promisePanelShown(panel), |
|
102 promiseClick(logoImg()), |
|
103 ]).then(TestRunner.next); |
|
104 |
|
105 // In the search panel, click the Manage Engines box. |
|
106 let manageBox = $("manage"); |
|
107 ok(!!manageBox, "The Manage Engines box should be present in the document"); |
|
108 yield Promise.all([ |
|
109 promiseManagerOpen(), |
|
110 promiseClick(manageBox), |
|
111 ]).then(TestRunner.next); |
|
112 |
|
113 // Done. Revert the current engine and remove the new engines. |
|
114 Services.search.currentEngine = oldCurrentEngine; |
|
115 yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); |
|
116 |
|
117 let events = []; |
|
118 for (let engine of gNewEngines) { |
|
119 Services.search.removeEngine(engine); |
|
120 events.push("State"); |
|
121 } |
|
122 yield promiseSearchEvents(events).then(TestRunner.next); |
|
123 } |
|
124 |
|
125 function searchEventListener(event) { |
|
126 info("Got search event " + event.detail.type); |
|
127 let passed = false; |
|
128 let nonempty = gExpectedSearchEventQueue.length > 0; |
|
129 ok(nonempty, "Expected search event queue should be nonempty"); |
|
130 if (nonempty) { |
|
131 let { type, deferred } = gExpectedSearchEventQueue.shift(); |
|
132 is(event.detail.type, type, "Got expected search event " + type); |
|
133 if (event.detail.type == type) { |
|
134 passed = true; |
|
135 // Let gSearch respond to the event before continuing. |
|
136 executeSoon(() => deferred.resolve()); |
|
137 } |
|
138 } |
|
139 if (!passed) { |
|
140 info("Didn't get expected event, stopping the test"); |
|
141 getContentWindow().removeEventListener(SERVICE_EVENT_NAME, |
|
142 searchEventListener); |
|
143 // Set next() to a no-op so the test really does stop. |
|
144 TestRunner.next = function () {}; |
|
145 TestRunner.finish(); |
|
146 } |
|
147 } |
|
148 |
|
149 function $(idSuffix) { |
|
150 return getContentDocument().getElementById("newtab-search-" + idSuffix); |
|
151 } |
|
152 |
|
153 function promiseSearchEvents(events) { |
|
154 info("Expecting search events: " + events); |
|
155 events = events.map(e => ({ type: e, deferred: Promise.defer() })); |
|
156 gExpectedSearchEventQueue.push(...events); |
|
157 return Promise.all(events.map(e => e.deferred.promise)); |
|
158 } |
|
159 |
|
160 function promiseNewSearchEngine(withLogo) { |
|
161 let basename = withLogo ? ENGINE_LOGO : ENGINE_NO_LOGO; |
|
162 info("Waiting for engine to be added: " + basename); |
|
163 |
|
164 // Wait for the search events triggered by adding the new engine. |
|
165 // engine-added engine-loaded |
|
166 let expectedSearchEvents = ["State", "State"]; |
|
167 if (withLogo) { |
|
168 // an engine-changed for each of the two logos |
|
169 expectedSearchEvents.push("State", "State"); |
|
170 } |
|
171 let eventPromise = promiseSearchEvents(expectedSearchEvents); |
|
172 |
|
173 // Wait for addEngine(). |
|
174 let addDeferred = Promise.defer(); |
|
175 let url = getRootDirectory(gTestPath) + basename; |
|
176 Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, { |
|
177 onSuccess: function (engine) { |
|
178 info("Search engine added: " + basename); |
|
179 gNewEngines.push(engine); |
|
180 addDeferred.resolve(engine); |
|
181 }, |
|
182 onError: function (errCode) { |
|
183 ok(false, "addEngine failed with error code " + errCode); |
|
184 addDeferred.reject(); |
|
185 }, |
|
186 }); |
|
187 |
|
188 // Make a new promise that wraps the previous promises. The only point of |
|
189 // this is to pass the new engine to the yielder via deferred.resolve(), |
|
190 // which is a little nicer than passing an array whose first element is the |
|
191 // new engine. |
|
192 let deferred = Promise.defer(); |
|
193 Promise.all([addDeferred.promise, eventPromise]).then(values => { |
|
194 let newEngine = values[0]; |
|
195 deferred.resolve(newEngine); |
|
196 }, () => deferred.reject()); |
|
197 return deferred.promise; |
|
198 } |
|
199 |
|
200 function checkCurrentEngine(basename) { |
|
201 let engine = Services.search.currentEngine; |
|
202 ok(engine.name.contains(basename), |
|
203 "Sanity check: current engine: engine.name=" + engine.name + |
|
204 " basename=" + basename); |
|
205 |
|
206 // gSearch.currentEngineName |
|
207 is(gSearch().currentEngineName, engine.name, |
|
208 "currentEngineName: " + engine.name); |
|
209 |
|
210 // search bar logo |
|
211 let logoSize = [px * window.devicePixelRatio for (px of LOGO_LOW_DPI_SIZE)]; |
|
212 let logoURI = engine.getIconURLBySize(...logoSize); |
|
213 let logo = logoImg(); |
|
214 is(logo.hidden, !logoURI, |
|
215 "Logo should be visible iff engine has a logo: " + engine.name); |
|
216 if (logoURI) { |
|
217 is(logo.style.backgroundImage, 'url("' + logoURI + '")', "Logo URI"); |
|
218 } |
|
219 |
|
220 // "selected" attributes of engines in the panel |
|
221 let panel = searchPanel(); |
|
222 for (let engineBox of panel.childNodes) { |
|
223 let engineName = engineBox.getAttribute("engine"); |
|
224 if (engineName == engine.name) { |
|
225 is(engineBox.getAttribute("selected"), "true", |
|
226 "Engine box's selected attribute should be true for " + |
|
227 "selected engine: " + engineName); |
|
228 } |
|
229 else { |
|
230 ok(!engineBox.hasAttribute("selected"), |
|
231 "Engine box's selected attribute should be absent for " + |
|
232 "non-selected engine: " + engineName); |
|
233 } |
|
234 } |
|
235 } |
|
236 |
|
237 function promisePanelShown(panel) { |
|
238 let deferred = Promise.defer(); |
|
239 info("Waiting for popupshown"); |
|
240 panel.addEventListener("popupshown", function onEvent() { |
|
241 panel.removeEventListener("popupshown", onEvent); |
|
242 is(panel.state, "open", "Panel state"); |
|
243 executeSoon(() => deferred.resolve()); |
|
244 }); |
|
245 return deferred.promise; |
|
246 } |
|
247 |
|
248 function promiseClick(node) { |
|
249 let deferred = Promise.defer(); |
|
250 let win = getContentWindow(); |
|
251 SimpleTest.waitForFocus(() => { |
|
252 EventUtils.synthesizeMouseAtCenter(node, {}, win); |
|
253 deferred.resolve(); |
|
254 }, win); |
|
255 return deferred.promise; |
|
256 } |
|
257 |
|
258 function promiseManagerOpen() { |
|
259 info("Waiting for the search manager window to open..."); |
|
260 let deferred = Promise.defer(); |
|
261 let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]. |
|
262 getService(Ci.nsIWindowWatcher); |
|
263 winWatcher.registerNotification(function onWin(subj, topic, data) { |
|
264 if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) { |
|
265 subj.addEventListener("load", function onLoad() { |
|
266 subj.removeEventListener("load", onLoad); |
|
267 if (subj.document.documentURI == |
|
268 "chrome://browser/content/search/engineManager.xul") { |
|
269 winWatcher.unregisterNotification(onWin); |
|
270 ok(true, "Observed search manager window opened"); |
|
271 is(subj.opener, gWindow, |
|
272 "Search engine manager opener should be the chrome browser " + |
|
273 "window containing the newtab page"); |
|
274 executeSoon(() => { |
|
275 subj.close(); |
|
276 deferred.resolve(); |
|
277 }); |
|
278 } |
|
279 }); |
|
280 } |
|
281 }); |
|
282 return deferred.promise; |
|
283 } |
|
284 |
|
285 function searchPanel() { |
|
286 return $("panel"); |
|
287 } |
|
288 |
|
289 function logoImg() { |
|
290 return $("logo"); |
|
291 } |
|
292 |
|
293 function gSearch() { |
|
294 return getContentWindow().gSearch; |
|
295 } |