michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: // See browser/components/search/test/browser_*_behavior.js for tests of actual michael@0: // searches. michael@0: michael@0: const ENGINE_LOGO = "searchEngineLogo.xml"; michael@0: const ENGINE_NO_LOGO = "searchEngineNoLogo.xml"; michael@0: michael@0: const SERVICE_EVENT_NAME = "ContentSearchService"; michael@0: michael@0: const LOGO_LOW_DPI_SIZE = [65, 26]; michael@0: const LOGO_HIGH_DPI_SIZE = [130, 52]; michael@0: michael@0: // The test has an expected search event queue and a search event listener. michael@0: // Search events that are expected to happen are added to the queue, and the michael@0: // listener consumes the queue and ensures that each event it receives is at michael@0: // the head of the queue. michael@0: // michael@0: // Each item in the queue is an object { type, deferred }. type is the michael@0: // expected search event type. deferred is a Promise.defer() value that is michael@0: // resolved when the event is consumed. michael@0: var gExpectedSearchEventQueue = []; michael@0: michael@0: var gNewEngines = []; michael@0: michael@0: function runTests() { michael@0: let oldCurrentEngine = Services.search.currentEngine; michael@0: michael@0: yield addNewTabPageTab(); michael@0: yield whenSearchInitDone(); michael@0: michael@0: // The tab is removed at the end of the test, so there's no need to remove michael@0: // this listener at the end of the test. michael@0: info("Adding search event listener"); michael@0: getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener); michael@0: michael@0: let panel = searchPanel(); michael@0: is(panel.state, "closed", "Search panel should be closed initially"); michael@0: michael@0: // The panel's animation often is not finished when the test clicks on panel michael@0: // children, which makes the test click the wrong children, so disable it. michael@0: panel.setAttribute("animate", "false"); michael@0: michael@0: // Add the two test engines. michael@0: let logoEngine = null; michael@0: yield promiseNewSearchEngine(true).then(engine => { michael@0: logoEngine = engine; michael@0: TestRunner.next(); michael@0: }); michael@0: ok(!!logoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE), michael@0: "Sanity check: engine should have 1x logo"); michael@0: ok(!!logoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE), michael@0: "Sanity check: engine should have 2x logo"); michael@0: michael@0: let noLogoEngine = null; michael@0: yield promiseNewSearchEngine(false).then(engine => { michael@0: noLogoEngine = engine; michael@0: TestRunner.next(); michael@0: }); michael@0: ok(!noLogoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE), michael@0: "Sanity check: engine should not have 1x logo"); michael@0: ok(!noLogoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE), michael@0: "Sanity check: engine should not have 2x logo"); michael@0: michael@0: // Use the search service to change the current engine to the logo engine. michael@0: Services.search.currentEngine = logoEngine; michael@0: yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); michael@0: checkCurrentEngine(ENGINE_LOGO); michael@0: michael@0: // Click the logo to open the search panel. michael@0: yield Promise.all([ michael@0: promisePanelShown(panel), michael@0: promiseClick(logoImg()), michael@0: ]).then(TestRunner.next); michael@0: michael@0: // In the search panel, click the no-logo engine. It should become the michael@0: // current engine. michael@0: let noLogoBox = null; michael@0: for (let box of panel.childNodes) { michael@0: if (box.getAttribute("engine") == noLogoEngine.name) { michael@0: noLogoBox = box; michael@0: break; michael@0: } michael@0: } michael@0: ok(noLogoBox, "Search panel should contain the no-logo engine"); michael@0: yield Promise.all([ michael@0: promiseSearchEvents(["CurrentEngine"]), michael@0: promiseClick(noLogoBox), michael@0: ]).then(TestRunner.next); michael@0: michael@0: checkCurrentEngine(ENGINE_NO_LOGO); michael@0: michael@0: // Switch back to the logo engine. michael@0: Services.search.currentEngine = logoEngine; michael@0: yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); michael@0: checkCurrentEngine(ENGINE_LOGO); michael@0: michael@0: // Open the panel again. michael@0: yield Promise.all([ michael@0: promisePanelShown(panel), michael@0: promiseClick(logoImg()), michael@0: ]).then(TestRunner.next); michael@0: michael@0: // In the search panel, click the Manage Engines box. michael@0: let manageBox = $("manage"); michael@0: ok(!!manageBox, "The Manage Engines box should be present in the document"); michael@0: yield Promise.all([ michael@0: promiseManagerOpen(), michael@0: promiseClick(manageBox), michael@0: ]).then(TestRunner.next); michael@0: michael@0: // Done. Revert the current engine and remove the new engines. michael@0: Services.search.currentEngine = oldCurrentEngine; michael@0: yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next); michael@0: michael@0: let events = []; michael@0: for (let engine of gNewEngines) { michael@0: Services.search.removeEngine(engine); michael@0: events.push("State"); michael@0: } michael@0: yield promiseSearchEvents(events).then(TestRunner.next); michael@0: } michael@0: michael@0: function searchEventListener(event) { michael@0: info("Got search event " + event.detail.type); michael@0: let passed = false; michael@0: let nonempty = gExpectedSearchEventQueue.length > 0; michael@0: ok(nonempty, "Expected search event queue should be nonempty"); michael@0: if (nonempty) { michael@0: let { type, deferred } = gExpectedSearchEventQueue.shift(); michael@0: is(event.detail.type, type, "Got expected search event " + type); michael@0: if (event.detail.type == type) { michael@0: passed = true; michael@0: // Let gSearch respond to the event before continuing. michael@0: executeSoon(() => deferred.resolve()); michael@0: } michael@0: } michael@0: if (!passed) { michael@0: info("Didn't get expected event, stopping the test"); michael@0: getContentWindow().removeEventListener(SERVICE_EVENT_NAME, michael@0: searchEventListener); michael@0: // Set next() to a no-op so the test really does stop. michael@0: TestRunner.next = function () {}; michael@0: TestRunner.finish(); michael@0: } michael@0: } michael@0: michael@0: function $(idSuffix) { michael@0: return getContentDocument().getElementById("newtab-search-" + idSuffix); michael@0: } michael@0: michael@0: function promiseSearchEvents(events) { michael@0: info("Expecting search events: " + events); michael@0: events = events.map(e => ({ type: e, deferred: Promise.defer() })); michael@0: gExpectedSearchEventQueue.push(...events); michael@0: return Promise.all(events.map(e => e.deferred.promise)); michael@0: } michael@0: michael@0: function promiseNewSearchEngine(withLogo) { michael@0: let basename = withLogo ? ENGINE_LOGO : ENGINE_NO_LOGO; michael@0: info("Waiting for engine to be added: " + basename); michael@0: michael@0: // Wait for the search events triggered by adding the new engine. michael@0: // engine-added engine-loaded michael@0: let expectedSearchEvents = ["State", "State"]; michael@0: if (withLogo) { michael@0: // an engine-changed for each of the two logos michael@0: expectedSearchEvents.push("State", "State"); michael@0: } michael@0: let eventPromise = promiseSearchEvents(expectedSearchEvents); michael@0: michael@0: // Wait for addEngine(). michael@0: let addDeferred = Promise.defer(); michael@0: let url = getRootDirectory(gTestPath) + basename; michael@0: Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, { michael@0: onSuccess: function (engine) { michael@0: info("Search engine added: " + basename); michael@0: gNewEngines.push(engine); michael@0: addDeferred.resolve(engine); michael@0: }, michael@0: onError: function (errCode) { michael@0: ok(false, "addEngine failed with error code " + errCode); michael@0: addDeferred.reject(); michael@0: }, michael@0: }); michael@0: michael@0: // Make a new promise that wraps the previous promises. The only point of michael@0: // this is to pass the new engine to the yielder via deferred.resolve(), michael@0: // which is a little nicer than passing an array whose first element is the michael@0: // new engine. michael@0: let deferred = Promise.defer(); michael@0: Promise.all([addDeferred.promise, eventPromise]).then(values => { michael@0: let newEngine = values[0]; michael@0: deferred.resolve(newEngine); michael@0: }, () => deferred.reject()); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function checkCurrentEngine(basename) { michael@0: let engine = Services.search.currentEngine; michael@0: ok(engine.name.contains(basename), michael@0: "Sanity check: current engine: engine.name=" + engine.name + michael@0: " basename=" + basename); michael@0: michael@0: // gSearch.currentEngineName michael@0: is(gSearch().currentEngineName, engine.name, michael@0: "currentEngineName: " + engine.name); michael@0: michael@0: // search bar logo michael@0: let logoSize = [px * window.devicePixelRatio for (px of LOGO_LOW_DPI_SIZE)]; michael@0: let logoURI = engine.getIconURLBySize(...logoSize); michael@0: let logo = logoImg(); michael@0: is(logo.hidden, !logoURI, michael@0: "Logo should be visible iff engine has a logo: " + engine.name); michael@0: if (logoURI) { michael@0: is(logo.style.backgroundImage, 'url("' + logoURI + '")', "Logo URI"); michael@0: } michael@0: michael@0: // "selected" attributes of engines in the panel michael@0: let panel = searchPanel(); michael@0: for (let engineBox of panel.childNodes) { michael@0: let engineName = engineBox.getAttribute("engine"); michael@0: if (engineName == engine.name) { michael@0: is(engineBox.getAttribute("selected"), "true", michael@0: "Engine box's selected attribute should be true for " + michael@0: "selected engine: " + engineName); michael@0: } michael@0: else { michael@0: ok(!engineBox.hasAttribute("selected"), michael@0: "Engine box's selected attribute should be absent for " + michael@0: "non-selected engine: " + engineName); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function promisePanelShown(panel) { michael@0: let deferred = Promise.defer(); michael@0: info("Waiting for popupshown"); michael@0: panel.addEventListener("popupshown", function onEvent() { michael@0: panel.removeEventListener("popupshown", onEvent); michael@0: is(panel.state, "open", "Panel state"); michael@0: executeSoon(() => deferred.resolve()); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseClick(node) { michael@0: let deferred = Promise.defer(); michael@0: let win = getContentWindow(); michael@0: SimpleTest.waitForFocus(() => { michael@0: EventUtils.synthesizeMouseAtCenter(node, {}, win); michael@0: deferred.resolve(); michael@0: }, win); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseManagerOpen() { michael@0: info("Waiting for the search manager window to open..."); michael@0: let deferred = Promise.defer(); michael@0: let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]. michael@0: getService(Ci.nsIWindowWatcher); michael@0: winWatcher.registerNotification(function onWin(subj, topic, data) { michael@0: if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) { michael@0: subj.addEventListener("load", function onLoad() { michael@0: subj.removeEventListener("load", onLoad); michael@0: if (subj.document.documentURI == michael@0: "chrome://browser/content/search/engineManager.xul") { michael@0: winWatcher.unregisterNotification(onWin); michael@0: ok(true, "Observed search manager window opened"); michael@0: is(subj.opener, gWindow, michael@0: "Search engine manager opener should be the chrome browser " + michael@0: "window containing the newtab page"); michael@0: executeSoon(() => { michael@0: subj.close(); michael@0: deferred.resolve(); michael@0: }); michael@0: } michael@0: }); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function searchPanel() { michael@0: return $("panel"); michael@0: } michael@0: michael@0: function logoImg() { michael@0: return $("logo"); michael@0: } michael@0: michael@0: function gSearch() { michael@0: return getContentWindow().gSearch; michael@0: }