1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/test/newtab/browser_newtab_search.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,295 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +// See browser/components/search/test/browser_*_behavior.js for tests of actual 1.8 +// searches. 1.9 + 1.10 +const ENGINE_LOGO = "searchEngineLogo.xml"; 1.11 +const ENGINE_NO_LOGO = "searchEngineNoLogo.xml"; 1.12 + 1.13 +const SERVICE_EVENT_NAME = "ContentSearchService"; 1.14 + 1.15 +const LOGO_LOW_DPI_SIZE = [65, 26]; 1.16 +const LOGO_HIGH_DPI_SIZE = [130, 52]; 1.17 + 1.18 +// The test has an expected search event queue and a search event listener. 1.19 +// Search events that are expected to happen are added to the queue, and the 1.20 +// listener consumes the queue and ensures that each event it receives is at 1.21 +// the head of the queue. 1.22 +// 1.23 +// Each item in the queue is an object { type, deferred }. type is the 1.24 +// expected search event type. deferred is a Promise.defer() value that is 1.25 +// resolved when the event is consumed. 1.26 +var gExpectedSearchEventQueue = []; 1.27 + 1.28 +var gNewEngines = []; 1.29 + 1.30 +function runTests() { 1.31 + let oldCurrentEngine = Services.search.currentEngine; 1.32 + 1.33 + yield addNewTabPageTab(); 1.34 + yield whenSearchInitDone(); 1.35 + 1.36 + // The tab is removed at the end of the test, so there's no need to remove 1.37 + // this listener at the end of the test. 1.38 + info("Adding search event listener"); 1.39 + getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener); 1.40 + 1.41 + let panel = searchPanel(); 1.42 + is(panel.state, "closed", "Search panel should be closed initially"); 1.43 + 1.44 + // The panel's animation often is not finished when the test clicks on panel 1.45 + // children, which makes the test click the wrong children, so disable it. 1.46 + panel.setAttribute("animate", "false"); 1.47 + 1.48 + // Add the two test engines. 1.49 + let logoEngine = null; 1.50 + yield promiseNewSearchEngine(true).then(engine => { 1.51 + logoEngine = engine; 1.52 + TestRunner.next(); 1.53 + }); 1.54 + ok(!!logoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE), 1.55 + "Sanity check: engine should have 1x logo"); 1.56 + ok(!!logoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE), 1.57 + "Sanity check: engine should have 2x logo"); 1.58 + 1.59 + let noLogoEngine = null; 1.60 + yield promiseNewSearchEngine(false).then(engine => { 1.61 + noLogoEngine = engine; 1.62 + TestRunner.next(); 1.63 + }); 1.64 + ok(!noLogoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE), 1.65 + "Sanity check: engine should not have 1x logo"); 1.66 + ok(!noLogoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE), 1.67 + "Sanity check: engine should not have 2x logo"); 1.68 + 1.69 + // Use the search service to change the current engine to the logo engine. 1.70 + Services.search.currentEngine = logoEngine; 1.71 + yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); 1.72 + checkCurrentEngine(ENGINE_LOGO); 1.73 + 1.74 + // Click the logo to open the search panel. 1.75 + yield Promise.all([ 1.76 + promisePanelShown(panel), 1.77 + promiseClick(logoImg()), 1.78 + ]).then(TestRunner.next); 1.79 + 1.80 + // In the search panel, click the no-logo engine. It should become the 1.81 + // current engine. 1.82 + let noLogoBox = null; 1.83 + for (let box of panel.childNodes) { 1.84 + if (box.getAttribute("engine") == noLogoEngine.name) { 1.85 + noLogoBox = box; 1.86 + break; 1.87 + } 1.88 + } 1.89 + ok(noLogoBox, "Search panel should contain the no-logo engine"); 1.90 + yield Promise.all([ 1.91 + promiseSearchEvents(["CurrentEngine"]), 1.92 + promiseClick(noLogoBox), 1.93 + ]).then(TestRunner.next); 1.94 + 1.95 + checkCurrentEngine(ENGINE_NO_LOGO); 1.96 + 1.97 + // Switch back to the logo engine. 1.98 + Services.search.currentEngine = logoEngine; 1.99 + yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); 1.100 + checkCurrentEngine(ENGINE_LOGO); 1.101 + 1.102 + // Open the panel again. 1.103 + yield Promise.all([ 1.104 + promisePanelShown(panel), 1.105 + promiseClick(logoImg()), 1.106 + ]).then(TestRunner.next); 1.107 + 1.108 + // In the search panel, click the Manage Engines box. 1.109 + let manageBox = $("manage"); 1.110 + ok(!!manageBox, "The Manage Engines box should be present in the document"); 1.111 + yield Promise.all([ 1.112 + promiseManagerOpen(), 1.113 + promiseClick(manageBox), 1.114 + ]).then(TestRunner.next); 1.115 + 1.116 + // Done. Revert the current engine and remove the new engines. 1.117 + Services.search.currentEngine = oldCurrentEngine; 1.118 + yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); 1.119 + 1.120 + let events = []; 1.121 + for (let engine of gNewEngines) { 1.122 + Services.search.removeEngine(engine); 1.123 + events.push("State"); 1.124 + } 1.125 + yield promiseSearchEvents(events).then(TestRunner.next); 1.126 +} 1.127 + 1.128 +function searchEventListener(event) { 1.129 + info("Got search event " + event.detail.type); 1.130 + let passed = false; 1.131 + let nonempty = gExpectedSearchEventQueue.length > 0; 1.132 + ok(nonempty, "Expected search event queue should be nonempty"); 1.133 + if (nonempty) { 1.134 + let { type, deferred } = gExpectedSearchEventQueue.shift(); 1.135 + is(event.detail.type, type, "Got expected search event " + type); 1.136 + if (event.detail.type == type) { 1.137 + passed = true; 1.138 + // Let gSearch respond to the event before continuing. 1.139 + executeSoon(() => deferred.resolve()); 1.140 + } 1.141 + } 1.142 + if (!passed) { 1.143 + info("Didn't get expected event, stopping the test"); 1.144 + getContentWindow().removeEventListener(SERVICE_EVENT_NAME, 1.145 + searchEventListener); 1.146 + // Set next() to a no-op so the test really does stop. 1.147 + TestRunner.next = function () {}; 1.148 + TestRunner.finish(); 1.149 + } 1.150 +} 1.151 + 1.152 +function $(idSuffix) { 1.153 + return getContentDocument().getElementById("newtab-search-" + idSuffix); 1.154 +} 1.155 + 1.156 +function promiseSearchEvents(events) { 1.157 + info("Expecting search events: " + events); 1.158 + events = events.map(e => ({ type: e, deferred: Promise.defer() })); 1.159 + gExpectedSearchEventQueue.push(...events); 1.160 + return Promise.all(events.map(e => e.deferred.promise)); 1.161 +} 1.162 + 1.163 +function promiseNewSearchEngine(withLogo) { 1.164 + let basename = withLogo ? ENGINE_LOGO : ENGINE_NO_LOGO; 1.165 + info("Waiting for engine to be added: " + basename); 1.166 + 1.167 + // Wait for the search events triggered by adding the new engine. 1.168 + // engine-added engine-loaded 1.169 + let expectedSearchEvents = ["State", "State"]; 1.170 + if (withLogo) { 1.171 + // an engine-changed for each of the two logos 1.172 + expectedSearchEvents.push("State", "State"); 1.173 + } 1.174 + let eventPromise = promiseSearchEvents(expectedSearchEvents); 1.175 + 1.176 + // Wait for addEngine(). 1.177 + let addDeferred = Promise.defer(); 1.178 + let url = getRootDirectory(gTestPath) + basename; 1.179 + Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, { 1.180 + onSuccess: function (engine) { 1.181 + info("Search engine added: " + basename); 1.182 + gNewEngines.push(engine); 1.183 + addDeferred.resolve(engine); 1.184 + }, 1.185 + onError: function (errCode) { 1.186 + ok(false, "addEngine failed with error code " + errCode); 1.187 + addDeferred.reject(); 1.188 + }, 1.189 + }); 1.190 + 1.191 + // Make a new promise that wraps the previous promises. The only point of 1.192 + // this is to pass the new engine to the yielder via deferred.resolve(), 1.193 + // which is a little nicer than passing an array whose first element is the 1.194 + // new engine. 1.195 + let deferred = Promise.defer(); 1.196 + Promise.all([addDeferred.promise, eventPromise]).then(values => { 1.197 + let newEngine = values[0]; 1.198 + deferred.resolve(newEngine); 1.199 + }, () => deferred.reject()); 1.200 + return deferred.promise; 1.201 +} 1.202 + 1.203 +function checkCurrentEngine(basename) { 1.204 + let engine = Services.search.currentEngine; 1.205 + ok(engine.name.contains(basename), 1.206 + "Sanity check: current engine: engine.name=" + engine.name + 1.207 + " basename=" + basename); 1.208 + 1.209 + // gSearch.currentEngineName 1.210 + is(gSearch().currentEngineName, engine.name, 1.211 + "currentEngineName: " + engine.name); 1.212 + 1.213 + // search bar logo 1.214 + let logoSize = [px * window.devicePixelRatio for (px of LOGO_LOW_DPI_SIZE)]; 1.215 + let logoURI = engine.getIconURLBySize(...logoSize); 1.216 + let logo = logoImg(); 1.217 + is(logo.hidden, !logoURI, 1.218 + "Logo should be visible iff engine has a logo: " + engine.name); 1.219 + if (logoURI) { 1.220 + is(logo.style.backgroundImage, 'url("' + logoURI + '")', "Logo URI"); 1.221 + } 1.222 + 1.223 + // "selected" attributes of engines in the panel 1.224 + let panel = searchPanel(); 1.225 + for (let engineBox of panel.childNodes) { 1.226 + let engineName = engineBox.getAttribute("engine"); 1.227 + if (engineName == engine.name) { 1.228 + is(engineBox.getAttribute("selected"), "true", 1.229 + "Engine box's selected attribute should be true for " + 1.230 + "selected engine: " + engineName); 1.231 + } 1.232 + else { 1.233 + ok(!engineBox.hasAttribute("selected"), 1.234 + "Engine box's selected attribute should be absent for " + 1.235 + "non-selected engine: " + engineName); 1.236 + } 1.237 + } 1.238 +} 1.239 + 1.240 +function promisePanelShown(panel) { 1.241 + let deferred = Promise.defer(); 1.242 + info("Waiting for popupshown"); 1.243 + panel.addEventListener("popupshown", function onEvent() { 1.244 + panel.removeEventListener("popupshown", onEvent); 1.245 + is(panel.state, "open", "Panel state"); 1.246 + executeSoon(() => deferred.resolve()); 1.247 + }); 1.248 + return deferred.promise; 1.249 +} 1.250 + 1.251 +function promiseClick(node) { 1.252 + let deferred = Promise.defer(); 1.253 + let win = getContentWindow(); 1.254 + SimpleTest.waitForFocus(() => { 1.255 + EventUtils.synthesizeMouseAtCenter(node, {}, win); 1.256 + deferred.resolve(); 1.257 + }, win); 1.258 + return deferred.promise; 1.259 +} 1.260 + 1.261 +function promiseManagerOpen() { 1.262 + info("Waiting for the search manager window to open..."); 1.263 + let deferred = Promise.defer(); 1.264 + let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]. 1.265 + getService(Ci.nsIWindowWatcher); 1.266 + winWatcher.registerNotification(function onWin(subj, topic, data) { 1.267 + if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) { 1.268 + subj.addEventListener("load", function onLoad() { 1.269 + subj.removeEventListener("load", onLoad); 1.270 + if (subj.document.documentURI == 1.271 + "chrome://browser/content/search/engineManager.xul") { 1.272 + winWatcher.unregisterNotification(onWin); 1.273 + ok(true, "Observed search manager window opened"); 1.274 + is(subj.opener, gWindow, 1.275 + "Search engine manager opener should be the chrome browser " + 1.276 + "window containing the newtab page"); 1.277 + executeSoon(() => { 1.278 + subj.close(); 1.279 + deferred.resolve(); 1.280 + }); 1.281 + } 1.282 + }); 1.283 + } 1.284 + }); 1.285 + return deferred.promise; 1.286 +} 1.287 + 1.288 +function searchPanel() { 1.289 + return $("panel"); 1.290 +} 1.291 + 1.292 +function logoImg() { 1.293 + return $("logo"); 1.294 +} 1.295 + 1.296 +function gSearch() { 1.297 + return getContentWindow().gSearch; 1.298 +}