summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js')
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js516
1 files changed, 516 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js b/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js
new file mode 100644
index 0000000000..3513fd2dac
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js
@@ -0,0 +1,516 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that heuristic results are updated/restyled to search results when a
+ * one-off is selected.
+ */
+
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "oneOffSearchButtons", () => {
+ return UrlbarTestUtils.getOneOffSearchButtons(window);
+});
+
+const TEST_DEFAULT_ENGINE_NAME = "Test";
+
+const HISTORY_URL = "https://mozilla.org/";
+
+const KEYWORD = "kw";
+const KEYWORD_URL = "https://mozilla.org/search?q=%s";
+
+// Expected result data for our test results.
+const RESULT_DATA_BY_TYPE = {
+ [UrlbarUtils.RESULT_TYPE.URL]: {
+ icon: `page-icon:${HISTORY_URL}`,
+ actionL10n: {
+ id: "urlbar-result-action-visit",
+ },
+ },
+ [UrlbarUtils.RESULT_TYPE.SEARCH]: {
+ icon: "chrome://global/skin/icons/search-glass.svg",
+ actionL10n: {
+ id: "urlbar-result-action-search-w-engine",
+ args: { engine: TEST_DEFAULT_ENGINE_NAME },
+ },
+ },
+ [UrlbarUtils.RESULT_TYPE.KEYWORD]: {
+ icon: `page-icon:${KEYWORD_URL}`,
+ },
+};
+
+function getSourceIcon(source) {
+ switch (source) {
+ case UrlbarUtils.RESULT_SOURCE.BOOKMARKS:
+ return "chrome://browser/skin/bookmark.svg";
+ case UrlbarUtils.RESULT_SOURCE.HISTORY:
+ return "chrome://browser/skin/history.svg";
+ case UrlbarUtils.RESULT_SOURCE.TABS:
+ return "chrome://browser/skin/tab.svg";
+ default:
+ return null;
+ }
+}
+
+/**
+ * Asserts that the heuristic result is *not* restyled to look like a search
+ * result.
+ *
+ * @param {UrlbarUtils.RESULT_TYPE} expectedType
+ * The expected type of the heuristic.
+ * @param {object} resultDetails
+ * The return value of UrlbarTestUtils.getDetailsOfResultAt(window, 0).
+ */
+async function heuristicIsNotRestyled(expectedType, resultDetails) {
+ Assert.equal(
+ resultDetails.type,
+ expectedType,
+ "The restyled result is the expected type."
+ );
+
+ Assert.equal(
+ resultDetails.displayed.title,
+ resultDetails.title,
+ "The displayed title is equal to the payload title."
+ );
+
+ let data = RESULT_DATA_BY_TYPE[expectedType];
+ Assert.ok(data, "Sanity check: Expected type is recognized");
+
+ let [actionText] = data.actionL10n
+ ? await document.l10n.formatValues([data.actionL10n])
+ : [""];
+
+ if (
+ expectedType === UrlbarUtils.RESULT_TYPE.URL &&
+ resultDetails.result.heuristic &&
+ resultDetails.result.payload.title
+ ) {
+ Assert.equal(
+ resultDetails.displayed.url,
+ resultDetails.result.payload.displayUrl
+ );
+ } else {
+ Assert.equal(
+ resultDetails.displayed.action,
+ actionText,
+ "The result has the expected non-styled action text."
+ );
+ }
+
+ Assert.equal(
+ BrowserTestUtils.is_visible(resultDetails.element.separator),
+ !!actionText,
+ "The title separator is " + (actionText ? "visible" : "hidden")
+ );
+ Assert.equal(
+ BrowserTestUtils.is_visible(resultDetails.element.action),
+ !!actionText,
+ "The action text is " + (actionText ? "visible" : "hidden")
+ );
+
+ Assert.equal(
+ resultDetails.image,
+ data.icon,
+ "The result has the expected non-styled icon."
+ );
+}
+
+/**
+ * Asserts that the heuristic result is restyled to look like a search result.
+ *
+ * @param {UrlbarUtils.RESULT_TYPE} expectedType
+ * The expected type of the heuristic.
+ * @param {object} resultDetails
+ * The return value of UrlbarTestUtils.getDetailsOfResultAt(window, 0).
+ * @param {string} searchString
+ * The current search string. The restyled heuristic result's title is
+ * expected to be this string.
+ * @param {element} selectedOneOff
+ * The selected one-off button.
+ */
+async function heuristicIsRestyled(
+ expectedType,
+ resultDetails,
+ searchString,
+ selectedOneOff
+) {
+ let engine = selectedOneOff.engine;
+ let source = selectedOneOff.source;
+ if (!engine && !source) {
+ Assert.ok(false, "An invalid one-off was passed to urlbarResultIsRestyled");
+ return;
+ }
+ Assert.equal(
+ resultDetails.type,
+ expectedType,
+ "The restyled result is still the expected type."
+ );
+
+ let actionText;
+ if (engine) {
+ [actionText] = await document.l10n.formatValues([
+ {
+ id: "urlbar-result-action-search-w-engine",
+ args: { engine: engine.name },
+ },
+ ]);
+ } else if (source) {
+ [actionText] = await document.l10n.formatValues([
+ {
+ id: `urlbar-result-action-search-${UrlbarUtils.getResultSourceName(
+ source
+ )}`,
+ },
+ ]);
+ }
+ Assert.equal(
+ resultDetails.displayed.action,
+ actionText,
+ "Restyled result's action text should be updated"
+ );
+
+ Assert.equal(
+ resultDetails.displayed.title,
+ searchString,
+ "The restyled result's title should be equal to the search string."
+ );
+
+ Assert.ok(
+ BrowserTestUtils.is_visible(resultDetails.element.separator),
+ "The restyled result's title separator should be visible"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(resultDetails.element.action),
+ "The restyled result's action text should be visible"
+ );
+
+ if (engine) {
+ Assert.equal(
+ resultDetails.image,
+ engine.iconURI?.spec || UrlbarUtils.ICON.SEARCH_GLASS,
+ "The restyled result's icon should be the engine's icon."
+ );
+ } else if (source) {
+ Assert.equal(
+ resultDetails.image,
+ getSourceIcon(source),
+ "The restyled result's icon should be the local one-off's icon."
+ );
+ }
+}
+
+/**
+ * Asserts that the specified one-off (if any) is selected and that the
+ * heuristic result is either restyled or not restyled as appropriate. If
+ * there's a selected one-off, then the heuristic is expected to be restyled; if
+ * there's no selected one-off, then it's expected not to be restyled.
+ *
+ * @param {string} searchString
+ * The current search string. If a one-off is selected, then the restyled
+ * heuristic result's title is expected to be this string.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The expected type of the heuristic.
+ * @param {number} expectedSelectedOneOffIndex
+ * The index of the expected selected one-off button. If no one-off is
+ * expected to be selected, then pass -1.
+ */
+async function assertState(
+ searchString,
+ expectedHeuristicType,
+ expectedSelectedOneOffIndex
+) {
+ Assert.equal(
+ oneOffSearchButtons.selectedButtonIndex,
+ expectedSelectedOneOffIndex,
+ "Expected one-off should be selected"
+ );
+
+ let resultDetails = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ if (expectedSelectedOneOffIndex >= 0) {
+ await heuristicIsRestyled(
+ expectedHeuristicType,
+ resultDetails,
+ searchString,
+ oneOffSearchButtons.selectedButton
+ );
+ } else {
+ await heuristicIsNotRestyled(expectedHeuristicType, resultDetails);
+ }
+}
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: TEST_DEFAULT_ENGINE_NAME,
+ keyword: "@test",
+ },
+ { setAsDefault: true }
+ );
+ let engine = Services.search.getEngineByName(TEST_DEFAULT_ENGINE_NAME);
+ await Services.search.moveEngine(engine, 0);
+
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(HISTORY_URL);
+ }
+
+ await PlacesUtils.keywords.insert({
+ keyword: KEYWORD,
+ url: KEYWORD_URL,
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.keywords.remove(KEYWORD);
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+
+ // Move the mouse away from the view so that a result or one-off isn't
+ // inadvertently highlighted. See bug 1659011.
+ EventUtils.synthesizeMouse(
+ gURLBar.inputField,
+ 0,
+ 0,
+ { type: "mousemove" },
+ window
+ );
+});
+
+add_task(async function arrow_engine_url() {
+ await doArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, false);
+});
+
+add_task(async function arrow_engine_search() {
+ await doArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, false);
+});
+
+add_task(async function arrow_engine_keyword() {
+ await doArrowTest(`${KEYWORD} test`, UrlbarUtils.RESULT_TYPE.KEYWORD, false);
+});
+
+add_task(async function arrow_local_url() {
+ await doArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, true);
+});
+
+add_task(async function arrow_local_search() {
+ await doArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, true);
+});
+
+add_task(async function arrow_local_keyword() {
+ await doArrowTest(`${KEYWORD} test`, UrlbarUtils.RESULT_TYPE.KEYWORD, true);
+});
+
+/**
+ * Arrows down to the one-offs, checks the heuristic, and clicks it.
+ *
+ * @param {string} searchString
+ * The search string to use.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The type of heuristic result that the search string is expected to trigger.
+ * @param {boolean} useLocal
+ * Whether to test a local one-off or an engine one-off. If true, test a
+ * local one-off. If false, test an engine one-off.
+ */
+async function doArrowTest(searchString, expectedHeuristicType, useLocal) {
+ await doTest(searchString, expectedHeuristicType, useLocal, async () => {
+ info(
+ "Arrow down to the one-offs, observe heuristic is restyled as a search result."
+ );
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: resultCount });
+ await searchPromise;
+ await assertState(searchString, expectedHeuristicType, 0);
+
+ let depth = 1;
+ if (useLocal) {
+ for (; !oneOffSearchButtons.selectedButton.source; depth++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ Assert.ok(
+ oneOffSearchButtons.selectedButton.source,
+ "Selected one-off is local"
+ );
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ }
+
+ info(
+ "Arrow up out of the one-offs, observe heuristic styling is restored."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp", { repeat: depth });
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ info(
+ "Arrow back down into the one-offs, observe heuristic is restyled as a search result."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: depth });
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ });
+}
+
+add_task(async function altArrow_engine_url() {
+ await doAltArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, false);
+});
+
+add_task(async function altArrow_engine_search() {
+ await doAltArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, false);
+});
+
+add_task(async function altArrow_engine_keyword() {
+ await doAltArrowTest(
+ `${KEYWORD} test`,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ false
+ );
+});
+
+add_task(async function altArrow_local_url() {
+ await doAltArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, true);
+});
+
+add_task(async function altArrow_local_search() {
+ await doAltArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, true);
+});
+
+add_task(async function altArrow_local_keyword() {
+ await doAltArrowTest(
+ `${KEYWORD} test`,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ true
+ );
+});
+
+/**
+ * Alt-arrows down to the one-offs so that the heuristic remains selected,
+ * checks the heuristic, and clicks it.
+ *
+ * @param {string} searchString
+ * The search string to use.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The type of heuristic result that the search string is expected to trigger.
+ * @param {boolean} useLocal
+ * Whether to test a local one-off or an engine one-off. If true, test a
+ * local one-off. If false, test an engine one-off.
+ */
+async function doAltArrowTest(searchString, expectedHeuristicType, useLocal) {
+ await doTest(searchString, expectedHeuristicType, useLocal, async () => {
+ info(
+ "Alt+down into the one-offs, observe heuristic is restyled as a search result."
+ );
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await searchPromise;
+ await assertState(searchString, expectedHeuristicType, 0);
+
+ let depth = 1;
+ if (useLocal) {
+ for (; !oneOffSearchButtons.selectedButton.source; depth++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+ Assert.ok(
+ oneOffSearchButtons.selectedButton.source,
+ "Selected one-off is local"
+ );
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ }
+
+ info(
+ "Arrow down and then up to re-select the heuristic, observe its styling is restored."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ info(
+ "Alt+down into the one-offs, observe the heuristic is restyled as a search result."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: depth });
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+
+ info("Alt+up out of the one-offs, observe the heuristic is restored.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true, repeat: depth });
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ info(
+ "Alt+down into the one-offs, observe the heuristic is restyled as a search result."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: depth });
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ });
+}
+
+/**
+ * The main test function. Starts a search, asserts that the heuristic has the
+ * expected type, calls a callback to run more checks, and then finally clicks
+ * the restyled heuristic to make sure search mode is confirmed.
+ *
+ * @param {string} searchString
+ * The search string to use.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The type of heuristic result that the search string is expected to trigger.
+ * @param {boolean} useLocal
+ * Whether to test a local one-off or an engine one-off. If true, test a
+ * local one-off. If false, test an engine one-off.
+ * @param {Function} callback
+ * This is called after the search completes. It should perform whatever
+ * checks are necessary for the test task. Important: When it returns, it
+ * should make sure that the first one-off is selected.
+ */
+async function doTest(searchString, expectedHeuristicType, useLocal, callback) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ await TestUtils.waitForCondition(
+ () => !oneOffSearchButtons._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(result.heuristic, "First result is heuristic");
+ Assert.equal(
+ result.type,
+ expectedHeuristicType,
+ "Heuristic is expected type"
+ );
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ await callback();
+
+ Assert.ok(
+ oneOffSearchButtons.selectedButton,
+ "The callback should leave a one-off selected so that the heuristic remains re-styled"
+ );
+
+ info("Click the heuristic result and observe it confirms search mode.");
+ let selectedButton = oneOffSearchButtons.selectedButton;
+ let expectedSearchMode = {
+ entry: "oneoff",
+ isPreview: true,
+ };
+ if (useLocal) {
+ expectedSearchMode.source = selectedButton.source;
+ } else {
+ expectedSearchMode.engineName = selectedButton.engine.name;
+ }
+
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ let heuristicRow = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 0
+ );
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(heuristicRow, {});
+ await searchPromise;
+
+ expectedSearchMode.isPreview = false;
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+}