tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_quicksuggest.js (78412B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // Tests for AMP and Wikipedia suggestions and some aspects of the Suggest
      6 // urlbar provider that aren't tested elsewhere. See also
      7 // `test_quicksuggest_merino.js`.
      8 
      9 "use strict";
     10 
     11 ChromeUtils.defineESModuleGetters(this, {
     12  AmpMatchingStrategy:
     13    "moz-src:///toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustSuggest.sys.mjs",
     14  AmpSuggestions:
     15    "moz-src:///browser/components/urlbar/private/AmpSuggestions.sys.mjs",
     16  SuggestBackendRust:
     17    "moz-src:///browser/components/urlbar/private/SuggestBackendRust.sys.mjs",
     18  SuggestionProvider:
     19    "moz-src:///toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustSuggest.sys.mjs",
     20 });
     21 
     22 const SPONSORED_SEARCH_STRING = "amp";
     23 const NONSPONSORED_SEARCH_STRING = "wikipedia";
     24 const SPONSORED_AND_NONSPONSORED_SEARCH_STRING = "sponsored and non-sponsored";
     25 
     26 const HTTP_SEARCH_STRING = "http prefix";
     27 const HTTPS_SEARCH_STRING = "https prefix";
     28 const PREFIX_SUGGESTIONS_STRIPPED_URL = "example.com/prefix-test";
     29 
     30 const ONE_CHAR_SEARCH_STRINGS = ["x", "x ", " x", " x "];
     31 
     32 const { TIMESTAMP_TEMPLATE, TIMESTAMP_LENGTH } = AmpSuggestions;
     33 const TIMESTAMP_SEARCH_STRING = "timestamp";
     34 const TIMESTAMP_SUGGESTION_URL = `http://example.com/timestamp-${TIMESTAMP_TEMPLATE}`;
     35 const TIMESTAMP_SUGGESTION_CLICK_URL = `http://click.reporting.test.com/timestamp-${TIMESTAMP_TEMPLATE}-foo`;
     36 
     37 const REMOTE_SETTINGS_RESULTS = [
     38  QuickSuggestTestUtils.ampRemoteSettings({
     39    keywords: [
     40      SPONSORED_SEARCH_STRING,
     41      SPONSORED_AND_NONSPONSORED_SEARCH_STRING,
     42    ],
     43  }),
     44  QuickSuggestTestUtils.wikipediaRemoteSettings({
     45    keywords: [
     46      NONSPONSORED_SEARCH_STRING,
     47      SPONSORED_AND_NONSPONSORED_SEARCH_STRING,
     48    ],
     49  }),
     50  {
     51    id: 3,
     52    url: "http://" + PREFIX_SUGGESTIONS_STRIPPED_URL,
     53    title: "HTTP Suggestion",
     54    keywords: [HTTP_SEARCH_STRING],
     55    full_keywords: [[HTTP_SEARCH_STRING, 1]],
     56    click_url: "http://example.com/http-click",
     57    impression_url: "http://example.com/http-impression",
     58    advertiser: "HttpAdvertiser",
     59    iab_category: "22 - Shopping",
     60    icon: "1234",
     61    serp_categories: [2],
     62  },
     63  {
     64    id: 4,
     65    url: "https://" + PREFIX_SUGGESTIONS_STRIPPED_URL,
     66    title: "https suggestion",
     67    keywords: [HTTPS_SEARCH_STRING],
     68    full_keywords: [[HTTPS_SEARCH_STRING, 1]],
     69    click_url: "http://click.reporting.test.com/prefix",
     70    impression_url: "http://impression.reporting.test.com/prefix",
     71    advertiser: "TestAdvertiserPrefix",
     72    iab_category: "22 - Shopping",
     73    icon: "1234",
     74  },
     75  {
     76    id: 5,
     77    url: TIMESTAMP_SUGGESTION_URL,
     78    title: "Timestamp suggestion",
     79    keywords: [TIMESTAMP_SEARCH_STRING],
     80    full_keywords: [[TIMESTAMP_SEARCH_STRING, 1]],
     81    click_url: TIMESTAMP_SUGGESTION_CLICK_URL,
     82    impression_url: "http://impression.reporting.test.com/timestamp",
     83    advertiser: "TestAdvertiserTimestamp",
     84    iab_category: "22 - Shopping",
     85    icon: "1234",
     86  },
     87  QuickSuggestTestUtils.ampRemoteSettings({
     88    keywords: [...ONE_CHAR_SEARCH_STRINGS, "12", "a longer keyword"],
     89    title: "Suggestion with 1-char keyword",
     90    url: "http://example.com/1-char-keyword",
     91  }),
     92  QuickSuggestTestUtils.ampRemoteSettings({
     93    keywords: [
     94      "amp full key",
     95      "amp full keyw",
     96      "amp full keywo",
     97      "amp full keywor",
     98      "amp full keyword",
     99      "xyz",
    100    ],
    101    full_keywords: [
    102      ["amp full keyword", 5],
    103      ["xyz", 1],
    104    ],
    105    title: "AMP suggestion with full keyword and prefix keywords",
    106    url: "https://example.com/amp-full-keyword",
    107  }),
    108  QuickSuggestTestUtils.wikipediaRemoteSettings({
    109    keywords: [
    110      "wikipedia full key",
    111      "wikipedia full keyw",
    112      "wikipedia full keywo",
    113      "wikipedia full keywor",
    114      "wikipedia full keyword",
    115    ],
    116    full_keywords: [["wikipedia full keyword", 5]],
    117    title: "Wikipedia suggestion with full keyword and prefix keywords",
    118    url: "https://example.com/wikipedia-full-keyword",
    119  }),
    120 ];
    121 
    122 const MERINO_SUGGESTIONS = [
    123  {
    124    title: "Amp Suggestion",
    125    url: "https://example.com/amp",
    126    provider: "adm",
    127    is_sponsored: true,
    128    score: 0.31,
    129    icon: "https://example.com/amp-icon",
    130    iab_category: "22 - Shopping",
    131    block_id: 1,
    132    full_keyword: "amp",
    133    advertiser: "Amp",
    134    impression_url: "https://example.com/amp-impression",
    135    click_url: "https://example.com/amp-click",
    136  },
    137  {
    138    title: "Wikipedia Suggestion",
    139    url: "https://example.com/wikipedia",
    140    is_sponsored: false,
    141    score: 0.23,
    142    description: "description",
    143    icon: "https://example.com/wikipedia-icon",
    144    full_keyword: "wikipedia",
    145    advertiser: "dynamic-wikipedia",
    146    block_id: 0,
    147    provider: "wikipedia",
    148    categories: [6], // Education
    149  },
    150 ];
    151 
    152 let gMaxResultsSuggestionsCount;
    153 
    154 function expectedSponsoredPriorityResult() {
    155  return {
    156    ...QuickSuggestTestUtils.ampResult(),
    157    isBestMatch: true,
    158    suggestedIndex: 1,
    159    isSuggestedIndexRelativeToGroup: false,
    160  };
    161 }
    162 
    163 function expectedHttpResult() {
    164  let suggestion = REMOTE_SETTINGS_RESULTS[2];
    165  return QuickSuggestTestUtils.ampResult({
    166    keyword: HTTP_SEARCH_STRING,
    167    title: suggestion.title,
    168    url: suggestion.url,
    169    originalUrl: suggestion.url,
    170    impressionUrl: suggestion.impression_url,
    171    clickUrl: suggestion.click_url,
    172    blockId: suggestion.id,
    173    advertiser: suggestion.advertiser,
    174    categories: suggestion.serp_categories,
    175    suggestedIndex: -1,
    176  });
    177 }
    178 
    179 function expectedHttpsResult() {
    180  let suggestion = REMOTE_SETTINGS_RESULTS[3];
    181  return QuickSuggestTestUtils.ampResult({
    182    keyword: HTTPS_SEARCH_STRING,
    183    title: suggestion.title,
    184    url: suggestion.url,
    185    originalUrl: suggestion.url,
    186    impressionUrl: suggestion.impression_url,
    187    clickUrl: suggestion.click_url,
    188    blockId: suggestion.id,
    189    advertiser: suggestion.advertiser,
    190    suggestedIndex: -1,
    191  });
    192 }
    193 
    194 add_setup(async function init() {
    195  // Add a bunch of suggestions that have the same keyword so we can verify the
    196  // provider respects its `queryContext.maxResults` cap when adding results.
    197  let maxResults = UrlbarPrefs.get("maxRichResults");
    198  Assert.greater(maxResults, 0, "This test expects maxRichResults to be > 0");
    199  gMaxResultsSuggestionsCount = 2 * maxResults;
    200  for (let i = 0; i < gMaxResultsSuggestionsCount; i++) {
    201    REMOTE_SETTINGS_RESULTS.push(
    202      QuickSuggestTestUtils.ampRemoteSettings({
    203        keywords: ["maxresults"],
    204        title: "maxresults " + i,
    205        url: "https://example.com/maxresults/" + i,
    206      })
    207    );
    208  }
    209 
    210  // Install a default test engine.
    211  let engine = await addTestSuggestionsEngine();
    212  await Services.search.setDefault(
    213    engine,
    214    Ci.nsISearchService.CHANGE_REASON_UNKNOWN
    215  );
    216 
    217  UrlbarPrefs.set("quicksuggest.ampTopPickCharThreshold", 0);
    218 
    219  await QuickSuggestTestUtils.ensureQuickSuggestInit();
    220  await resetRemoteSettingsData();
    221 });
    222 
    223 add_task(async function offline_telemetryType_amp() {
    224  Assert.equal(
    225    QuickSuggest.getFeature("AmpSuggestions").getSuggestionTelemetryType({
    226      source: "rust",
    227    }),
    228    "adm_sponsored",
    229    "Telemetry type should be 'adm_sponsored'"
    230  );
    231 });
    232 
    233 add_task(async function offline_telemetryType_wikipedia() {
    234  Assert.equal(
    235    QuickSuggest.getFeature("WikipediaSuggestions").getSuggestionTelemetryType({
    236      source: "rust",
    237    }),
    238    "adm_nonsponsored",
    239    "Telemetry type should be 'adm_nonsponsored'"
    240  );
    241 });
    242 
    243 add_task(async function online_telemetryType_amp() {
    244  Assert.equal(
    245    QuickSuggest.getFeature("AmpSuggestions").getSuggestionTelemetryType({
    246      source: "merino",
    247    }),
    248    "adm_sponsored",
    249    "Telemetry type should be 'adm_sponsored'"
    250  );
    251 });
    252 
    253 add_task(async function online_telemetryType_wikipedia() {
    254  Assert.equal(
    255    QuickSuggest.getFeature("WikipediaSuggestions").getSuggestionTelemetryType({
    256      source: "merino",
    257    }),
    258    "wikipedia",
    259    "Telemetry type should be 'wikipedia'"
    260  );
    261 });
    262 
    263 // Tests with both `all` and sponsored enabled with a sponsored search string.
    264 // Sponsored suggestions should be matched.
    265 add_task(async function allEnabled_sponsoredEnabled_sponsoredSearch() {
    266  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    267  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    268  await QuickSuggestTestUtils.forceSync();
    269 
    270  let context = createContext(SPONSORED_SEARCH_STRING, {
    271    providers: [UrlbarProviderQuickSuggest.name],
    272    isPrivate: false,
    273  });
    274  await check_results({
    275    context,
    276    matches: [QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 })],
    277  });
    278 
    279  // The title should include the full keyword and em dash, and the part of the
    280  // title that the search string does not match should be highlighted.
    281  let result = context.results[0];
    282  let { value, highlights } = result.getDisplayableValueAndHighlights("title", {
    283    tokens: context.tokens,
    284  });
    285  Assert.equal(
    286    value,
    287    `${SPONSORED_SEARCH_STRING} — Amp Suggestion`,
    288    "The title should be correct"
    289  );
    290  Assert.deepEqual(highlights, [], "The highlights should be correct");
    291 });
    292 
    293 // Tests with both `all` and sponsored enabled with a non-sponsored search
    294 // string. Non-sponsored suggestions should be matched.
    295 add_task(async function allEnabled_sponsoredEnabled_nonsponsoredSearch() {
    296  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    297  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    298  await QuickSuggestTestUtils.forceSync();
    299 
    300  let context = createContext(NONSPONSORED_SEARCH_STRING, {
    301    providers: [UrlbarProviderQuickSuggest.name],
    302    isPrivate: false,
    303  });
    304  await check_results({
    305    context,
    306    matches: [QuickSuggestTestUtils.wikipediaResult()],
    307  });
    308 
    309  // The title should include the full keyword and em dash, and the part of the
    310  // title that the search string does not match should be highlighted.
    311  let result = context.results[0];
    312  let { value, highlights } = result.getDisplayableValueAndHighlights("title", {
    313    tokens: context.tokens,
    314  });
    315  Assert.equal(
    316    value,
    317    `${NONSPONSORED_SEARCH_STRING} — Wikipedia Suggestion`,
    318    "The title should be correct"
    319  );
    320  Assert.deepEqual(highlights, [], "The highlights should be correct");
    321 });
    322 
    323 // Tests with both `all` and sponsored enabled with a search string that doesn't
    324 // match anything.
    325 add_task(async function allEnabled_sponsoredEnabled_nonmatchingSearch() {
    326  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    327  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    328  await QuickSuggestTestUtils.forceSync();
    329 
    330  let context = createContext("this doesn't match anything", {
    331    providers: [UrlbarProviderQuickSuggest.name],
    332    isPrivate: false,
    333  });
    334  await check_results({ context, matches: [] });
    335 });
    336 
    337 // Tests with `all` enabled and sponsored disabled with a sponsored search
    338 // string. No suggestions should be matched.
    339 add_task(async function allEnabled_sponsoredDisabled_sponsoredSearch() {
    340  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    341  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
    342  await QuickSuggestTestUtils.forceSync();
    343 
    344  let context = createContext(SPONSORED_SEARCH_STRING, {
    345    providers: [UrlbarProviderQuickSuggest.name],
    346    isPrivate: false,
    347  });
    348  await check_results({ context, matches: [] });
    349 });
    350 
    351 // Tests with `all` enabled and sponsored disabled with a non-sponsored search
    352 // string. Non-sponsored suggestions should be matched.
    353 add_task(async function allEnabled_sponsoredDisabled_nonsponsoredSearch() {
    354  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    355  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
    356  await QuickSuggestTestUtils.forceSync();
    357 
    358  let context = createContext(NONSPONSORED_SEARCH_STRING, {
    359    providers: [UrlbarProviderQuickSuggest.name],
    360    isPrivate: false,
    361  });
    362  await check_results({
    363    context,
    364    matches: [QuickSuggestTestUtils.wikipediaResult()],
    365  });
    366 });
    367 
    368 // Tests with `all` disabled and sponsored enabled with a sponsored search
    369 // string. No suggestions should be matched. The settings UI does not make this
    370 // case possible, but the prefs are independent, so it's technically possible.
    371 add_task(async function allDisabled_sponsoredEnabled_sponsoredSearch() {
    372  UrlbarPrefs.set("suggest.quicksuggest.all", false);
    373  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    374  await QuickSuggestTestUtils.forceSync();
    375 
    376  let context = createContext(SPONSORED_SEARCH_STRING, {
    377    providers: [UrlbarProviderQuickSuggest.name],
    378    isPrivate: false,
    379  });
    380  await check_results({ context, matches: [] });
    381 });
    382 
    383 // Tests with `all` disabled and sponsored enabled with a non-sponsored search
    384 // string. No suggestions should be matched. The settings UI does not make this
    385 // case possible, but the prefs are independent, so it's technically possible.
    386 add_task(async function allDisabled_sponsoredEnabled_nonsponsoredSearch() {
    387  UrlbarPrefs.set("suggest.quicksuggest.all", false);
    388  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    389  await QuickSuggestTestUtils.forceSync();
    390 
    391  let context = createContext(NONSPONSORED_SEARCH_STRING, {
    392    providers: [UrlbarProviderQuickSuggest.name],
    393    isPrivate: false,
    394  });
    395  await check_results({ context, matches: [] });
    396 });
    397 
    398 // Tests with both `all` and sponsored disabled with a sponsored search string.
    399 // No suggestions should be matched.
    400 add_task(async function allDisabled_sponsoredDisabled_sponsoredSearch() {
    401  UrlbarPrefs.set("suggest.quicksuggest.all", false);
    402  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
    403  await QuickSuggestTestUtils.forceSync();
    404 
    405  let context = createContext(SPONSORED_SEARCH_STRING, {
    406    providers: [UrlbarProviderQuickSuggest.name],
    407    isPrivate: false,
    408  });
    409  await check_results({ context, matches: [] });
    410 });
    411 
    412 // Tests with both `all` and sponsored disabled with a non-sponsored search
    413 // string. No suggestions should be matched.
    414 add_task(async function allDisabled_sponsoredDisabled_nonsponsoredSearch() {
    415  UrlbarPrefs.set("suggest.quicksuggest.all", false);
    416  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
    417  await QuickSuggestTestUtils.forceSync();
    418 
    419  let context = createContext(NONSPONSORED_SEARCH_STRING, {
    420    providers: [UrlbarProviderQuickSuggest.name],
    421    isPrivate: false,
    422  });
    423  await check_results({ context, matches: [] });
    424 });
    425 
    426 // Search string matching should be case insensitive and ignore leading spaces.
    427 add_task(async function caseInsensitiveAndLeadingSpaces() {
    428  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    429  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    430  await QuickSuggestTestUtils.forceSync();
    431 
    432  let context = createContext("  " + SPONSORED_SEARCH_STRING.toUpperCase(), {
    433    providers: [UrlbarProviderQuickSuggest.name],
    434    isPrivate: false,
    435  });
    436  await check_results({
    437    context,
    438    matches: [QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 })],
    439  });
    440 });
    441 
    442 // The provider should not be active for search strings that are empty or
    443 // contain only spaces.
    444 add_task(async function emptySearchStringsAndSpaces() {
    445  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    446  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    447  await QuickSuggestTestUtils.forceSync();
    448 
    449  let searchStrings = ["", " ", "  ", "              "];
    450  for (let str of searchStrings) {
    451    let msg = JSON.stringify(str) + ` (length = ${str.length})`;
    452    info("Testing search string: " + msg);
    453 
    454    let context = createContext(str, {
    455      providers: [UrlbarProviderQuickSuggest.name],
    456      isPrivate: false,
    457    });
    458    await check_results({
    459      context,
    460      matches: [],
    461    });
    462    Assert.ok(
    463      !(await UrlbarProvidersManager.getProvider(
    464        UrlbarProviderQuickSuggest.name
    465      ).isActive(context)),
    466      "Provider should not be active for search string: " + msg
    467    );
    468  }
    469 });
    470 
    471 // Results should be returned even when `browser.search.suggest.enabled` is
    472 // false.
    473 add_task(async function browser_search_suggest_disabled() {
    474  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    475  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    476  UrlbarPrefs.set("browser.search.suggest.enabled", false);
    477  await QuickSuggestTestUtils.forceSync();
    478 
    479  let context = createContext(SPONSORED_SEARCH_STRING, {
    480    providers: [UrlbarProviderQuickSuggest.name],
    481    isPrivate: false,
    482  });
    483  await check_results({
    484    context,
    485    matches: [QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 })],
    486  });
    487 
    488  UrlbarPrefs.clear("browser.search.suggest.enabled");
    489 });
    490 
    491 // Results should be returned even when `browser.urlbar.suggest.searches` is
    492 // false.
    493 add_task(async function browser_suggest_searches_disabled() {
    494  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    495  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    496  UrlbarPrefs.set("suggest.searches", false);
    497  await QuickSuggestTestUtils.forceSync();
    498 
    499  let context = createContext(SPONSORED_SEARCH_STRING, {
    500    providers: [UrlbarProviderQuickSuggest.name],
    501    isPrivate: false,
    502  });
    503  await check_results({
    504    context,
    505    matches: [QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 })],
    506  });
    507 
    508  UrlbarPrefs.clear("suggest.searches");
    509 });
    510 
    511 // Neither sponsored nor non-sponsored results should appear in private contexts
    512 // even when suggestions in private windows are enabled.
    513 add_task(async function privateContext() {
    514  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    515  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    516  await QuickSuggestTestUtils.forceSync();
    517 
    518  for (let privateSuggestionsEnabled of [true, false]) {
    519    UrlbarPrefs.set(
    520      "browser.search.suggest.enabled.private",
    521      privateSuggestionsEnabled
    522    );
    523    let context = createContext(SPONSORED_SEARCH_STRING, {
    524      providers: [UrlbarProviderQuickSuggest.name],
    525      isPrivate: true,
    526    });
    527    await check_results({
    528      context,
    529      matches: [],
    530    });
    531  }
    532 
    533  UrlbarPrefs.clear("browser.search.suggest.enabled.private");
    534 });
    535 
    536 // When search suggestions come before general results and the only general
    537 // result is a quick suggest result, it should come last.
    538 add_task(async function suggestionsBeforeGeneral_only() {
    539  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    540  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    541  UrlbarPrefs.set("browser.search.suggest.enabled", true);
    542  UrlbarPrefs.set("suggest.searches", true);
    543  UrlbarPrefs.set("showSearchSuggestionsFirst", true);
    544  await QuickSuggestTestUtils.forceSync();
    545 
    546  let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
    547  await check_results({
    548    context,
    549    matches: [
    550      makeSearchResult(context, {
    551        heuristic: true,
    552        query: SPONSORED_SEARCH_STRING,
    553        engineName: Services.search.defaultEngine.name,
    554      }),
    555      makeSearchResult(context, {
    556        query: SPONSORED_SEARCH_STRING,
    557        suggestion: SPONSORED_SEARCH_STRING + " foo",
    558        engineName: Services.search.defaultEngine.name,
    559      }),
    560      makeSearchResult(context, {
    561        query: SPONSORED_SEARCH_STRING,
    562        suggestion: SPONSORED_SEARCH_STRING + " bar",
    563        engineName: Services.search.defaultEngine.name,
    564      }),
    565      QuickSuggestTestUtils.ampResult(),
    566    ],
    567  });
    568 
    569  UrlbarPrefs.clear("browser.search.suggest.enabled");
    570  UrlbarPrefs.clear("suggest.searches");
    571  UrlbarPrefs.clear("showSearchSuggestionsFirst");
    572 });
    573 
    574 // When search suggestions come before general results and there are other
    575 // general results besides quick suggest, the quick suggest result should come
    576 // last.
    577 add_task(async function suggestionsBeforeGeneral_others() {
    578  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    579  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    580  UrlbarPrefs.set("browser.search.suggest.enabled", true);
    581  UrlbarPrefs.set("suggest.searches", true);
    582  UrlbarPrefs.set("showSearchSuggestionsFirst", true);
    583  await QuickSuggestTestUtils.forceSync();
    584 
    585  let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
    586 
    587  // Add some history that will match our query below.
    588  let maxResults = UrlbarPrefs.get("maxRichResults");
    589  let historyResults = [];
    590  for (let i = 0; i < maxResults; i++) {
    591    let url = "http://example.com/" + SPONSORED_SEARCH_STRING + i;
    592    historyResults.push(
    593      makeVisitResult(context, {
    594        uri: url,
    595        title: "test visit for " + url,
    596      })
    597    );
    598    await PlacesTestUtils.addVisits(url);
    599  }
    600  historyResults = historyResults.reverse().slice(0, historyResults.length - 4);
    601 
    602  await check_results({
    603    context,
    604    matches: [
    605      makeSearchResult(context, {
    606        heuristic: true,
    607        query: SPONSORED_SEARCH_STRING,
    608        engineName: Services.search.defaultEngine.name,
    609      }),
    610      makeSearchResult(context, {
    611        query: SPONSORED_SEARCH_STRING,
    612        suggestion: SPONSORED_SEARCH_STRING + " foo",
    613        engineName: Services.search.defaultEngine.name,
    614      }),
    615      makeSearchResult(context, {
    616        query: SPONSORED_SEARCH_STRING,
    617        suggestion: SPONSORED_SEARCH_STRING + " bar",
    618        engineName: Services.search.defaultEngine.name,
    619      }),
    620      QuickSuggestTestUtils.ampResult(),
    621      ...historyResults,
    622    ],
    623  });
    624 
    625  UrlbarPrefs.clear("browser.search.suggest.enabled");
    626  UrlbarPrefs.clear("suggest.searches");
    627  UrlbarPrefs.clear("showSearchSuggestionsFirst");
    628  await PlacesUtils.history.clear();
    629 });
    630 
    631 // When general results come before search suggestions and the only general
    632 // result is a quick suggest result, it should come before suggestions.
    633 add_task(async function generalBeforeSuggestions_only() {
    634  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    635  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    636  UrlbarPrefs.set("browser.search.suggest.enabled", true);
    637  UrlbarPrefs.set("suggest.searches", true);
    638  UrlbarPrefs.set("showSearchSuggestionsFirst", false);
    639  await QuickSuggestTestUtils.forceSync();
    640 
    641  let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
    642  await check_results({
    643    context,
    644    matches: [
    645      makeSearchResult(context, {
    646        heuristic: true,
    647        query: SPONSORED_SEARCH_STRING,
    648        engineName: Services.search.defaultEngine.name,
    649      }),
    650      QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 }),
    651      makeSearchResult(context, {
    652        query: SPONSORED_SEARCH_STRING,
    653        suggestion: SPONSORED_SEARCH_STRING + " foo",
    654        engineName: Services.search.defaultEngine.name,
    655      }),
    656      makeSearchResult(context, {
    657        query: SPONSORED_SEARCH_STRING,
    658        suggestion: SPONSORED_SEARCH_STRING + " bar",
    659        engineName: Services.search.defaultEngine.name,
    660      }),
    661    ],
    662  });
    663 
    664  UrlbarPrefs.clear("browser.search.suggest.enabled");
    665  UrlbarPrefs.clear("suggest.searches");
    666  UrlbarPrefs.clear("showSearchSuggestionsFirst");
    667 });
    668 
    669 // When general results come before search suggestions and there are other
    670 // general results besides quick suggest, the quick suggest result should be the
    671 // last general result.
    672 add_task(async function generalBeforeSuggestions_others() {
    673  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    674  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    675  UrlbarPrefs.set("browser.search.suggest.enabled", true);
    676  UrlbarPrefs.set("suggest.searches", true);
    677  UrlbarPrefs.set("showSearchSuggestionsFirst", false);
    678  await QuickSuggestTestUtils.forceSync();
    679 
    680  let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
    681 
    682  // Add some history that will match our query below.
    683  let maxResults = UrlbarPrefs.get("maxRichResults");
    684  let historyResults = [];
    685  for (let i = 0; i < maxResults; i++) {
    686    let url = "http://example.com/" + SPONSORED_SEARCH_STRING + i;
    687    historyResults.push(
    688      makeVisitResult(context, {
    689        uri: url,
    690        title: "test visit for " + url,
    691      })
    692    );
    693    await PlacesTestUtils.addVisits(url);
    694  }
    695  historyResults = historyResults.reverse().slice(0, historyResults.length - 4);
    696 
    697  await check_results({
    698    context,
    699    matches: [
    700      makeSearchResult(context, {
    701        heuristic: true,
    702        query: SPONSORED_SEARCH_STRING,
    703        engineName: Services.search.defaultEngine.name,
    704      }),
    705      ...historyResults,
    706      QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 }),
    707      makeSearchResult(context, {
    708        query: SPONSORED_SEARCH_STRING,
    709        suggestion: SPONSORED_SEARCH_STRING + " foo",
    710        engineName: Services.search.defaultEngine.name,
    711      }),
    712      makeSearchResult(context, {
    713        query: SPONSORED_SEARCH_STRING,
    714        suggestion: SPONSORED_SEARCH_STRING + " bar",
    715        engineName: Services.search.defaultEngine.name,
    716      }),
    717    ],
    718  });
    719 
    720  UrlbarPrefs.clear("browser.search.suggest.enabled");
    721  UrlbarPrefs.clear("suggest.searches");
    722  UrlbarPrefs.clear("showSearchSuggestionsFirst");
    723  await PlacesUtils.history.clear();
    724 });
    725 
    726 // The provider should not add more than `queryContext.maxResults` results.
    727 add_task(async function maxResults() {
    728  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    729  await QuickSuggestTestUtils.forceSync();
    730 
    731  let searchString = "maxresults";
    732  let suggestions = await QuickSuggest.rustBackend.query(searchString);
    733  Assert.equal(
    734    suggestions.length,
    735    gMaxResultsSuggestionsCount,
    736    "The backend should return all matching suggestions"
    737  );
    738 
    739  let context = createContext(searchString, {
    740    providers: [UrlbarProviderQuickSuggest.name],
    741    isPrivate: false,
    742  });
    743 
    744  // Spy on `muxer.sort()` so we can verify the provider limited the number of
    745  // results it added to the query.
    746  let muxerName = context.muxer || "UnifiedComplete";
    747  let muxer = UrlbarProvidersManager.muxers.get(muxerName);
    748  Assert.ok(!!muxer, "Muxer should exist");
    749 
    750  let sandbox = sinon.createSandbox();
    751  let spy = sandbox.spy(muxer, "sort");
    752 
    753  // Use `check_results()` to do the query.
    754  await check_results({
    755    context,
    756    matches: [
    757      QuickSuggestTestUtils.ampResult({
    758        keyword: "maxresults",
    759        title: "maxresults 0",
    760        url: "https://example.com/maxresults/0",
    761        suggestedIndex: -1,
    762      }),
    763    ],
    764  });
    765 
    766  // Check the `sort()` calls.
    767  let calls = spy.getCalls();
    768  Assert.greater(
    769    calls.length,
    770    0,
    771    "muxer.sort() should have been called at least once"
    772  );
    773 
    774  for (let c of calls) {
    775    let unsortedResults = c.args[1];
    776    Assert.lessOrEqual(
    777      unsortedResults.length,
    778      UrlbarPrefs.get("maxRichResults"),
    779      "Provider should have added no more than maxRichResults results"
    780    );
    781  }
    782 
    783  sandbox.restore();
    784 });
    785 
    786 // When the Suggest provider adds more than one result and they are not hidden
    787 // exposures, the muxer should add the first one to the final results list and
    788 // discard the rest, and the discarded results should not prevent the muxer from
    789 // adding other non-Suggest results.
    790 add_task(async function manySuggestResults_visible() {
    791  await doManySuggestResultsTest({
    792    expectedSuggestResults: [
    793      QuickSuggestTestUtils.ampResult({
    794        keyword: "maxresults",
    795        title: "maxresults 0",
    796        url: "https://example.com/maxresults/0",
    797        suggestedIndex: -1,
    798      }),
    799    ],
    800    expectedOtherResultsCount: UrlbarPrefs.get("maxRichResults") - 1,
    801  });
    802 });
    803 
    804 // When the Suggest provider adds more than one result and they are hidden
    805 // exposures, the muxer should add up to `queryContext.maxResults` of them to
    806 // the final results list, and they should not prevent the muxer from adding
    807 // other non-Suggest results.
    808 add_task(async function manySuggestResults_hiddenExposures() {
    809  UrlbarPrefs.set("exposureResults", "rust_adm_sponsored");
    810  UrlbarPrefs.set("showExposureResults", false);
    811 
    812  // Build the list of expected Suggest results.
    813  let results = [];
    814  let maxResults = UrlbarPrefs.get("maxRichResults");
    815  let suggestResultsCount = Math.min(gMaxResultsSuggestionsCount, maxResults);
    816  for (let i = 0; i < suggestResultsCount; i++) {
    817    let index = maxResults - 1 - i;
    818    results.push({
    819      ...QuickSuggestTestUtils.ampResult({
    820        keyword: "maxresults",
    821        title: "maxresults " + index,
    822        url: "https://example.com/maxresults/" + index,
    823        suggestedIndex: -1,
    824      }),
    825      exposureTelemetry: UrlbarUtils.EXPOSURE_TELEMETRY.HIDDEN,
    826    });
    827  }
    828 
    829  await doManySuggestResultsTest({
    830    expectedSuggestResults: results,
    831    expectedOtherResultsCount: maxResults,
    832  });
    833 
    834  UrlbarPrefs.clear("exposureResults");
    835  UrlbarPrefs.clear("showExposureResults");
    836 });
    837 
    838 async function doManySuggestResultsTest({
    839  expectedSuggestResults,
    840  expectedOtherResultsCount,
    841 }) {
    842  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    843  await QuickSuggestTestUtils.forceSync();
    844 
    845  // Make sure many Suggest suggestions match the search string.
    846  let searchString = "maxresults";
    847  let suggestions = await QuickSuggest.rustBackend.query(searchString);
    848  Assert.equal(
    849    suggestions.length,
    850    gMaxResultsSuggestionsCount,
    851    "Sanity check: The backend should return all matching suggestions"
    852  );
    853  Assert.greater(
    854    suggestions.length,
    855    1,
    856    "Sanity check: There should be more than 1 matching suggestion"
    857  );
    858 
    859  // Register a test provider that adds a bunch of history results.
    860  let otherResults = [];
    861  let maxResults = UrlbarPrefs.get("maxRichResults");
    862  for (let i = 0; i < maxResults; i++) {
    863    otherResults.push(
    864      new UrlbarResult({
    865        type: UrlbarUtils.RESULT_TYPE.URL,
    866        source: UrlbarUtils.RESULT_SOURCE.HISTORY,
    867        payload: { url: "http://example.com/history/" + i },
    868      })
    869    );
    870  }
    871 
    872  let provider = new UrlbarTestUtils.TestProvider({ results: otherResults });
    873  UrlbarProvidersManager.registerProvider(provider);
    874 
    875  // Do a search that matches all the Suggest suggestions and the test
    876  // provider's results. The Suggest suggestion(s) should be first since its
    877  // `suggestedIndex` is 0.
    878  await check_results({
    879    context: createContext(searchString, {
    880      providers: [UrlbarProviderQuickSuggest.name, provider.name],
    881      isPrivate: false,
    882    }),
    883    matches: [
    884      ...otherResults.slice(0, expectedOtherResultsCount),
    885      ...expectedSuggestResults,
    886    ],
    887  });
    888 
    889  UrlbarProvidersManager.unregisterProvider(provider);
    890 }
    891 
    892 add_task(async function dedupeAgainstURL_samePrefix() {
    893  await doDedupeAgainstURLTest({
    894    searchString: HTTP_SEARCH_STRING,
    895    expectedQuickSuggestResult: expectedHttpResult(),
    896    otherPrefix: "http://",
    897    expectOther: false,
    898  });
    899 });
    900 
    901 add_task(async function dedupeAgainstURL_higherPrefix() {
    902  await doDedupeAgainstURLTest({
    903    searchString: HTTPS_SEARCH_STRING,
    904    expectedQuickSuggestResult: expectedHttpsResult(),
    905    otherPrefix: "http://",
    906    expectOther: false,
    907  });
    908 });
    909 
    910 add_task(async function dedupeAgainstURL_lowerPrefix() {
    911  await doDedupeAgainstURLTest({
    912    searchString: HTTP_SEARCH_STRING,
    913    expectedQuickSuggestResult: expectedHttpResult(),
    914    otherPrefix: "https://",
    915    expectOther: true,
    916  });
    917 });
    918 
    919 /**
    920 * Tests how the muxer dedupes URL results against quick suggest results.
    921 * Depending on prefix rank, quick suggest results should be preferred over
    922 * other URL results with the same stripped URL: Other results should be
    923 * discarded when their prefix rank is lower than the prefix rank of the quick
    924 * suggest. They should not be discarded when their prefix rank is higher, and
    925 * in that case both results should be included.
    926 *
    927 * This function adds a visit to the URL formed by the given `otherPrefix` and
    928 * `PREFIX_SUGGESTIONS_STRIPPED_URL`. The visit's title will be set to the given
    929 * `searchString` so that both the visit and the quick suggest will match it.
    930 *
    931 * @param {object} options
    932 *   Options object.
    933 * @param {string} options.searchString
    934 *   The search string that should trigger one of the mock prefix-test quick
    935 *   suggest results.
    936 * @param {object} options.expectedQuickSuggestResult
    937 *   The expected quick suggest result.
    938 * @param {string} options.otherPrefix
    939 *   The visit will be created with a URL with this prefix, e.g., "http://".
    940 * @param {boolean} options.expectOther
    941 *   Whether the visit result should appear in the final results.
    942 */
    943 async function doDedupeAgainstURLTest({
    944  searchString,
    945  expectedQuickSuggestResult,
    946  otherPrefix,
    947  expectOther,
    948 }) {
    949  // Disable search suggestions. This means the expected suggestedIndex for
    950  // sponsored suggestions will now be -1. We assume expectedQuickSuggestResult
    951  // is sponsored, so set its suggestedIndex now.
    952  UrlbarPrefs.set("suggest.searches", false);
    953  expectedQuickSuggestResult.suggestedIndex = -1;
    954 
    955  // Add a visit that will match our query below.
    956  let otherURL = otherPrefix + PREFIX_SUGGESTIONS_STRIPPED_URL;
    957  await PlacesTestUtils.addVisits({ uri: otherURL, title: searchString });
    958 
    959  // First, do a search with quick suggest disabled to make sure the search
    960  // string matches the visit.
    961  info("Doing first query");
    962  UrlbarPrefs.set("suggest.quicksuggest.all", false);
    963  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
    964  let context = createContext(searchString, { isPrivate: false });
    965  await check_results({
    966    context,
    967    matches: [
    968      makeSearchResult(context, {
    969        heuristic: true,
    970        query: searchString,
    971        engineName: Services.search.defaultEngine.name,
    972      }),
    973      makeVisitResult(context, {
    974        uri: otherURL,
    975        title: searchString,
    976      }),
    977    ],
    978  });
    979 
    980  // Now do another search with quick suggest enabled.
    981  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    982  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
    983  await QuickSuggestTestUtils.forceSync();
    984 
    985  context = createContext(searchString, { isPrivate: false });
    986 
    987  let expectedResults = [
    988    makeSearchResult(context, {
    989      heuristic: true,
    990      query: searchString,
    991      engineName: Services.search.defaultEngine.name,
    992    }),
    993  ];
    994 
    995  if (expectOther) {
    996    expectedResults.push(
    997      makeVisitResult(context, {
    998        uri: otherURL,
    999        title: searchString,
   1000      })
   1001    );
   1002  }
   1003 
   1004  // The expected result is last since its expected suggestedIndex is -1.
   1005  expectedResults.push(expectedQuickSuggestResult);
   1006 
   1007  info("Doing second query");
   1008  await check_results({ context, matches: expectedResults });
   1009 
   1010  UrlbarPrefs.clear("suggest.quicksuggest.all");
   1011  UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
   1012  await QuickSuggestTestUtils.forceSync();
   1013 
   1014  UrlbarPrefs.clear("suggest.searches");
   1015  await PlacesUtils.history.clear();
   1016 }
   1017 
   1018 // Timestamp templates in URLs should be replaced with real timestamps.
   1019 add_task(async function timestamps() {
   1020  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1021  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1022  await QuickSuggestTestUtils.forceSync();
   1023 
   1024  // Do a search.
   1025  let context = createContext(TIMESTAMP_SEARCH_STRING, {
   1026    providers: [UrlbarProviderQuickSuggest.name],
   1027    isPrivate: false,
   1028  });
   1029  let controller = UrlbarTestUtils.newMockController();
   1030  await controller.startQuery(context);
   1031 
   1032  // Should be one quick suggest result.
   1033  Assert.equal(context.results.length, 1, "One result returned");
   1034  let result = context.results[0];
   1035 
   1036  QuickSuggestTestUtils.assertTimestampsReplaced(result, {
   1037    url: TIMESTAMP_SUGGESTION_URL,
   1038    sponsoredClickUrl: TIMESTAMP_SUGGESTION_CLICK_URL,
   1039  });
   1040 });
   1041 
   1042 // Real quick suggest URLs include a timestamp template that
   1043 // UrlbarProviderQuickSuggest fills in when it fetches suggestions. When the
   1044 // user picks a quick suggest, its URL with its particular timestamp is added to
   1045 // history. If the user triggers the quick suggest again later, its new
   1046 // timestamp may be different from the one in the user's history. In that case,
   1047 // the two URLs should be treated as dupes and only the quick suggest should be
   1048 // shown, not the URL from history.
   1049 add_task(async function dedupeAgainstURL_timestamps() {
   1050  // Disable search suggestions. This means the expected suggestedIndex for
   1051  // sponsored suggestions will now be -1.
   1052  UrlbarPrefs.set("suggest.searches", false);
   1053 
   1054  // Add a visit that will match the query below and dupe the quick suggest.
   1055  let dupeURL = TIMESTAMP_SUGGESTION_URL.replace(
   1056    TIMESTAMP_TEMPLATE,
   1057    "2013051113"
   1058  );
   1059 
   1060  // Add other visits that will match the query and almost dupe the quick
   1061  // suggest but not quite because they have invalid timestamps.
   1062  let badTimestamps = [
   1063    // not numeric digits
   1064    "x".repeat(TIMESTAMP_LENGTH),
   1065    // too few digits
   1066    "5".repeat(TIMESTAMP_LENGTH - 1),
   1067    // empty string, too few digits
   1068    "",
   1069  ];
   1070  let badTimestampURLs = badTimestamps.map(str =>
   1071    TIMESTAMP_SUGGESTION_URL.replace(TIMESTAMP_TEMPLATE, str)
   1072  );
   1073 
   1074  await PlacesTestUtils.addVisits(
   1075    [dupeURL, ...badTimestampURLs].map(uri => ({
   1076      uri,
   1077      title: TIMESTAMP_SEARCH_STRING,
   1078    }))
   1079  );
   1080 
   1081  // First, do a search with quick suggest disabled to make sure the search
   1082  // string matches all the other URLs.
   1083  info("Doing first query");
   1084  UrlbarPrefs.set("suggest.quicksuggest.all", false);
   1085  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
   1086  let context = createContext(TIMESTAMP_SEARCH_STRING, { isPrivate: false });
   1087 
   1088  let expectedHeuristic = makeSearchResult(context, {
   1089    heuristic: true,
   1090    query: TIMESTAMP_SEARCH_STRING,
   1091    engineName: Services.search.defaultEngine.name,
   1092  });
   1093  let expectedDupeResult = makeVisitResult(context, {
   1094    uri: dupeURL,
   1095    title: TIMESTAMP_SEARCH_STRING,
   1096  });
   1097  let expectedBadTimestampResults = [...badTimestampURLs].reverse().map(uri =>
   1098    makeVisitResult(context, {
   1099      uri,
   1100      title: TIMESTAMP_SEARCH_STRING,
   1101    })
   1102  );
   1103 
   1104  await check_results({
   1105    context,
   1106    matches: [
   1107      expectedHeuristic,
   1108      ...expectedBadTimestampResults,
   1109      expectedDupeResult,
   1110    ],
   1111  });
   1112 
   1113  // Now do another search with quick suggest enabled.
   1114  info("Doing second query");
   1115  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1116  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1117  await QuickSuggestTestUtils.forceSync();
   1118  context = createContext(TIMESTAMP_SEARCH_STRING, { isPrivate: false });
   1119 
   1120  let expectedQuickSuggest = QuickSuggestTestUtils.ampResult({
   1121    originalUrl: TIMESTAMP_SUGGESTION_URL,
   1122    keyword: TIMESTAMP_SEARCH_STRING,
   1123    title: "Timestamp suggestion",
   1124    impressionUrl: "http://impression.reporting.test.com/timestamp",
   1125    blockId: 5,
   1126    advertiser: "TestAdvertiserTimestamp",
   1127    iabCategory: "22 - Shopping",
   1128    // suggestedIndex is -1 since search suggestions are disabled.
   1129    suggestedIndex: -1,
   1130  });
   1131 
   1132  let expectedResults = [expectedHeuristic, ...expectedBadTimestampResults];
   1133 
   1134  const QUICK_SUGGEST_INDEX = expectedResults.length;
   1135  expectedResults.push(expectedQuickSuggest);
   1136 
   1137  let controller = UrlbarTestUtils.newMockController();
   1138  await controller.startQuery(context);
   1139  info("Actual results: " + JSON.stringify(context.results));
   1140 
   1141  Assert.equal(
   1142    context.results.length,
   1143    expectedResults.length,
   1144    "Found the expected number of results"
   1145  );
   1146 
   1147  function getPayload(result, { ignore = [] } = {}) {
   1148    ignore.push("suggestionObject");
   1149    let payload = {};
   1150    for (let [key, value] of Object.entries(result.payload)) {
   1151      if (value !== undefined && !ignore.includes(key)) {
   1152        payload[key] = value;
   1153      }
   1154    }
   1155    return payload;
   1156  }
   1157 
   1158  // Check actual vs. expected result properties.
   1159  for (let i = 0; i < expectedResults.length; i++) {
   1160    let actual = context.results[i];
   1161    let expected = expectedResults[i];
   1162    info(
   1163      `Comparing results at index ${i}:` +
   1164        " actual=" +
   1165        JSON.stringify(actual) +
   1166        " expected=" +
   1167        JSON.stringify(expected)
   1168    );
   1169    Assert.equal(
   1170      actual.type,
   1171      expected.type,
   1172      `result.type at result index ${i}`
   1173    );
   1174    Assert.equal(
   1175      actual.source,
   1176      expected.source,
   1177      `result.source at result index ${i}`
   1178    );
   1179    Assert.equal(
   1180      actual.heuristic,
   1181      expected.heuristic,
   1182      `result.heuristic at result index ${i}`
   1183    );
   1184 
   1185    // Check payloads except for the quick suggest.
   1186    if (i != QUICK_SUGGEST_INDEX) {
   1187      Assert.deepEqual(
   1188        getPayload(context.results[i], { ignore: ["frecency", "lastVisit"] }),
   1189        getPayload(expectedResults[i], { ignore: ["frecency", "lastVisit"] }),
   1190        "Payload at index " + i
   1191      );
   1192    }
   1193  }
   1194 
   1195  // Check the quick suggest's payload excluding the timestamp-related
   1196  // properties.
   1197  let actualQuickSuggest = context.results[QUICK_SUGGEST_INDEX];
   1198  let ignore = ["sponsoredClickUrl", "url", "urlTimestampIndex"];
   1199  Assert.deepEqual(
   1200    getPayload(actualQuickSuggest, { ignore }),
   1201    getPayload(expectedQuickSuggest, { ignore }),
   1202    "Quick suggest payload excluding timestamp-related keys"
   1203  );
   1204 
   1205  // Now check the timestamps in the payload.
   1206  QuickSuggestTestUtils.assertTimestampsReplaced(actualQuickSuggest, {
   1207    url: TIMESTAMP_SUGGESTION_URL,
   1208    sponsoredClickUrl: TIMESTAMP_SUGGESTION_CLICK_URL,
   1209  });
   1210 
   1211  // Clean up.
   1212  UrlbarPrefs.clear("suggest.quicksuggest.all");
   1213  UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
   1214  await QuickSuggestTestUtils.forceSync();
   1215 
   1216  UrlbarPrefs.clear("suggest.searches");
   1217  await PlacesUtils.history.clear();
   1218 });
   1219 
   1220 // Tests `UrlbarResult` dismissal.
   1221 add_task(async function dismissResult() {
   1222  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1223  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1224  await QuickSuggestTestUtils.forceSync();
   1225 
   1226  let tests = [
   1227    // [suggestion, expected result]
   1228    [
   1229      REMOTE_SETTINGS_RESULTS[0],
   1230      QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 }),
   1231    ],
   1232    [REMOTE_SETTINGS_RESULTS[1], QuickSuggestTestUtils.wikipediaResult()],
   1233    [REMOTE_SETTINGS_RESULTS[2], expectedHttpResult()],
   1234    [REMOTE_SETTINGS_RESULTS[3], expectedHttpsResult()],
   1235  ];
   1236 
   1237  for (let [suggestion, expectedResult] of tests) {
   1238    info("Testing suggestion: " + JSON.stringify(suggestion));
   1239 
   1240    // Do a search to get a real `UrlbarResult` created for the suggestion.
   1241    let context = createContext(suggestion.keywords[0], {
   1242      providers: [UrlbarProviderQuickSuggest.name],
   1243      isPrivate: false,
   1244    });
   1245    await check_results({
   1246      context,
   1247      matches: [expectedResult],
   1248    });
   1249 
   1250    // Dismiss it.
   1251    await QuickSuggest.dismissResult(context.results[0]);
   1252    Assert.ok(
   1253      await QuickSuggest.isResultDismissed(context.results[0]),
   1254      "isResultDismissed should return true"
   1255    );
   1256    Assert.ok(
   1257      await QuickSuggest.canClearDismissedSuggestions(),
   1258      "canClearDismissedSuggestions should return true"
   1259    );
   1260 
   1261    // Do another search. The result shouldn't be added.
   1262    await check_results({
   1263      context: createContext(suggestion.keywords[0], {
   1264        providers: [UrlbarProviderQuickSuggest.name],
   1265        isPrivate: false,
   1266      }),
   1267      matches: [],
   1268    });
   1269 
   1270    await QuickSuggest.clearDismissedSuggestions();
   1271    Assert.ok(
   1272      !(await QuickSuggest.isResultDismissed(context.results[0])),
   1273      "isResultDismissed should return false"
   1274    );
   1275    Assert.ok(
   1276      !(await QuickSuggest.canClearDismissedSuggestions()),
   1277      "canClearDismissedSuggestions should return false"
   1278    );
   1279  }
   1280 });
   1281 
   1282 // Tests dismissing a `UrlbarResult` whose URL has a timestamp template.
   1283 add_task(async function dismissResultWithTimestamp() {
   1284  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1285  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1286  await QuickSuggestTestUtils.forceSync();
   1287 
   1288  // Do a search.
   1289  let context = createContext(TIMESTAMP_SEARCH_STRING, {
   1290    providers: [UrlbarProviderQuickSuggest.name],
   1291    isPrivate: false,
   1292  });
   1293  let controller = UrlbarTestUtils.newMockController();
   1294  await controller.startQuery(context);
   1295 
   1296  // Should be one quick suggest result.
   1297  Assert.equal(context.results.length, 1, "One result returned");
   1298  let result = context.results[0];
   1299 
   1300  QuickSuggestTestUtils.assertTimestampsReplaced(result, {
   1301    url: TIMESTAMP_SUGGESTION_URL,
   1302    sponsoredClickUrl: TIMESTAMP_SUGGESTION_CLICK_URL,
   1303  });
   1304 
   1305  Assert.ok(result.payload.originalUrl, "The actual result has an originalUrl");
   1306  Assert.equal(
   1307    result.payload.originalUrl,
   1308    REMOTE_SETTINGS_RESULTS[4].url,
   1309    "The actual result's originalUrl should be the raw suggestion URL with a timestamp template"
   1310  );
   1311 
   1312  // Dismiss the result.
   1313  await QuickSuggest.dismissResult(result);
   1314  Assert.ok(
   1315    await QuickSuggest.isResultDismissed(result),
   1316    "isResultDismissed should return true"
   1317  );
   1318  Assert.ok(
   1319    await QuickSuggest.canClearDismissedSuggestions(),
   1320    "canClearDismissedSuggestions should return true"
   1321  );
   1322 
   1323  // Do another search. The result shouldn't be added.
   1324  await check_results({
   1325    context: createContext(TIMESTAMP_SEARCH_STRING, {
   1326      providers: [UrlbarProviderQuickSuggest.name],
   1327      isPrivate: false,
   1328    }),
   1329    matches: [],
   1330  });
   1331 
   1332  await QuickSuggest.clearDismissedSuggestions();
   1333  Assert.ok(
   1334    !(await QuickSuggest.isResultDismissed(context.results[0])),
   1335    "isResultDismissed should return false"
   1336  );
   1337  Assert.ok(
   1338    !(await QuickSuggest.canClearDismissedSuggestions()),
   1339    "canClearDismissedSuggestions should return false"
   1340  );
   1341 });
   1342 
   1343 add_task(async function sponsoredPriority_normal() {
   1344  await doSponsoredPriorityTest({
   1345    searchWord: SPONSORED_SEARCH_STRING,
   1346    remoteSettingsData: [REMOTE_SETTINGS_RESULTS[0]],
   1347    expectedMatches: [expectedSponsoredPriorityResult()],
   1348  });
   1349 });
   1350 
   1351 add_task(async function sponsoredPriority_nonsponsoredSuggestion() {
   1352  // Not affect to except sponsored suggestion.
   1353  await doSponsoredPriorityTest({
   1354    searchWord: NONSPONSORED_SEARCH_STRING,
   1355    remoteSettingsData: [REMOTE_SETTINGS_RESULTS[1]],
   1356    expectedMatches: [QuickSuggestTestUtils.wikipediaResult()],
   1357  });
   1358 });
   1359 
   1360 add_task(async function sponsoredPriority_sponsoredIndex() {
   1361  await doSponsoredPriorityTest({
   1362    nimbusSettings: { quickSuggestSponsoredIndex: 2 },
   1363    searchWord: SPONSORED_SEARCH_STRING,
   1364    remoteSettingsData: [REMOTE_SETTINGS_RESULTS[0]],
   1365    expectedMatches: [expectedSponsoredPriorityResult()],
   1366  });
   1367 });
   1368 
   1369 async function doSponsoredPriorityTest({
   1370  remoteSettingsConfig = {},
   1371  nimbusSettings = {},
   1372  searchWord,
   1373  remoteSettingsData,
   1374  expectedMatches,
   1375 }) {
   1376  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1377  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1378  await QuickSuggestTestUtils.forceSync();
   1379 
   1380  const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({
   1381    ...nimbusSettings,
   1382    quickSuggestSponsoredPriority: true,
   1383  });
   1384 
   1385  await resetRemoteSettingsData(remoteSettingsData);
   1386  await QuickSuggestTestUtils.setConfig(remoteSettingsConfig);
   1387 
   1388  await check_results({
   1389    context: createContext(searchWord, {
   1390      providers: [UrlbarProviderQuickSuggest.name],
   1391      isPrivate: false,
   1392    }),
   1393    matches: expectedMatches,
   1394  });
   1395 
   1396  await cleanUpNimbusEnable();
   1397  await resetRemoteSettingsData();
   1398  await QuickSuggestTestUtils.setConfig(QuickSuggestTestUtils.DEFAULT_CONFIG);
   1399 }
   1400 
   1401 // When a Suggest best match and a tab-to-search (TTS) are shown in the same
   1402 // search, both will have a `suggestedIndex` value of 1. The TTS should appear
   1403 // first.
   1404 add_task(async function tabToSearch() {
   1405  // We'll use a sponsored priority result as the best match result. Different
   1406  // types of Suggest results can appear as best matches, and they all should
   1407  // have the same behavior.
   1408  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1409  await QuickSuggestTestUtils.forceSync();
   1410  UrlbarPrefs.set("suggest.quickactions", false);
   1411 
   1412  Services.prefs.setBoolPref(
   1413    "browser.urlbar.quicksuggest.sponsoredPriority",
   1414    true
   1415  );
   1416 
   1417  // Disable tab-to-search onboarding results so we get a regular TTS result,
   1418  // which we can test a little more easily with `makeSearchResult()`.
   1419  UrlbarPrefs.set("tabToSearch.onboard.interactionsLeft", 0);
   1420 
   1421  // Disable search suggestions so we don't need to expect them below.
   1422  Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
   1423 
   1424  // Install a test engine. The main part of its domain name needs to match the
   1425  // best match result too so we can trigger both its TTS and the best match.
   1426  let engineURL = `https://foo.${SPONSORED_SEARCH_STRING}.com/`;
   1427  let extension = await SearchTestUtils.installSearchExtension(
   1428    {
   1429      name: "Test",
   1430      search_url: engineURL,
   1431    },
   1432    { skipUnload: true }
   1433  );
   1434  let engine = Services.search.getEngineByName("Test");
   1435 
   1436  // Also need to add a visit to trigger TTS.
   1437  await PlacesTestUtils.addVisits(engineURL);
   1438 
   1439  let context = createContext(SPONSORED_SEARCH_STRING, {
   1440    isPrivate: false,
   1441  });
   1442  await check_results({
   1443    context,
   1444    matches: [
   1445      // search heuristic
   1446      makeSearchResult(context, {
   1447        engineName: Services.search.defaultEngine.name,
   1448        engineIconUri: await Services.search.defaultEngine.getIconURL(),
   1449        heuristic: true,
   1450      }),
   1451      // tab to search
   1452      makeSearchResult(context, {
   1453        engineName: engine.name,
   1454        engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
   1455        searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
   1456          engine.searchUrlDomain
   1457        ),
   1458        providesSearchMode: true,
   1459        query: "",
   1460        providerName: "UrlbarProviderTabToSearch",
   1461        satisfiesAutofillThreshold: true,
   1462      }),
   1463      // Suggest best match
   1464      expectedSponsoredPriorityResult(),
   1465      // visit
   1466      makeVisitResult(context, {
   1467        uri: engineURL,
   1468        title: `test visit for ${engineURL}`,
   1469      }),
   1470    ],
   1471  });
   1472 
   1473  await cleanupPlaces();
   1474  await extension.unload();
   1475 
   1476  UrlbarPrefs.clear("tabToSearch.onboard.interactionsLeft");
   1477  UrlbarPrefs.clear("suggest.quickactions");
   1478  Services.prefs.clearUserPref("browser.search.suggest.enabled");
   1479  Services.prefs.clearUserPref("browser.urlbar.quicksuggest.sponsoredPriority");
   1480 });
   1481 
   1482 // When a Suggest best match and a global action are shown in the same
   1483 // search, both will have a `suggestedIndex` value of 1. The global action should
   1484 // appear first.
   1485 add_task(async function globalAction() {
   1486  // We'll use a sponsored priority result as the best match result. Different
   1487  // types of Suggest results can appear as best matches, and they all should
   1488  // have the same behavior.
   1489  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1490  await QuickSuggestTestUtils.forceSync();
   1491 
   1492  Services.prefs.setBoolPref(
   1493    "browser.urlbar.quicksuggest.sponsoredPriority",
   1494    true
   1495  );
   1496 
   1497  // Disable search suggestions so we don't need to expect them below.
   1498  Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
   1499 
   1500  // Set prefs to prevent quick actions onboarding label from showing.
   1501  UrlbarPrefs.set("quickactions.timesToShowOnboardingLabel", 3);
   1502  UrlbarPrefs.set("quickactions.timesShownOnboardingLabel", 3);
   1503 
   1504  let engineURL = `https://example.com/`;
   1505  let extension = await SearchTestUtils.installSearchExtension(
   1506    {
   1507      name: "Amp",
   1508      search_url: engineURL,
   1509    },
   1510    { skipUnload: true }
   1511  );
   1512 
   1513  await PlacesTestUtils.addVisits(engineURL);
   1514 
   1515  let context = createContext(SPONSORED_SEARCH_STRING, {
   1516    isPrivate: false,
   1517  });
   1518 
   1519  await check_results({
   1520    context,
   1521    matches: [
   1522      // search heuristic
   1523      makeSearchResult(context, {
   1524        engineName: Services.search.defaultEngine.name,
   1525        engineIconUri: await Services.search.defaultEngine.getIconURL(),
   1526        heuristic: true,
   1527      }),
   1528 
   1529      // "Search with engine" global action.
   1530      makeGlobalActionsResult({
   1531        actionsResults: [
   1532          {
   1533            providerName: "ActionsProviderContextualSearch",
   1534          },
   1535        ],
   1536        query: "",
   1537        input: "",
   1538        inputLength: context.searchString.length,
   1539        showOnboardingLabel: false,
   1540      }),
   1541 
   1542      // Suggest best match
   1543      expectedSponsoredPriorityResult(),
   1544 
   1545      // visit
   1546      makeVisitResult(context, {
   1547        uri: engineURL,
   1548        title: `test visit for ${engineURL}`,
   1549      }),
   1550    ],
   1551  });
   1552 
   1553  await cleanupPlaces();
   1554  await extension.unload();
   1555 
   1556  UrlbarPrefs.clear("quickactions.timesToShowOnboardingLabel");
   1557  UrlbarPrefs.clear("quickactions.timesShownOnboardingLabel");
   1558  Services.prefs.clearUserPref("browser.search.suggest.enabled");
   1559  Services.prefs.clearUserPref("browser.urlbar.quicksuggest.sponsoredPriority");
   1560 });
   1561 
   1562 // The `Amp` and `Wikipedia` Rust providers should be passed to the Rust
   1563 // component when querying depending on whether sponsored and non-sponsored
   1564 // suggestions are enabled.
   1565 add_task(async function rustProviders() {
   1566  await doRustProvidersTests({
   1567    searchString: SPONSORED_AND_NONSPONSORED_SEARCH_STRING,
   1568    tests: [
   1569      {
   1570        prefs: {
   1571          "suggest.quicksuggest.all": true,
   1572          "suggest.quicksuggest.sponsored": true,
   1573        },
   1574        expectedUrls: [
   1575          "https://example.com/amp",
   1576          "https://example.com/wikipedia",
   1577        ],
   1578      },
   1579      {
   1580        prefs: {
   1581          "suggest.quicksuggest.all": true,
   1582          "suggest.quicksuggest.sponsored": false,
   1583        },
   1584        expectedUrls: ["https://example.com/wikipedia"],
   1585      },
   1586      {
   1587        prefs: {
   1588          "suggest.quicksuggest.all": false,
   1589          "suggest.quicksuggest.sponsored": true,
   1590        },
   1591        expectedUrls: [],
   1592      },
   1593      {
   1594        prefs: {
   1595          "suggest.quicksuggest.all": false,
   1596          "suggest.quicksuggest.sponsored": false,
   1597        },
   1598        expectedUrls: [],
   1599      },
   1600    ],
   1601  });
   1602 });
   1603 
   1604 // Tests the keyword/search-string-length threshold. Keywords/search strings
   1605 // must be at least two characters long to be matched.
   1606 add_task(async function keywordLengthThreshold() {
   1607  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1608  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1609  await QuickSuggestTestUtils.forceSync();
   1610 
   1611  let tests = [
   1612    ...ONE_CHAR_SEARCH_STRINGS.map(keyword => ({ keyword, expected: false })),
   1613    { keyword: "12", expected: true },
   1614    { keyword: "a longer keyword", expected: true },
   1615  ];
   1616 
   1617  for (let { keyword, expected } of tests) {
   1618    await check_results({
   1619      context: createContext(keyword, {
   1620        providers: [UrlbarProviderQuickSuggest.name],
   1621        isPrivate: false,
   1622      }),
   1623      matches: !expected
   1624        ? []
   1625        : [
   1626            QuickSuggestTestUtils.ampResult({
   1627              keyword,
   1628              title: "Suggestion with 1-char keyword",
   1629              url: "http://example.com/1-char-keyword",
   1630              originalUrl: "http://example.com/1-char-keyword",
   1631              suggestedIndex: -1,
   1632            }),
   1633          ],
   1634    });
   1635  }
   1636 });
   1637 
   1638 // AMP should be a top pick when `quicksuggest.ampTopPickCharThreshold` is
   1639 // non-zero and the query length meets the threshold; otherwise it should not be
   1640 // a top pick. It shouldn't matter whether the query is one of the suggestion's
   1641 // full keywords.
   1642 add_task(async function ampTopPickCharThreshold() {
   1643  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1644  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1645  await QuickSuggestTestUtils.forceSync();
   1646 
   1647  UrlbarPrefs.set(
   1648    "quicksuggest.ampTopPickCharThreshold",
   1649    "amp full keywo".length
   1650  );
   1651 
   1652  let tests = [
   1653    // No top pick: Matches an AMP suggestion but the query is shorter than the
   1654    // threshold.
   1655    { keyword: "amp full key", amp: true, isTopPick: false },
   1656    { keyword: "amp full keyw", amp: true, isTopPick: false },
   1657    { keyword: "                 amp full key", amp: true, isTopPick: false },
   1658    { keyword: "                 amp full keyw", amp: true, isTopPick: false },
   1659 
   1660    // Top pick: Matches an AMP suggestion and the query meets the threshold.
   1661    { keyword: "amp full keywo", amp: true, isTopPick: true },
   1662    { keyword: "amp full keywor", amp: true, isTopPick: true },
   1663    { keyword: "amp full keyword", amp: true, isTopPick: true },
   1664    { keyword: "AmP FuLl KeYwOrD", amp: true, isTopPick: true },
   1665    { keyword: "               amp full keywo", amp: true, isTopPick: true },
   1666    { keyword: "               amp full keywor", amp: true, isTopPick: true },
   1667    { keyword: "               amp full keyword", amp: true, isTopPick: true },
   1668    { keyword: "               AmP FuLl KeYwOrD", amp: true, isTopPick: true },
   1669 
   1670    // No top pick: Matches an AMP suggestion but the query is shorter than the
   1671    // threshold. It doesn't matter that the query is equal to the suggestion's
   1672    // full keyword.
   1673    { keyword: "xyz", fullKeyword: "xyz", amp: true, isTopPick: false },
   1674    { keyword: "XyZ", fullKeyword: "xyz", amp: true, isTopPick: false },
   1675    {
   1676      keyword: "                            xyz",
   1677      fullKeyword: "xyz",
   1678      amp: true,
   1679      isTopPick: false,
   1680    },
   1681    {
   1682      keyword: "                            XyZ",
   1683      fullKeyword: "xyz",
   1684      amp: true,
   1685      isTopPick: false,
   1686    },
   1687 
   1688    // No top pick: Matches a Wikipedia suggestion and some queries meet the
   1689    // threshold, but Wikipedia should not be top pick.
   1690    { keyword: "wikipedia full key", isTopPick: false },
   1691    { keyword: "wikipedia full keyw", isTopPick: false },
   1692    { keyword: "wikipedia full keywo", isTopPick: false },
   1693    { keyword: "wikipedia full keywor", isTopPick: false },
   1694    { keyword: "wikipedia full keyword", isTopPick: false },
   1695 
   1696    // No match: These shouldn't match anything at all since they have extra
   1697    // spaces at the end, but they're included for completeness.
   1698    { keyword: "                 amp full key   ", noMatch: true },
   1699    { keyword: "                 amp full keyw   ", noMatch: true },
   1700    { keyword: "               amp full keywo   ", noMatch: true },
   1701    { keyword: "               amp full keywor   ", noMatch: true },
   1702    { keyword: "               amp full keyword   ", noMatch: true },
   1703    { keyword: "               AmP FuLl KeYwOrD   ", noMatch: true },
   1704    { keyword: "                            xyz   ", noMatch: true },
   1705    { keyword: "                            XyZ   ", noMatch: true },
   1706  ];
   1707 
   1708  for (let { keyword, fullKeyword, amp, isTopPick, noMatch } of tests) {
   1709    fullKeyword ??= amp ? "amp full keyword" : "wikipedia full keyword";
   1710    info(
   1711      "Running subtest: " +
   1712        JSON.stringify({ keyword, fullKeyword, amp, isTopPick })
   1713    );
   1714 
   1715    let expectedResult;
   1716    if (!noMatch) {
   1717      if (!amp) {
   1718        expectedResult = QuickSuggestTestUtils.wikipediaResult({
   1719          keyword,
   1720          fullKeyword,
   1721          title: "Wikipedia suggestion with full keyword and prefix keywords",
   1722          url: "https://example.com/wikipedia-full-keyword",
   1723        });
   1724      } else if (isTopPick) {
   1725        expectedResult = QuickSuggestTestUtils.ampResult({
   1726          keyword,
   1727          fullKeyword,
   1728          title: "AMP suggestion with full keyword and prefix keywords",
   1729          url: "https://example.com/amp-full-keyword",
   1730          suggestedIndex: 1,
   1731          isSuggestedIndexRelativeToGroup: false,
   1732          isBestMatch: true,
   1733          descriptionL10n: null,
   1734        });
   1735      } else {
   1736        expectedResult = QuickSuggestTestUtils.ampResult({
   1737          suggestedIndex: -1,
   1738          keyword,
   1739          fullKeyword,
   1740          title: "AMP suggestion with full keyword and prefix keywords",
   1741          url: "https://example.com/amp-full-keyword",
   1742        });
   1743      }
   1744    }
   1745 
   1746    await check_results({
   1747      context: createContext(keyword, {
   1748        providers: [UrlbarProviderQuickSuggest.name],
   1749        isPrivate: false,
   1750      }),
   1751      matches: expectedResult ? [expectedResult] : [],
   1752    });
   1753  }
   1754 
   1755  UrlbarPrefs.clear("quicksuggest.ampTopPickCharThreshold");
   1756 });
   1757 
   1758 // AMP should not be shown as a top pick when the threshold is zero.
   1759 add_task(async function ampTopPickCharThreshold_zero() {
   1760  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1761  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1762  await QuickSuggestTestUtils.forceSync();
   1763 
   1764  UrlbarPrefs.set("quicksuggest.ampTopPickCharThreshold", 0);
   1765 
   1766  let tests = [
   1767    { keyword: "amp full key", amp: true },
   1768    { keyword: "amp full keyw", amp: true },
   1769    { keyword: "amp full keywo", amp: true },
   1770    { keyword: "amp full keywor", amp: true },
   1771    { keyword: "amp full keyword", amp: true },
   1772    { keyword: "AmP FuLl KeYwOrD", amp: true },
   1773    { keyword: "xyz", fullKeyword: "xyz", amp: true },
   1774    { keyword: "XyZ", fullKeyword: "xyz", amp: true },
   1775    { keyword: "wikipedia full key" },
   1776    { keyword: "wikipedia full keyw" },
   1777    { keyword: "wikipedia full keywo" },
   1778    { keyword: "wikipedia full keywor" },
   1779    { keyword: "wikipedia full keyword" },
   1780  ];
   1781 
   1782  for (let { keyword, fullKeyword, amp } of tests) {
   1783    fullKeyword ??= amp ? "amp full keyword" : "wikipedia full keyword";
   1784    info("Running subtest: " + JSON.stringify({ keyword, fullKeyword, amp }));
   1785 
   1786    let expectedResult;
   1787    if (!amp) {
   1788      expectedResult = QuickSuggestTestUtils.wikipediaResult({
   1789        keyword,
   1790        fullKeyword,
   1791        title: "Wikipedia suggestion with full keyword and prefix keywords",
   1792        url: "https://example.com/wikipedia-full-keyword",
   1793      });
   1794    } else {
   1795      expectedResult = QuickSuggestTestUtils.ampResult({
   1796        keyword,
   1797        fullKeyword,
   1798        title: "AMP suggestion with full keyword and prefix keywords",
   1799        url: "https://example.com/amp-full-keyword",
   1800        suggestedIndex: -1,
   1801      });
   1802    }
   1803 
   1804    await check_results({
   1805      context: createContext(keyword, {
   1806        providers: [UrlbarProviderQuickSuggest.name],
   1807        isPrivate: false,
   1808      }),
   1809      matches: [expectedResult],
   1810    });
   1811  }
   1812 
   1813  UrlbarPrefs.clear("quicksuggest.ampTopPickCharThreshold");
   1814 });
   1815 
   1816 // Tests `ampMatchingStrategy`.
   1817 add_task(async function ampMatchingStrategy() {
   1818  UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1819  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1820  await QuickSuggestTestUtils.forceSync();
   1821 
   1822  // Test each strategy in `AmpMatchingStrategy`. There are only a few.
   1823  for (let [key, value] of Object.entries(AmpMatchingStrategy)) {
   1824    await doAmpMatchingStrategyTest({ key, value });
   1825 
   1826    // Reset back to the default strategy just to make sure that works.
   1827    await doAmpMatchingStrategyTest({
   1828      key: "(default)",
   1829      value: 0,
   1830    });
   1831  }
   1832 
   1833  // Test an invalid strategy integer value. The default strategy should
   1834  // actually be used. First we need to set a valid non-default strategy.
   1835  await doAmpMatchingStrategyTest({
   1836    key: "FTS_AGAINST_TITLE",
   1837    value: AmpMatchingStrategy.FTS_AGAINST_TITLE,
   1838  });
   1839  await doAmpMatchingStrategyTest({
   1840    key: "(invalid)",
   1841    value: 99,
   1842    expectedStrategy: 0,
   1843  });
   1844 
   1845  Services.prefs.clearUserPref(
   1846    "browser.urlbar.quicksuggest.ampMatchingStrategy"
   1847  );
   1848  await QuickSuggestTestUtils.forceSync();
   1849 });
   1850 
   1851 async function doAmpMatchingStrategyTest({
   1852  key,
   1853  value,
   1854  expectedStrategy = value,
   1855 }) {
   1856  info("Doing ampMatchingStrategy test: " + JSON.stringify({ key, value }));
   1857 
   1858  let sandbox = sinon.createSandbox();
   1859  let ingestSpy = sandbox.spy(QuickSuggest.rustBackend._test_store, "ingest");
   1860 
   1861  // Set the strategy. It should trigger ingest. (Assuming it's different from
   1862  // the current strategy. If it's not, ingest won't happen.)
   1863  Services.prefs.setIntPref(
   1864    "browser.urlbar.quicksuggest.ampMatchingStrategy",
   1865    value
   1866  );
   1867 
   1868  let ingestCall = await TestUtils.waitForCondition(() => {
   1869    return ingestSpy.getCalls().find(call => {
   1870      let ingestConstraints = call.args[0];
   1871      return ingestConstraints?.providers[0] == SuggestionProvider.AMP;
   1872    });
   1873  }, "Waiting for ingest() to be called with Amp provider");
   1874 
   1875  // Check the provider constraints in the ingest constraints.
   1876  let { providerConstraints } = ingestCall.args[0];
   1877  if (!expectedStrategy) {
   1878    Assert.ok(
   1879      !providerConstraints,
   1880      "ingest() should not have been called with provider constraints"
   1881    );
   1882  } else {
   1883    Assert.ok(
   1884      providerConstraints,
   1885      "ingest() should have been called with provider constraints"
   1886    );
   1887    Assert.strictEqual(
   1888      providerConstraints.ampAlternativeMatching,
   1889      expectedStrategy,
   1890      "ampAlternativeMatching should have been set"
   1891    );
   1892  }
   1893 
   1894  // Now do a query to make sure it also uses the correct provider constraints.
   1895  // No need to use `check_results()`. We only need to trigger a query, and
   1896  // checking the right results unnecessarily complicates things.
   1897  let querySpy = sandbox.spy(
   1898    QuickSuggest.rustBackend._test_store,
   1899    "queryWithMetrics"
   1900  );
   1901 
   1902  let controller = UrlbarTestUtils.newMockController();
   1903  await controller.startQuery(
   1904    createContext(SPONSORED_SEARCH_STRING, {
   1905      providers: [UrlbarProviderQuickSuggest.name],
   1906      isPrivate: false,
   1907    })
   1908  );
   1909 
   1910  let queryCalls = querySpy.getCalls();
   1911  Assert.equal(queryCalls.length, 1, "query() should have been called once");
   1912 
   1913  let query = queryCalls[0].args[0];
   1914  Assert.ok(query, "query() should have been called with a query object");
   1915  Assert.ok(
   1916    query.providerConstraints,
   1917    "query() should have been called with provider constraints"
   1918  );
   1919 
   1920  if (!expectedStrategy) {
   1921    Assert.strictEqual(
   1922      query.providerConstraints.ampAlternativeMatching,
   1923      null,
   1924      "ampAlternativeMatching should not have been set on query provider constraints"
   1925    );
   1926  } else {
   1927    Assert.strictEqual(
   1928      query.providerConstraints.ampAlternativeMatching,
   1929      expectedStrategy,
   1930      "ampAlternativeMatching should have been set on query provider constraints"
   1931    );
   1932  }
   1933 
   1934  sandbox.restore();
   1935 }
   1936 
   1937 add_task(async function offline_amp_disabled() {
   1938  for (let pref of [
   1939    "suggest.quicksuggest.all",
   1940    "suggest.quicksuggest.sponsored",
   1941    "amp.featureGate",
   1942    "suggest.amp",
   1943  ]) {
   1944    info("Testing with pref: " + pref);
   1945 
   1946    UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1947    UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   1948    await QuickSuggestTestUtils.forceSync();
   1949 
   1950    // First make sure we can match an AMP suggestion.
   1951    await check_results({
   1952      context: createContext(SPONSORED_SEARCH_STRING, {
   1953        providers: [UrlbarProviderQuickSuggest.name],
   1954        isPrivate: false,
   1955      }),
   1956      matches: [QuickSuggestTestUtils.ampResult({ suggestedIndex: -1 })],
   1957    });
   1958 
   1959    // Now disable the pref and try again.
   1960    UrlbarPrefs.set(pref, false);
   1961    await QuickSuggestTestUtils.forceSync();
   1962 
   1963    await check_results({
   1964      context: createContext(SPONSORED_SEARCH_STRING, {
   1965        providers: [UrlbarProviderQuickSuggest.name],
   1966        isPrivate: false,
   1967      }),
   1968      matches: [],
   1969    });
   1970 
   1971    UrlbarPrefs.clear(pref);
   1972  }
   1973 
   1974  await QuickSuggestTestUtils.forceSync();
   1975 });
   1976 
   1977 add_task(async function offline_wikipedia_disabled() {
   1978  for (let pref of [
   1979    "suggest.quicksuggest.all",
   1980    "wikipedia.featureGate",
   1981    "suggest.wikipedia",
   1982  ]) {
   1983    info("Testing with pref: " + pref);
   1984 
   1985    UrlbarPrefs.set("suggest.quicksuggest.all", true);
   1986    UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
   1987    await QuickSuggestTestUtils.forceSync();
   1988 
   1989    // First make sure we can match a Wikipedia suggestion.
   1990    await check_results({
   1991      context: createContext(NONSPONSORED_SEARCH_STRING, {
   1992        providers: [UrlbarProviderQuickSuggest.name],
   1993        isPrivate: false,
   1994      }),
   1995      matches: [QuickSuggestTestUtils.wikipediaResult()],
   1996    });
   1997 
   1998    // Now disable the pref and try again.
   1999    UrlbarPrefs.set(pref, false);
   2000    await QuickSuggestTestUtils.forceSync();
   2001 
   2002    await check_results({
   2003      context: createContext(NONSPONSORED_SEARCH_STRING, {
   2004        providers: [UrlbarProviderQuickSuggest.name],
   2005        isPrivate: false,
   2006      }),
   2007      matches: [],
   2008    });
   2009 
   2010    UrlbarPrefs.clear(pref);
   2011  }
   2012 
   2013  await QuickSuggestTestUtils.forceSync();
   2014 });
   2015 
   2016 add_task(async function online_amp_disabled() {
   2017  await doMerinoTest(async () => {
   2018    for (let pref of [
   2019      "suggest.quicksuggest.all",
   2020      "suggest.quicksuggest.sponsored",
   2021      "amp.featureGate",
   2022      "suggest.amp",
   2023    ]) {
   2024      info("Testing with pref: " + pref);
   2025 
   2026      UrlbarPrefs.set("suggest.quicksuggest.all", true);
   2027      UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
   2028      await QuickSuggestTestUtils.forceSync();
   2029 
   2030      // First make sure we can match an AMP suggestion.
   2031      await check_results({
   2032        context: createContext("test", {
   2033          providers: [UrlbarProviderQuickSuggest.name],
   2034          isPrivate: false,
   2035        }),
   2036        matches: [
   2037          QuickSuggestTestUtils.ampResult({
   2038            source: "merino",
   2039            provider: "adm",
   2040            icon: "https://example.com/amp-icon",
   2041            iabCategory: "22 - Shopping",
   2042            requestId: "request_id",
   2043            suggestedIndex: -1,
   2044          }),
   2045        ],
   2046      });
   2047 
   2048      // Now disable the pref and try again.
   2049      UrlbarPrefs.set(pref, false);
   2050      await QuickSuggestTestUtils.forceSync();
   2051 
   2052      // Unless the pref was `all`, the Wikipedia Merino suggestion should now
   2053      // be matched.
   2054      let expected =
   2055        pref == "suggest.quicksuggest.all"
   2056          ? []
   2057          : [
   2058              QuickSuggestTestUtils.wikipediaResult({
   2059                source: "merino",
   2060                provider: "wikipedia",
   2061                telemetryType: "wikipedia",
   2062                icon: "https://example.com/wikipedia-icon",
   2063              }),
   2064            ];
   2065      await check_results({
   2066        context: createContext("test", {
   2067          providers: [UrlbarProviderQuickSuggest.name],
   2068          isPrivate: false,
   2069        }),
   2070        matches: expected,
   2071      });
   2072 
   2073      UrlbarPrefs.clear(pref);
   2074    }
   2075 
   2076    await QuickSuggestTestUtils.forceSync();
   2077  });
   2078 });
   2079 
   2080 add_task(async function online_wikipedia_disabled() {
   2081  await doMerinoTest(async () => {
   2082    for (let pref of [
   2083      "suggest.quicksuggest.all",
   2084      "wikipedia.featureGate",
   2085      "suggest.wikipedia",
   2086    ]) {
   2087      info("Testing with pref: " + pref);
   2088 
   2089      UrlbarPrefs.set("suggest.quicksuggest.all", true);
   2090      UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
   2091      await QuickSuggestTestUtils.forceSync();
   2092 
   2093      // First make sure we can match a Wikipedia suggestion.
   2094      await check_results({
   2095        context: createContext("test", {
   2096          providers: [UrlbarProviderQuickSuggest.name],
   2097          isPrivate: false,
   2098        }),
   2099        matches: [
   2100          QuickSuggestTestUtils.wikipediaResult({
   2101            source: "merino",
   2102            provider: "wikipedia",
   2103            telemetryType: "wikipedia",
   2104            icon: "https://example.com/wikipedia-icon",
   2105          }),
   2106        ],
   2107      });
   2108 
   2109      // Now disable the pref and try again.
   2110      UrlbarPrefs.set(pref, false);
   2111      await QuickSuggestTestUtils.forceSync();
   2112 
   2113      await check_results({
   2114        context: createContext("test", {
   2115          providers: [UrlbarProviderQuickSuggest.name],
   2116          isPrivate: false,
   2117        }),
   2118        matches: [],
   2119      });
   2120 
   2121      UrlbarPrefs.clear(pref);
   2122    }
   2123 
   2124    await QuickSuggestTestUtils.forceSync();
   2125  });
   2126 });
   2127 
   2128 async function doMerinoTest(callback) {
   2129  UrlbarPrefs.set("quicksuggest.online.available", true);
   2130  UrlbarPrefs.set("quicksuggest.online.enabled", true);
   2131  await MerinoTestUtils.server.start();
   2132 
   2133  MerinoTestUtils.server.response.body.suggestions = MERINO_SUGGESTIONS;
   2134 
   2135  await callback();
   2136 
   2137  await MerinoTestUtils.server.stop();
   2138  UrlbarPrefs.clear("quicksuggest.online.available");
   2139  UrlbarPrefs.clear("quicksuggest.online.enabled");
   2140 }
   2141 
   2142 add_task(async function mergeRustProviderConstraints() {
   2143  let tests = [
   2144    {
   2145      a: null,
   2146      b: null,
   2147      expected: null,
   2148    },
   2149 
   2150    // b is null
   2151    {
   2152      a: {},
   2153      b: null,
   2154      expected: {},
   2155    },
   2156    {
   2157      a: { ampAlternativeMatching: 1 },
   2158      b: null,
   2159      expected: { ampAlternativeMatching: 1 },
   2160    },
   2161    {
   2162      a: { dynamicSuggestionTypes: [] },
   2163      b: null,
   2164      expected: { dynamicSuggestionTypes: [] },
   2165    },
   2166    {
   2167      a: { dynamicSuggestionTypes: ["aaa"] },
   2168      b: null,
   2169      expected: { dynamicSuggestionTypes: ["aaa"] },
   2170    },
   2171    {
   2172      a: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2173      b: null,
   2174      expected: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2175    },
   2176    {
   2177      a: { dynamicSuggestionTypes: ["aaa", "bbb"], ampAlternativeMatching: 1 },
   2178      b: null,
   2179      expected: {
   2180        dynamicSuggestionTypes: ["aaa", "bbb"],
   2181        ampAlternativeMatching: 1,
   2182      },
   2183    },
   2184 
   2185    // b is an empty object
   2186    {
   2187      a: {},
   2188      b: {},
   2189      expected: {},
   2190    },
   2191    {
   2192      a: { ampAlternativeMatching: 1 },
   2193      b: {},
   2194      expected: { ampAlternativeMatching: 1 },
   2195    },
   2196    {
   2197      a: { dynamicSuggestionTypes: [] },
   2198      b: {},
   2199      expected: { dynamicSuggestionTypes: [] },
   2200    },
   2201    {
   2202      a: { dynamicSuggestionTypes: ["aaa"] },
   2203      b: {},
   2204      expected: { dynamicSuggestionTypes: ["aaa"] },
   2205    },
   2206    {
   2207      a: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2208      b: {},
   2209      expected: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2210    },
   2211    {
   2212      a: { dynamicSuggestionTypes: ["aaa", "bbb"], ampAlternativeMatching: 1 },
   2213      b: {},
   2214      expected: {
   2215        dynamicSuggestionTypes: ["aaa", "bbb"],
   2216        ampAlternativeMatching: 1,
   2217      },
   2218    },
   2219 
   2220    // b is { ampAlternativeMatching: 1 }
   2221    {
   2222      a: {},
   2223      b: { ampAlternativeMatching: 1 },
   2224      expected: { ampAlternativeMatching: 1 },
   2225    },
   2226    {
   2227      a: { ampAlternativeMatching: 1 },
   2228      b: { ampAlternativeMatching: 1 },
   2229      expected: { ampAlternativeMatching: 1 },
   2230    },
   2231    {
   2232      a: { dynamicSuggestionTypes: [] },
   2233      b: { ampAlternativeMatching: 1 },
   2234      expected: { dynamicSuggestionTypes: [], ampAlternativeMatching: 1 },
   2235    },
   2236    {
   2237      a: { dynamicSuggestionTypes: ["aaa"] },
   2238      b: { ampAlternativeMatching: 1 },
   2239      expected: { dynamicSuggestionTypes: ["aaa"], ampAlternativeMatching: 1 },
   2240    },
   2241    {
   2242      a: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2243      b: { ampAlternativeMatching: 1 },
   2244      expected: {
   2245        dynamicSuggestionTypes: ["aaa", "bbb"],
   2246        ampAlternativeMatching: 1,
   2247      },
   2248    },
   2249    {
   2250      a: { dynamicSuggestionTypes: ["aaa", "bbb"], ampAlternativeMatching: 1 },
   2251      b: { ampAlternativeMatching: 1 },
   2252      expected: {
   2253        dynamicSuggestionTypes: ["aaa", "bbb"],
   2254        ampAlternativeMatching: 1,
   2255      },
   2256    },
   2257 
   2258    // b is { dynamicSuggestionTypes: [] }
   2259    {
   2260      a: {},
   2261      b: { dynamicSuggestionTypes: [] },
   2262      expected: { dynamicSuggestionTypes: [] },
   2263    },
   2264    {
   2265      a: { ampAlternativeMatching: 1 },
   2266      b: { dynamicSuggestionTypes: [] },
   2267      expected: { dynamicSuggestionTypes: [], ampAlternativeMatching: 1 },
   2268    },
   2269    {
   2270      a: { dynamicSuggestionTypes: [] },
   2271      b: { dynamicSuggestionTypes: [] },
   2272      expected: { dynamicSuggestionTypes: [] },
   2273    },
   2274    {
   2275      a: { dynamicSuggestionTypes: ["aaa"] },
   2276      b: { dynamicSuggestionTypes: [] },
   2277      expected: { dynamicSuggestionTypes: ["aaa"] },
   2278    },
   2279    {
   2280      a: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2281      b: { dynamicSuggestionTypes: [] },
   2282      expected: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2283    },
   2284    {
   2285      a: { dynamicSuggestionTypes: ["aaa", "bbb"], ampAlternativeMatching: 1 },
   2286      b: { dynamicSuggestionTypes: [] },
   2287      expected: {
   2288        dynamicSuggestionTypes: ["aaa", "bbb"],
   2289        ampAlternativeMatching: 1,
   2290      },
   2291    },
   2292 
   2293    // b is { dynamicSuggestionTypes: ["bbb"] }
   2294    {
   2295      a: {},
   2296      b: { dynamicSuggestionTypes: ["bbb"] },
   2297      expected: { dynamicSuggestionTypes: ["bbb"] },
   2298    },
   2299    {
   2300      a: { ampAlternativeMatching: 1 },
   2301      b: { dynamicSuggestionTypes: ["bbb"] },
   2302      expected: { dynamicSuggestionTypes: ["bbb"], ampAlternativeMatching: 1 },
   2303    },
   2304    {
   2305      a: { dynamicSuggestionTypes: [] },
   2306      b: { dynamicSuggestionTypes: ["bbb"] },
   2307      expected: { dynamicSuggestionTypes: ["bbb"] },
   2308    },
   2309    {
   2310      a: { dynamicSuggestionTypes: ["aaa"] },
   2311      b: { dynamicSuggestionTypes: ["bbb"] },
   2312      expected: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2313    },
   2314    {
   2315      a: { dynamicSuggestionTypes: ["bbb"] },
   2316      b: { dynamicSuggestionTypes: ["bbb"] },
   2317      expected: { dynamicSuggestionTypes: ["bbb"] },
   2318    },
   2319    {
   2320      a: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2321      b: { dynamicSuggestionTypes: ["bbb"] },
   2322      expected: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2323    },
   2324    {
   2325      a: { dynamicSuggestionTypes: ["aaa", "bbb"], ampAlternativeMatching: 1 },
   2326      b: { dynamicSuggestionTypes: ["bbb"] },
   2327      expected: {
   2328        dynamicSuggestionTypes: ["aaa", "bbb"],
   2329        ampAlternativeMatching: 1,
   2330      },
   2331    },
   2332 
   2333    // b is { dynamicSuggestionTypes: ["bbb", "ddd"] }
   2334    {
   2335      a: {},
   2336      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2337      expected: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2338    },
   2339    {
   2340      a: { ampAlternativeMatching: 1 },
   2341      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2342      expected: {
   2343        dynamicSuggestionTypes: ["bbb", "ddd"],
   2344        ampAlternativeMatching: 1,
   2345      },
   2346    },
   2347    {
   2348      a: { dynamicSuggestionTypes: [] },
   2349      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2350      expected: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2351    },
   2352    {
   2353      a: { dynamicSuggestionTypes: ["aaa"] },
   2354      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2355      expected: { dynamicSuggestionTypes: ["aaa", "bbb", "ddd"] },
   2356    },
   2357    {
   2358      a: { dynamicSuggestionTypes: ["bbb"] },
   2359      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2360      expected: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2361    },
   2362    {
   2363      a: { dynamicSuggestionTypes: ["aaa", "bbb"] },
   2364      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2365      expected: { dynamicSuggestionTypes: ["aaa", "bbb", "ddd"] },
   2366    },
   2367    {
   2368      a: { dynamicSuggestionTypes: ["aaa", "bbb", "ccc"] },
   2369      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2370      expected: { dynamicSuggestionTypes: ["aaa", "bbb", "ccc", "ddd"] },
   2371    },
   2372    {
   2373      a: {
   2374        dynamicSuggestionTypes: ["aaa", "bbb", "ccc"],
   2375        ampAlternativeMatching: 1,
   2376      },
   2377      b: { dynamicSuggestionTypes: ["bbb", "ddd"] },
   2378      expected: {
   2379        dynamicSuggestionTypes: ["aaa", "bbb", "ccc", "ddd"],
   2380        ampAlternativeMatching: 1,
   2381      },
   2382    },
   2383  ];
   2384 
   2385  for (let { a, b, expected } of tests) {
   2386    for (let [first, second] of [
   2387      [a, b],
   2388      [b, a],
   2389    ]) {
   2390      info("Doing test: " + JSON.stringify({ first, second }));
   2391      let actual = SuggestBackendRust.mergeProviderConstraints(first, second);
   2392      Assert.deepEqual(
   2393        actual,
   2394        expected,
   2395        "Expected merged constraints with " + JSON.stringify({ first, second })
   2396      );
   2397    }
   2398  }
   2399 });
   2400 
   2401 async function resetRemoteSettingsData(data = REMOTE_SETTINGS_RESULTS) {
   2402  let isAmp = suggestion => suggestion.iab_category == "22 - Shopping";
   2403  await QuickSuggestTestUtils.setRemoteSettingsRecords([
   2404    {
   2405      collection: QuickSuggestTestUtils.RS_COLLECTION.AMP,
   2406      type: QuickSuggestTestUtils.RS_TYPE.AMP,
   2407      attachment: data.filter(isAmp),
   2408    },
   2409    {
   2410      collection: QuickSuggestTestUtils.RS_COLLECTION.OTHER,
   2411      type: QuickSuggestTestUtils.RS_TYPE.WIKIPEDIA,
   2412      attachment: data.filter(s => !isAmp(s)),
   2413    },
   2414  ]);
   2415 }