/* 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 Merino integration with UrlbarProviderQuickSuggest. "use strict"; ChromeUtils.defineESModuleGetters(this, { AmpSuggestions: "moz-src:///browser/components/urlbar/private/AmpSuggestions.sys.mjs", }); const SEARCH_STRING = "frab"; const { DEFAULT_SUGGESTION_SCORE } = UrlbarProviderQuickSuggest; const { TIMESTAMP_TEMPLATE } = AmpSuggestions; const REMOTE_SETTINGS_RESULTS = [ QuickSuggestTestUtils.ampRemoteSettings({ keywords: [SEARCH_STRING], }), ]; const EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT = QuickSuggestTestUtils.ampResult({ keyword: SEARCH_STRING, suggestedIndex: -1, }); const EXPECTED_MERINO_URLBAR_RESULT = QuickSuggestTestUtils.ampResult({ source: "merino", provider: "adm", requestId: "request_id", suggestedIndex: -1, }); add_setup(async () => { await MerinoTestUtils.server.start(); // Set up the remote settings client with the test data. await QuickSuggestTestUtils.ensureQuickSuggestInit({ prefs: [ ["suggest.quicksuggest.all", true], ["suggest.quicksuggest.sponsored", true], ["quicksuggest.ampTopPickCharThreshold", 0], ], }); await resetRemoteSettingsData(); Assert.equal( typeof DEFAULT_SUGGESTION_SCORE, "number", "Sanity check: DEFAULT_SUGGESTION_SCORE is defined" ); }); // Tests with the Merino endpoint URL set to an empty string, which disables // fetching from Merino. add_task(async function merinoDisabled() { let mockEndpointUrl = UrlbarPrefs.get("merino.endpointURL"); UrlbarPrefs.set("merino.endpointURL", ""); UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); // Clear the remote settings suggestions so that if Merino is actually queried // -- which would be a bug -- we don't accidentally mask the Merino suggestion // by also matching an RS suggestion with the same or higher score. await QuickSuggestTestUtils.setRemoteSettingsRecords([]); let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [], }); UrlbarPrefs.set("merino.endpointURL", mockEndpointUrl); await resetRemoteSettingsData(); }); // Results should be fetched from Merino only when online is both available and // enabled. add_task(async function onlineAvailableAndEnabled() { // Clear the remote settings suggestions so that if Merino is actually queried // -- which would be a bug -- we don't accidentally mask the Merino suggestion // by also matching an RS suggestion with the same or higher score. await QuickSuggestTestUtils.setRemoteSettingsRecords([]); for (let onlineAvailable of [false, true]) { for (let onlineEnabled of [false, true]) { UrlbarPrefs.set("quicksuggest.online.available", onlineAvailable); UrlbarPrefs.set("quicksuggest.online.enabled", onlineEnabled); await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: onlineAvailable && onlineEnabled ? [EXPECTED_MERINO_URLBAR_RESULT] : [], }); } } await resetRemoteSettingsData(); }); // When the Merino suggestion has a higher score than the remote settings // suggestion, the Merino suggestion should be used. add_task(async function higherScore() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); MerinoTestUtils.server.response.body.suggestions[0].score = 2 * DEFAULT_SUGGESTION_SCORE; let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [EXPECTED_MERINO_URLBAR_RESULT], }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // When the Merino suggestion has a lower score than the remote settings // suggestion, the remote settings suggestion should be used. add_task(async function lowerScore() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); MerinoTestUtils.server.response.body.suggestions[0].score = DEFAULT_SUGGESTION_SCORE / 2; let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // When remote settings doesn't return a suggestion but Merino does, the Merino // suggestion should be used. add_task(async function noSuggestion_remoteSettings() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); let context = createContext("this doesn't match remote settings", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [EXPECTED_MERINO_URLBAR_RESULT], }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // When Merino doesn't return a suggestion but remote settings does, the remote // settings suggestion should be used. add_task(async function noSuggestion_merino() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); MerinoTestUtils.server.response.body.suggestions = []; let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // When Merino returns multiple suggestions, the one with the largest score // should be used. add_task(async function multipleMerinoSuggestions() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); MerinoTestUtils.server.response.body.suggestions = [ { provider: "adm", full_keyword: "multipleMerinoSuggestions 0 full_keyword", title: "multipleMerinoSuggestions 0 title", url: "multipleMerinoSuggestions 0 url", icon: "multipleMerinoSuggestions 0 icon", impression_url: "multipleMerinoSuggestions 0 impression_url", click_url: "multipleMerinoSuggestions 0 click_url", block_id: 0, advertiser: "multipleMerinoSuggestions 0 advertiser", iab_category: "22 - Shopping", is_sponsored: true, score: 0.1, }, { provider: "adm", full_keyword: "multipleMerinoSuggestions 1 full_keyword", title: "multipleMerinoSuggestions 1 title", url: "multipleMerinoSuggestions 1 url", icon: "multipleMerinoSuggestions 1 icon", impression_url: "multipleMerinoSuggestions 1 impression_url", click_url: "multipleMerinoSuggestions 1 click_url", block_id: 1, advertiser: "multipleMerinoSuggestions 1 advertiser", iab_category: "22 - Shopping", is_sponsored: true, score: 1, }, { provider: "adm", full_keyword: "multipleMerinoSuggestions 2 full_keyword", title: "multipleMerinoSuggestions 2 title", url: "multipleMerinoSuggestions 2 url", icon: "multipleMerinoSuggestions 2 icon", impression_url: "multipleMerinoSuggestions 2 impression_url", click_url: "multipleMerinoSuggestions 2 click_url", block_id: 2, advertiser: "multipleMerinoSuggestions 2 advertiser", iab_category: "22 - Shopping", is_sponsored: true, score: 0.2, }, ]; let context = createContext("test", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [ QuickSuggestTestUtils.ampResult({ keyword: "multipleMerinoSuggestions 1 full_keyword", title: "multipleMerinoSuggestions 1 title", url: "multipleMerinoSuggestions 1 url", originalUrl: "multipleMerinoSuggestions 1 url", icon: "multipleMerinoSuggestions 1 icon", impressionUrl: "multipleMerinoSuggestions 1 impression_url", clickUrl: "multipleMerinoSuggestions 1 click_url", blockId: 1, advertiser: "multipleMerinoSuggestions 1 advertiser", requestId: "request_id", source: "merino", provider: "adm", suggestedIndex: -1, }), ], }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Timestamp templates in URLs should be replaced with real timestamps. add_task(async function timestamps() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); // Set up the Merino response with template URLs. let suggestion = MerinoTestUtils.server.response.body.suggestions[0]; suggestion.url = `http://example.com/time-${TIMESTAMP_TEMPLATE}`; suggestion.click_url = `http://example.com/time-${TIMESTAMP_TEMPLATE}-foo`; // Do a search. let context = createContext("test", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); let controller = UrlbarTestUtils.newMockController({ input: { isPrivate: context.isPrivate, onFirstResult() { return false; }, getSearchSource() { return "dummy-search-source"; }, window: { location: { href: AppConstants.BROWSER_CHROME_URL, }, }, }, }); await controller.startQuery(context); // Should be one quick suggest result. Assert.equal(context.results.length, 1, "One result returned"); let result = context.results[0]; QuickSuggestTestUtils.assertTimestampsReplaced(result, { url: suggestion.click_url, sponsoredClickUrl: suggestion.click_url, }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Tests dismissals of managed Merino suggestions (suggestions that are managed // by a `SuggestFeature`). add_task(async function dismissals_managed() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); // Set up a single Merino AMP suggestion with a unique URL. let url = "https://example.com/merino-amp-url"; MerinoTestUtils.server.response = MerinoTestUtils.server.makeDefaultResponse(); MerinoTestUtils.server.response.body.suggestions[0].url = url; let expectedMerinoResult = QuickSuggestTestUtils.ampResult({ url, source: "merino", provider: "adm", requestId: "request_id", suggestedIndex: -1, }); // Do a search. The Merino suggestion should be matched. const context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [expectedMerinoResult], }); let result = context.results[0]; Assert.ok( QuickSuggest.getFeatureByResult(result), "Sanity check: The actual result should be managed by a feature" ); // Dismiss the Merino result. await QuickSuggest.dismissResult(result); Assert.ok( await QuickSuggest.isResultDismissed(result), "isResultDismissed should return true after dismissing result" ); // Do another search. The remote settings suggestion should now be matched. await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], }); // Clear dismissals. await QuickSuggest.clearDismissedSuggestions(); Assert.ok( !(await QuickSuggest.isResultDismissed(result)), "isResultDismissed should return false after clearing dismissals" ); // The Merino suggestion should be matched again. await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [expectedMerinoResult], }); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Tests dismissals of Merino AMP suggestions, which have special handling // around their dismissal keys. add_task(async function dismissals_amp() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); UrlbarPrefs.set("suggest.quicksuggest.all", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); await QuickSuggestTestUtils.forceSync(); let tests = [ { suggestion: { url: "https://example.com/0", }, expected: { // dismissal key should be the `url` value dismissalKey: "https://example.com/0", }, }, { suggestion: { url: `https://example.com/1-${TIMESTAMP_TEMPLATE}`, }, expected: { // dismissal key should be the original `url` value with the timestamp // template dismissalKey: `https://example.com/1-${TIMESTAMP_TEMPLATE}`, }, }, { suggestion: { url: "https://example.com/2", full_keyword: "full keyword 2", }, expected: { // dismissal key should be the `url` value dismissalKey: "https://example.com/2", notDismissalKeys: ["full keyword 2"], }, }, { suggestion: { url: `https://example.com/3-${TIMESTAMP_TEMPLATE}`, full_keyword: "full keyword 3", }, expected: { // dismissal key should be the `url` value dismissalKey: `https://example.com/3-${TIMESTAMP_TEMPLATE}`, notDismissalKeys: ["full keyword 3"], }, }, { suggestion: { url: "https://example.com/4", dismissal_key: "4-dismissal-key", }, expected: { // dismissal key should be the `dismissal_key` value dismissalKey: "4-dismissal-key", notDismissalKeys: ["https://example.com/4"], }, }, { suggestion: { url: `https://example.com/5-${TIMESTAMP_TEMPLATE}`, dismissal_key: "5-dismissal-key", }, expected: { // dismissal key should be the `dismissal_key` value dismissalKey: "5-dismissal-key", notDismissalKeys: [`https://example.com/5-${TIMESTAMP_TEMPLATE}`], }, }, { suggestion: { url: "https://example.com/6", full_keyword: "full keyword 6", dismissal_key: "6-dismissal-key", }, expected: { // dismissal key should be the `dismissal_key` value dismissalKey: "6-dismissal-key", notDismissalKeys: ["full keyword 6", "https://example.com/6"], }, }, { suggestion: { url: `https://example.com/7-${TIMESTAMP_TEMPLATE}`, full_keyword: "full keyword 7", dismissal_key: "7-dismissal-key", }, expected: { // dismissal key should be the `dismissal_key` value dismissalKey: "7-dismissal-key", notDismissalKeys: [ "full keyword 7", `https://example.com/7-${TIMESTAMP_TEMPLATE}`, ], }, }, ]; for (let test of tests) { info("Doing subtest: " + JSON.stringify(test)); let { suggestion, expected } = test; suggestion = { provider: "adm", title: "title", icon: null, impression_url: "https://example.com/impression", click_url: "https://example.com/click", block_id: 1, advertiser: "advertiser", iab_category: "22 - Shopping", is_sponsored: true, request_id: "request_id", score: 1, ...suggestion, }; MerinoTestUtils.server.response = MerinoTestUtils.server.makeDefaultResponse(); MerinoTestUtils.server.response.body.suggestions = [suggestion]; let expectedResult = QuickSuggestTestUtils.ampResult({ suggestedIndex: -1, provider: suggestion.provider, title: suggestion.title, fullKeyword: suggestion.full_keyword, url: suggestion.url, originalUrl: suggestion.original_url || suggestion.url, dismissalKey: suggestion.dismissal_key, requestId: suggestion.request_id, impressionUrl: suggestion.impression_url, clickUrl: suggestion.click_url, blockId: suggestion.block_id, advertiser: suggestion.advertiser, iabCategory: suggestion.iab_category, source: "merino", }); if (!suggestion.full_keyword) { delete expectedResult.payload.title; } // Do a search. The Merino suggestion should be matched. let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [expectedResult], // Ignore values related to the timestamp template. They're not important // for this test. conditionalPayloadProperties: { url: { ignore: true }, urlTimestampIndex: { ignore: true }, }, }); let result = context.results[0]; Assert.equal( QuickSuggest.getFeatureByResult(result)?.name, "AmpSuggestions", "Sanity check: The actual result should be managed by AmpSuggestions" ); // Dismiss the Merino result. await QuickSuggest.dismissResult(result); Assert.ok( await QuickSuggest.isResultDismissed(result), "isResultDismissed should return true after dismissing result" ); Assert.ok( await QuickSuggest.rustBackend.isDismissedByKey(expected.dismissalKey), "isDismissedByKey should return true after dismissing result" ); if (expected.notDismissalKeys) { for (let value of expected.notDismissalKeys) { Assert.ok( !(await QuickSuggest.rustBackend.isDismissedByKey(value)), "isDismissedByKey should return false for notDismissalKey: " + value ); } } // Do another search. The remote settings suggestion should now be matched. await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], }); // Clear dismissals. await QuickSuggest.clearDismissedSuggestions(); Assert.ok( !(await QuickSuggest.isResultDismissed(result)), "isResultDismissed should return false after clearing dismissals" ); // The Merino suggestion should be matched again. await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [expectedResult], conditionalPayloadProperties: { url: { ignore: true }, urlTimestampIndex: { ignore: true }, }, }); } MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Tests dismissals of unmanaged Merino suggestions (suggestions that are not // managed by a `SuggestFeature`). add_task(async function dismissals_unmanaged_1() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); // The "top_picks" provider is the only supported unmanaged suggestion. let provider = "top_picks"; let tests = [ { suggestion: { provider, url: "https://example.com/0", score: 1, }, expected: { // dismissal key should be the `url` value dismissalKey: "https://example.com/0", }, }, { suggestion: { provider, url: "https://example.com/1", original_url: "https://example.com/1-original-url", score: 1, }, expected: { // dismissal key should be the `original_url` value dismissalKey: "https://example.com/1-original-url", notDismissalKeys: ["https://example.com/1"], }, }, { suggestion: { provider, url: "https://example.com/2", original_url: "https://example.com/2-original-url", dismissal_key: "2-dismissal-key", score: 1, }, expected: { // dismissal key should be the `dismissal_key` value dismissalKey: "2-dismissal-key", notDismissalKeys: [ "https://example.com/2", "https://example.com/2-original-url", ], }, }, ]; for (let test of tests) { info("Doing subtest: " + JSON.stringify(test)); let { suggestion, expected } = test; MerinoTestUtils.server.response = MerinoTestUtils.server.makeDefaultResponse(); MerinoTestUtils.server.response.body.suggestions = [suggestion]; let expectedResult = { type: UrlbarUtils.RESULT_TYPE.URL, source: UrlbarUtils.RESULT_SOURCE.SEARCH, heuristic: false, payload: { provider, url: suggestion.url, originalUrl: suggestion.original_url, dismissalKey: suggestion.dismissal_key, source: "merino", isSponsored: false, shouldShowUrl: true, isBlockable: true, isManageable: true, telemetryType: provider, }, }; // Do a search. The Merino suggestion should be matched. let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [expectedResult], }); let result = context.results[0]; Assert.ok( !QuickSuggest.getFeatureByResult(result), "Sanity check: The actual result should not be managed by a feature" ); // Dismiss the Merino result. await QuickSuggest.dismissResult(result); Assert.ok( await QuickSuggest.isResultDismissed(result), "isResultDismissed should return true after dismissing result" ); Assert.ok( await QuickSuggest.rustBackend.isDismissedByKey(expected.dismissalKey), "isDismissedByKey should return true after dismissing result" ); if (expected.notDismissalKeys) { for (let value of expected.notDismissalKeys) { Assert.ok( !(await QuickSuggest.rustBackend.isDismissedByKey(value)), "isDismissedByKey should return false for notDismissalKey: " + value ); } } // Do another search. The remote settings suggestion should now be matched. await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], }); // Clear dismissals. await QuickSuggest.clearDismissedSuggestions(); Assert.ok( !(await QuickSuggest.isResultDismissed(result)), "isResultDismissed should return false after clearing dismissals" ); // The Merino suggestion should be matched again. await check_results({ context: createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [expectedResult], }); } MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Tests dismissals of unmanaged Merino suggestions (suggestions that are not // managed by a `SuggestFeature`) that all have the same URL but different // original URLs and dismissal keys. add_task(async function dismissals_unmanaged_2() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); // The "top_picks" provider is the only supported unmanaged suggestion. let provider = "top_picks"; MerinoTestUtils.server.response = MerinoTestUtils.server.makeDefaultResponse(); MerinoTestUtils.server.response.body.suggestions = [ // all three: url, original_url, dismissal_key { provider, url: "https://example.com/url", original_url: "https://example.com/original_url", dismissal_key: "dismissal-key", score: 1.0, }, // two: url, original_url { provider, url: "https://example.com/url", original_url: "https://example.com/original_url", score: 0.9, }, // only one: url { provider, url: "https://example.com/url", score: 0.8, }, ]; let expectedBaseResult = { type: UrlbarUtils.RESULT_TYPE.URL, source: UrlbarUtils.RESULT_SOURCE.SEARCH, heuristic: false, payload: { provider, url: "https://example.com/url", source: "merino", isSponsored: false, shouldShowUrl: true, isBlockable: true, isManageable: true, telemetryType: provider, }, }; // Do a search. The first Merino suggestion should be matched. info("Doing search 1"); let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [ { ...expectedBaseResult, payload: { ...expectedBaseResult.payload, originalUrl: "https://example.com/original_url", dismissalKey: "dismissal-key", }, }, ], }); let result = context.results[0]; Assert.ok( !QuickSuggest.getFeatureByResult(result), "Sanity check: The actual result should not be managed by a feature" ); // Dismiss it. await QuickSuggest.dismissResult(result); Assert.ok( await QuickSuggest.isResultDismissed(result), "isResultDismissed should return true after dismissing result 1" ); Assert.ok( await QuickSuggest.rustBackend.isDismissedByKey("dismissal-key"), "isDismissedByKey should return true after dismissing suggestion 1" ); for (let value of [ "https://example.com/url", "https://example.com/original_url", ]) { Assert.ok( !(await QuickSuggest.rustBackend.isDismissedByKey(value)), "isDismissedByKey should return false after dismissing suggestion 1: " + value ); } // Do another search. The second suggestion should be matched. info("Doing search 2"); context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [ { ...expectedBaseResult, payload: { ...expectedBaseResult.payload, originalUrl: "https://example.com/original_url", // no dismissal_key }, }, ], }); // Dismiss it. result = context.results[0]; await QuickSuggest.dismissResult(result); Assert.ok( await QuickSuggest.isResultDismissed(result), "isResultDismissed should return true after dismissing result 2" ); for (let value of ["dismissal-key", "https://example.com/original_url"]) { Assert.ok( await QuickSuggest.rustBackend.isDismissedByKey(value), "isDismissedByKey should return true after dismissing suggestion 2: " + value ); } Assert.ok( !(await QuickSuggest.rustBackend.isDismissedByKey( "https://example.com/url" )), "isDismissedByKey should return false after dismissing suggestion 2" ); // Do another search. The third suggestion should be matched. info("Doing search 3"); context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [ // no dismissal_key or original_url expectedBaseResult, ], }); // Dismiss it. result = context.results[0]; await QuickSuggest.dismissResult(result); Assert.ok( await QuickSuggest.isResultDismissed(result), "isResultDismissed should return true after dismissing result 3" ); for (let value of [ "dismissal-key", "https://example.com/original_url", "https://example.com/url", ]) { Assert.ok( await QuickSuggest.rustBackend.isDismissedByKey(value), "isDismissedByKey should return true after dismissing suggestion 3: " + value ); } await QuickSuggest.clearDismissedSuggestions(); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Tests a Merino suggestion that is a top pick/best match. add_task(async function bestMatch() { UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); // Set up a suggestion with `is_top_pick` and the "top_picks" provider so that // UrlbarProviderQuickSuggest will make a default result for it. let provider = "top_picks"; MerinoTestUtils.server.response.body.suggestions = [ { is_top_pick: true, provider, full_keyword: "full_keyword", title: "title", url: "url", icon: null, score: 1, }, ]; let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [ { isBestMatch: true, type: UrlbarUtils.RESULT_TYPE.URL, source: UrlbarUtils.RESULT_SOURCE.SEARCH, heuristic: false, payload: { telemetryType: provider, title: "full_keyword — title", url: "url", icon: null, isSponsored: false, isBlockable: true, isManageable: true, source: "merino", provider, }, }, ], }); // This isn't necessary since `check_results()` checks `isBestMatch`, but // check it here explicitly for good measure. Assert.ok(context.results[0].isBestMatch, "Result is a best match"); MerinoTestUtils.server.reset(); merinoClient().resetSession(); }); // Tests a sponsored suggestion that isn't managed by a feature. When the `all` // pref is disabled, a result for the suggestion should not be added. add_task(async function unmanaged_sponsored_allDisabled() { await doUnmanagedTest({ pref: "suggest.quicksuggest.all", suggestion: { title: "Sponsored without feature", url: "https://example.com/sponsored-without-feature", is_sponsored: true, }, shouldBeAdded: false, }); }); // Tests a sponsored suggestion that isn't managed by a feature. When the // sponsored pref is disabled, a result for the suggestion should not be added. add_task(async function unmanaged_sponsored_sponsoredDisabled() { await doUnmanagedTest({ pref: "suggest.quicksuggest.sponsored", suggestion: { title: "Sponsored without feature", url: "https://example.com/sponsored-without-feature", is_sponsored: true, }, shouldBeAdded: false, }); }); // Tests a nonsponsored suggestion that isn't managed by a feature. When the // `all` pref is disabled, a result for the suggestion should not be added. add_task(async function unmanaged_nonsponsored_allDisabled() { await doUnmanagedTest({ pref: "suggest.quicksuggest.all", suggestion: { title: "Nonsponsored without feature", url: "https://example.com/nonsponsored-without-feature", // no is_sponsored }, shouldBeAdded: false, }); }); // Tests a nonsponsored suggestion that isn't managed by a feature. When the // `all` pref is enabled and the sponsored pref is disabled, a result for the // suggestion should be added. add_task(async function unmanaged_nonsponsored_sponsoredDisabled() { await doUnmanagedTest({ pref: "suggest.quicksuggest.sponsored", suggestion: { title: "Nonsponsored without feature", url: "https://example.com/nonsponsored-without-feature", // no is_sponsored }, shouldBeAdded: true, }); }); async function doUnmanagedTest({ pref, suggestion, shouldBeAdded }) { // The "top_picks" provider is the only supported unmanaged suggestion. suggestion.provider = "top_picks"; UrlbarPrefs.set("suggest.quicksuggest.all", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); await QuickSuggestTestUtils.forceSync(); UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); MerinoTestUtils.server.response.body.suggestions = [suggestion]; let expectedResult = { type: UrlbarUtils.RESULT_TYPE.URL, source: UrlbarUtils.RESULT_SOURCE.SEARCH, heuristic: false, payload: { title: suggestion.title, url: suggestion.url, provider: suggestion.provider, telemetryType: suggestion.provider, isSponsored: !!suggestion.is_sponsored, source: "merino", isBlockable: true, isManageable: true, shouldShowUrl: true, }, }; // Do an initial search. The `all` pref and sponsored suggestions are both // enabled, so the suggestion should be matched. info("Doing search 1"); await check_results({ context: createContext("test", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [expectedResult], }); // Set the passed-in pref to false and do another search. The suggestion // should be matched as expected. UrlbarPrefs.set(pref, false); await QuickSuggestTestUtils.forceSync(); info("Doing search 2"); await check_results({ context: createContext("test", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: shouldBeAdded ? [expectedResult] : [], }); // Flip the pref back to true and do a third search. UrlbarPrefs.set(pref, true); await QuickSuggestTestUtils.forceSync(); info("Doing search 3"); let context = createContext("test", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }); await check_results({ context, matches: [expectedResult], }); // Trigger the dismiss command on the result. let dismissalPromise = TestUtils.topicObserved( "quicksuggest-dismissals-changed" ); let providersManager = ProvidersManager.getInstanceForSap("urlbar"); triggerCommand({ feature: providersManager.getProvider(UrlbarProviderQuickSuggest.name), command: "dismiss", result: context.results[0], expectedCountsByCall: { removeResult: 1, }, }); await dismissalPromise; Assert.ok( await QuickSuggest.isResultDismissed(context.results[0]), "The result should be dismissed" ); await QuickSuggest.clearDismissedSuggestions(); MerinoTestUtils.server.reset(); merinoClient().resetSession(); } // An unmanaged suggestion with an unrecognized Merino provider (i.e., not // "top_picks") should not be added. add_task(async function unmanaged_unrecognized() { UrlbarPrefs.set("suggest.quicksuggest.all", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); await QuickSuggestTestUtils.forceSync(); UrlbarPrefs.set("quicksuggest.online.available", true); UrlbarPrefs.set("quicksuggest.online.enabled", true); MerinoTestUtils.server.response.body.suggestions = [ { title: "Some unrecognized suggestion", url: "https://example.com/unmanaged_unrecognized", provider: "unmanaged-unrecognized-provider", }, ]; await check_results({ context: createContext("test", { providers: [UrlbarProviderQuickSuggest.name], isPrivate: false, }), matches: [], }); }); function merinoClient() { return QuickSuggest.getFeature("SuggestBackendMerino")?.client; } async function resetRemoteSettingsData() { await QuickSuggestTestUtils.setRemoteSettingsRecords([ { collection: QuickSuggestTestUtils.RS_COLLECTION.AMP, type: QuickSuggestTestUtils.RS_TYPE.AMP, attachment: REMOTE_SETTINGS_RESULTS, }, ]); }