From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../unit/test_quicksuggest_bestMatch.js | 463 +++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js (limited to 'browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js') diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js new file mode 100644 index 0000000000..853073a6c0 --- /dev/null +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js @@ -0,0 +1,463 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Tests best match quick suggest results. "Best match" refers to two different +// concepts: +// +// (1) The "best match" UI treatment (labeled "top pick" in the UI) that makes a +// result's row larger than usual and sets `suggestedIndex` to 1. +// (2) The quick suggest config in remote settings can contain a `best_match` +// object that tells Firefox to use the best match UI treatment if the +// user's search string is a certain length. +// +// This file tests aspects of both concepts. +// +// See also test_quicksuggest_topPicks.js. "Top picks" refer to a similar +// concept but it is not related to (2). + +"use strict"; + +const MAX_RESULT_COUNT = UrlbarPrefs.get("maxRichResults"); + +// This search string length needs to be >= 4 to trigger its suggestion as a +// best match instead of a usual quick suggest. +const BEST_MATCH_POSITION_SEARCH_STRING = "bestmatchposition"; +const BEST_MATCH_POSITION = Math.round(MAX_RESULT_COUNT / 2); + +const REMOTE_SETTINGS_RESULTS = [ + { + id: 1, + url: "http://example.com/", + title: "Fullkeyword title", + keywords: [ + "fu", + "ful", + "full", + "fullk", + "fullke", + "fullkey", + "fullkeyw", + "fullkeywo", + "fullkeywor", + "fullkeyword", + ], + click_url: "http://example.com/click", + impression_url: "http://example.com/impression", + advertiser: "TestAdvertiser", + }, + { + id: 2, + url: "http://example.com/best-match-position", + title: `${BEST_MATCH_POSITION_SEARCH_STRING} title`, + keywords: [BEST_MATCH_POSITION_SEARCH_STRING], + click_url: "http://example.com/click", + impression_url: "http://example.com/impression", + advertiser: "TestAdvertiser", + position: BEST_MATCH_POSITION, + }, +]; + +const EXPECTED_BEST_MATCH_URLBAR_RESULT = { + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + isBestMatch: true, + payload: { + telemetryType: "adm_sponsored", + url: "http://example.com/", + originalUrl: "http://example.com/", + title: "Fullkeyword title", + icon: null, + isSponsored: true, + sponsoredImpressionUrl: "http://example.com/impression", + sponsoredClickUrl: "http://example.com/click", + sponsoredBlockId: 1, + sponsoredAdvertiser: "TestAdvertiser", + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-learn-more-about-firefox-suggest" + : "firefox-suggest-urlbar-learn-more", + }, + isBlockable: UrlbarPrefs.get("bestMatchBlockingEnabled"), + blockL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-dismiss-firefox-suggest" + : "firefox-suggest-urlbar-block", + }, + displayUrl: "http://example.com", + source: "remote-settings", + }, +}; + +const EXPECTED_NON_BEST_MATCH_URLBAR_RESULT = { + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + telemetryType: "adm_sponsored", + url: "http://example.com/", + originalUrl: "http://example.com/", + title: "Fullkeyword title", + qsSuggestion: "fullkeyword", + icon: null, + isSponsored: true, + sponsoredImpressionUrl: "http://example.com/impression", + sponsoredClickUrl: "http://example.com/click", + sponsoredBlockId: 1, + sponsoredAdvertiser: "TestAdvertiser", + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-learn-more-about-firefox-suggest" + : "firefox-suggest-urlbar-learn-more", + }, + isBlockable: UrlbarPrefs.get("quickSuggestBlockingEnabled"), + blockL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-dismiss-firefox-suggest" + : "firefox-suggest-urlbar-block", + }, + displayUrl: "http://example.com", + source: "remote-settings", + }, +}; + +const EXPECTED_BEST_MATCH_POSITION_URLBAR_RESULT = { + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + isBestMatch: true, + payload: { + telemetryType: "adm_sponsored", + url: "http://example.com/best-match-position", + originalUrl: "http://example.com/best-match-position", + title: `${BEST_MATCH_POSITION_SEARCH_STRING} title`, + icon: null, + isSponsored: true, + sponsoredImpressionUrl: "http://example.com/impression", + sponsoredClickUrl: "http://example.com/click", + sponsoredBlockId: 2, + sponsoredAdvertiser: "TestAdvertiser", + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-learn-more-about-firefox-suggest" + : "firefox-suggest-urlbar-learn-more", + }, + isBlockable: UrlbarPrefs.get("bestMatchBlockingEnabled"), + blockL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-dismiss-firefox-suggest" + : "firefox-suggest-urlbar-block", + }, + displayUrl: "http://example.com/best-match-position", + source: "remote-settings", + }, +}; + +add_task(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("bestMatch.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("suggest.bestmatch", true); + + // Disable search suggestions so we don't hit the network. + Services.prefs.setBoolPref("browser.search.suggest.enabled", false); + + await QuickSuggestTestUtils.ensureQuickSuggestInit({ + remoteSettingsResults: [ + { + type: "data", + attachment: REMOTE_SETTINGS_RESULTS, + }, + ], + config: QuickSuggestTestUtils.BEST_MATCH_CONFIG, + }); +}); + +// Tests a best match result. +add_task(async function bestMatch() { + let context = createContext("fullkeyword", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_BEST_MATCH_URLBAR_RESULT], + }); + + let result = context.results[0]; + + // The title should not include the full keyword and em dash, and the part of + // the title that the search string matches should be highlighted. + Assert.equal(result.title, "Fullkeyword title", "result.title"); + Assert.deepEqual( + result.titleHighlights, + [[0, "fullkeyword".length]], + "result.titleHighlights" + ); + + Assert.equal(result.suggestedIndex, 1, "result.suggestedIndex"); + Assert.equal( + !!result.isSuggestedIndexRelativeToGroup, + false, + "result.isSuggestedIndexRelativeToGroup" + ); +}); + +// Tests a usual, non-best match quick suggest result. +add_task(async function nonBestMatch() { + // Search for a substring of the full search string so we can test title + // highlights. + let context = createContext("fu", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_NON_BEST_MATCH_URLBAR_RESULT], + }); + + let result = context.results[0]; + + // The title should include the full keyword and em dash, and the part of the + // title that the search string does not match should be highlighted. + Assert.equal(result.title, "fullkeyword — Fullkeyword title", "result.title"); + Assert.deepEqual( + result.titleHighlights, + [["fu".length, "fullkeyword".length - "fu".length]], + "result.titleHighlights" + ); + + Assert.equal(result.suggestedIndex, -1, "result.suggestedIndex"); + Assert.equal( + result.isSuggestedIndexRelativeToGroup, + true, + "result.isSuggestedIndexRelativeToGroup" + ); +}); + +// Tests prefix keywords leading up to a best match. +add_task(async function prefixKeywords() { + let sawNonBestMatch = false; + let sawBestMatch = false; + for (let keyword of REMOTE_SETTINGS_RESULTS[0].keywords) { + info(`Searching for "${keyword}"`); + let context = createContext(keyword, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + + let expectedResult; + if (keyword.length < 4) { + expectedResult = EXPECTED_NON_BEST_MATCH_URLBAR_RESULT; + sawNonBestMatch = true; + } else { + expectedResult = EXPECTED_BEST_MATCH_URLBAR_RESULT; + sawBestMatch = true; + } + + await check_results({ + context, + matches: [expectedResult], + }); + } + + Assert.ok(sawNonBestMatch, "Sanity check: Saw a non-best match"); + Assert.ok(sawBestMatch, "Sanity check: Saw a best match"); +}); + +// When tab-to-search is shown in the same search, both it and the best match +// will have a `suggestedIndex` value of 1. The TTS should appear first. +add_task(async function tabToSearch() { + // Disable tab-to-search onboarding results so we get a regular TTS result, + // which we can test a little more easily with `makeSearchResult()`. + UrlbarPrefs.set("tabToSearch.onboard.interactionsLeft", 0); + + // Install a test engine. The main part of its domain name needs to match the + // best match result too so we can trigger both its TTS and the best match. + let engineURL = "https://foo.fullkeyword.com/"; + let extension = await SearchTestUtils.installSearchExtension( + { + name: "Test", + search_url: engineURL, + }, + { skipUnload: true } + ); + let engine = Services.search.getEngineByName("Test"); + + // Also need to add a visit to trigger TTS. + await PlacesTestUtils.addVisits(engineURL); + + let context = createContext("fullkeyword", { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + // search heuristic + makeSearchResult(context, { + engineName: Services.search.defaultEngine.name, + engineIconUri: Services.search.defaultEngine.iconURI?.spec, + heuristic: true, + }), + // tab to search + makeSearchResult(context, { + engineName: engine.name, + engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS, + uri: UrlbarUtils.stripPublicSuffixFromHost(engine.searchUrlDomain), + providesSearchMode: true, + query: "", + providerName: "TabToSearch", + satisfiesAutofillThreshold: true, + }), + // best match + EXPECTED_BEST_MATCH_URLBAR_RESULT, + // visit + makeVisitResult(context, { + uri: engineURL, + title: `test visit for ${engineURL}`, + }), + ], + }); + + await cleanupPlaces(); + await extension.unload(); + + UrlbarPrefs.clear("tabToSearch.onboard.interactionsLeft"); +}); + +// When the best match feature gate is disabled, quick suggest results should be +// shown as the usual non-best match results. +add_task(async function disabled_featureGate() { + UrlbarPrefs.set("bestMatch.enabled", false); + await doDisabledTest(); + UrlbarPrefs.set("bestMatch.enabled", true); +}); + +// When the best match suggestions are disabled, quick suggest results should be +// shown as the usual non-best match results. +add_task(async function disabled_suggestions() { + UrlbarPrefs.set("suggest.bestmatch", false); + await doDisabledTest(); + UrlbarPrefs.set("suggest.bestmatch", true); +}); + +// When best match is disabled, quick suggest results should be shown as the +// usual, non-best match results. +async function doDisabledTest() { + let context = createContext("fullkeywor", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_NON_BEST_MATCH_URLBAR_RESULT], + }); + + let result = context.results[0]; + + // The title should include the full keyword and em dash, and the part of the + // title that the search string does not match should be highlighted. + Assert.equal(result.title, "fullkeyword — Fullkeyword title", "result.title"); + Assert.deepEqual( + result.titleHighlights, + [["fullkeywor".length, 1]], + "result.titleHighlights" + ); + + Assert.equal(result.suggestedIndex, -1, "result.suggestedIndex"); + Assert.equal( + result.isSuggestedIndexRelativeToGroup, + true, + "result.isSuggestedIndexRelativeToGroup" + ); +} + +// `suggestion.position` should be ignored when the suggestion is a best match. +add_task(async function position() { + Assert.greater( + BEST_MATCH_POSITION, + 1, + "Precondition: `suggestion.position` > the best match index" + ); + + UrlbarPrefs.set("quicksuggest.allowPositionInSuggestions", true); + + let context = createContext(BEST_MATCH_POSITION_SEARCH_STRING, { + isPrivate: false, + }); + + // Add some visits to fill up the view. + let maxResultCount = UrlbarPrefs.get("maxRichResults"); + let visitResults = []; + for (let i = 0; i < maxResultCount; i++) { + let url = `http://example.com/${BEST_MATCH_POSITION_SEARCH_STRING}-${i}`; + await PlacesTestUtils.addVisits(url); + visitResults.unshift( + makeVisitResult(context, { + uri: url, + title: `test visit for ${url}`, + }) + ); + } + + // Do a search. + await check_results({ + context, + matches: [ + // search heuristic + makeSearchResult(context, { + engineName: Services.search.defaultEngine.name, + engineIconUri: Services.search.defaultEngine.iconURI?.spec, + heuristic: true, + }), + // best match whose backing suggestion has a `position` + EXPECTED_BEST_MATCH_POSITION_URLBAR_RESULT, + // visits + ...visitResults.slice(0, MAX_RESULT_COUNT - 2), + ], + }); + + await cleanupPlaces(); + UrlbarPrefs.clear("quicksuggest.allowPositionInSuggestions"); +}); + +// Tests a suggestion that is blocked from being a best match. +add_task(async function blockedAsBestMatch() { + let config = QuickSuggestTestUtils.BEST_MATCH_CONFIG; + config.best_match.blocked_suggestion_ids = [1]; + await QuickSuggestTestUtils.withConfig({ + config, + callback: async () => { + let context = createContext("fullkeyword", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_NON_BEST_MATCH_URLBAR_RESULT], + }); + }, + }); +}); + +// Tests without a best_match config to make sure nothing breaks. +add_task(async function noConfig() { + await QuickSuggestTestUtils.withConfig({ + config: {}, + callback: async () => { + let context = createContext("fullkeyword", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_NON_BEST_MATCH_URLBAR_RESULT], + }); + }, + }); +}); -- cgit v1.2.3