tor-browser

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

test_quicksuggest_importantDatesSuggestions.js (14371B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const REMOTE_SETTINGS_RECORDS = [
      7  // US region, en-US locale
      8  {
      9    type: "dynamic-suggestions",
     10    suggestion_type: "important_dates",
     11    filter_expression: "env.country == 'US' && env.locale == 'en-US'",
     12    attachment: [
     13      {
     14        keywords: [["event", [" 1"]]],
     15        data: {
     16          result: {
     17            isImportantDate: true,
     18            payload: {
     19              dates: ["2025-03-05", "2026-02-18"],
     20              name: "Event 1",
     21            },
     22          },
     23        },
     24      },
     25      {
     26        keywords: [["multi d", ["ay event"]]],
     27        data: {
     28          result: {
     29            isImportantDate: true,
     30            payload: {
     31              dates: [
     32                ["2025-06-10", "2025-06-20"],
     33                ["2026-06-10", "2026-06-20"],
     34              ],
     35              name: "Multi Day Event",
     36            },
     37          },
     38        },
     39      },
     40    ],
     41  },
     42 
     43  // DE region, de locale
     44  {
     45    type: "dynamic-suggestions",
     46    suggestion_type: "important_dates",
     47    filter_expression: "env.country == 'DE' && env.locale == 'de'",
     48    attachment: [
     49      {
     50        keywords: ["de de event"],
     51        data: {
     52          result: {
     53            isImportantDate: true,
     54            payload: {
     55              dates: ["2026-01-01"],
     56              name: "DE de Event",
     57            },
     58          },
     59        },
     60      },
     61    ],
     62  },
     63 
     64  // DE region, en-US locale
     65  {
     66    type: "dynamic-suggestions",
     67    suggestion_type: "important_dates",
     68    filter_expression: "env.country == 'DE' && env.locale == 'en-US'",
     69    attachment: [
     70      {
     71        keywords: ["de en-us event"],
     72        data: {
     73          result: {
     74            isImportantDate: true,
     75            payload: {
     76              dates: ["2026-01-02"],
     77              name: "DE en-US Event",
     78            },
     79          },
     80        },
     81      },
     82    ],
     83  },
     84 
     85  // other non-important-dates records
     86  {
     87    type: "dynamic-suggestions",
     88    suggestion_type: "other_suggestions",
     89    attachment: [
     90      {
     91        keywords: [["event", [" 2"]]],
     92        data: {
     93          result: {
     94            isBestMatch: true,
     95            payload: {
     96              title: "Top Pick Suggestion 1",
     97              url: "https://foo.com/",
     98              description:
     99                "A suggestion that just so happens to have the same keyword",
    100            },
    101          },
    102        },
    103      },
    104    ],
    105  },
    106  {
    107    type: "dynamic-suggestions",
    108    suggestion_type: "other_suggestions",
    109    attachment: [
    110      {
    111        keywords: [["event", [" 3"]]],
    112        data: {
    113          result: {
    114            isBestMatch: true,
    115            payload: {
    116              title: "Top Pick Suggestion 2",
    117              url: "https://foo.com/",
    118              description:
    119                "Another suggestion that just so happens to have the same keyword",
    120            },
    121          },
    122        },
    123      },
    124    ],
    125  },
    126 ];
    127 
    128 let SystemDate;
    129 
    130 add_setup(async function () {
    131  await QuickSuggestTestUtils.ensureQuickSuggestInit({
    132    remoteSettingsRecords: REMOTE_SETTINGS_RECORDS,
    133    prefs: [["quicksuggest.dynamicSuggestionTypes", "other_suggestions"]],
    134  });
    135 
    136  // All tasks will assume US region, en-US locale by default.
    137  await QuickSuggestTestUtils.setRegionAndLocale({
    138    region: "US",
    139    locale: "en-US",
    140  });
    141 
    142  await Services.search.init();
    143 
    144  SystemDate = Cu.getGlobalForObject(QuickSuggestTestUtils).Date;
    145 });
    146 
    147 add_task(async function telemetryType() {
    148  Assert.equal(
    149    QuickSuggest.getFeature(
    150      "ImportantDatesSuggestions"
    151    ).getSuggestionTelemetryType({}),
    152    "important_dates",
    153    "Telemetry type should be 'important_dates'"
    154  );
    155 });
    156 
    157 // Important dates should respect the `importantDates.featureGate` and
    158 // `suggest.importantDates` prefs.
    159 add_task(async function enablingPrefs() {
    160  setTime("2025-03-01T00:00");
    161 
    162  let query = "event 1";
    163  let expected = makeExpectedResult({
    164    date: "Wednesday, March 5, 2025",
    165    descriptionL10n: {
    166      id: "urlbar-result-dates-countdown",
    167      args: { name: "Event 1", daysUntilStart: 4 },
    168    },
    169  });
    170 
    171  for (let pref of ["importantDates.featureGate", "suggest.importantDates"]) {
    172    info("Doing enabling-pref test: " + pref);
    173 
    174    // First make sure the result is matched.
    175    await checkDatesResults(query, expected);
    176 
    177    // Now disable the pref and do another search.
    178    UrlbarPrefs.set(pref, false);
    179    await checkDatesResults(query, null);
    180 
    181    // Clean up.
    182    UrlbarPrefs.set(pref, true);
    183    await QuickSuggestTestUtils.forceSync();
    184  }
    185 });
    186 
    187 // Important dates are considered "utility" suggestions and not part of the
    188 // Suggest brand, so they should be enabled regardless of the `all` and
    189 // sponsored Suggest prefs.
    190 add_task(async function neitherSponsoredNorNonsponsored() {
    191  setTime("2025-03-01T00:00");
    192 
    193  let query = "event 1";
    194  let expected = makeExpectedResult({
    195    date: "Wednesday, March 5, 2025",
    196    descriptionL10n: {
    197      id: "urlbar-result-dates-countdown",
    198      args: { name: "Event 1", daysUntilStart: 4 },
    199    },
    200  });
    201 
    202  for (let all of [true, false]) {
    203    for (let sponsored of [true, false]) {
    204      info(
    205        "Doing all/sponsored pref test: " + JSON.stringify({ all, sponsored })
    206      );
    207 
    208      UrlbarPrefs.set("suggest.quicksuggest.all", all);
    209      UrlbarPrefs.set("suggest.quicksuggest.sponsored", sponsored);
    210      await QuickSuggestTestUtils.forceSync();
    211 
    212      await checkDatesResults(query, expected);
    213    }
    214  }
    215 
    216  UrlbarPrefs.clear("suggest.quicksuggest.all");
    217  UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
    218  await QuickSuggestTestUtils.forceSync();
    219 });
    220 
    221 add_task(async function fourDaysBefore() {
    222  setTime("2025-03-01T00:00");
    223 
    224  let query = "event 1";
    225  let expected = makeExpectedResult({
    226    date: "Wednesday, March 5, 2025",
    227    descriptionL10n: {
    228      id: "urlbar-result-dates-countdown",
    229      args: { name: "Event 1", daysUntilStart: 4 },
    230    },
    231  });
    232 
    233  await checkDatesResults(query, expected);
    234 });
    235 
    236 add_task(async function onDayOfEvent() {
    237  setTime("2025-03-05T00:00");
    238 
    239  let query = "event 1";
    240  let expected = makeExpectedResult({
    241    date: "Wednesday, March 5, 2025",
    242    descriptionL10n: {
    243      id: "urlbar-result-dates-today",
    244      args: { name: "Event 1" },
    245    },
    246  });
    247 
    248  await checkDatesResults(query, expected);
    249 });
    250 
    251 add_task(async function oneDayAfter() {
    252  setTime("2025-03-06T00:00");
    253 
    254  let query = "event 1";
    255  let expected = makeExpectedResult({
    256    // Should select the event in the next year.
    257    date: "Wednesday, February 18, 2026",
    258    // Since the event is over SHOW_COUNTDOWN_THRESHOLD_DAYS
    259    // days away, it should not display the countdown.
    260    description: "Event 1",
    261  });
    262 
    263  await checkDatesResults(query, expected);
    264 });
    265 
    266 add_task(async function afterAllInstances() {
    267  setTime("2027-01-01T00:00");
    268 
    269  let query = "event 1";
    270  let expected = null;
    271 
    272  await checkDatesResults(query, expected);
    273 });
    274 
    275 add_task(async function beforeMultiDay() {
    276  setTime("2025-06-09T23:59");
    277 
    278  let query = "multi day event";
    279  let expected = makeExpectedResult({
    280    date: "June 10 – 20, 2025",
    281    descriptionL10n: {
    282      id: "urlbar-result-dates-countdown-range",
    283      args: { name: "Multi Day Event", daysUntilStart: 1 },
    284    },
    285  });
    286 
    287  await checkDatesResults(query, expected);
    288 });
    289 
    290 add_task(async function duringMultiDay() {
    291  setTime("2025-06-19T00:00");
    292 
    293  let query = "multi day event";
    294  let expected = makeExpectedResult({
    295    date: "June 10 – 20, 2025",
    296    descriptionL10n: {
    297      id: "urlbar-result-dates-ongoing",
    298      args: { name: "Multi Day Event", daysUntilEnd: 1 },
    299    },
    300  });
    301 
    302  await checkDatesResults(query, expected);
    303 });
    304 
    305 add_task(async function lastDayDuringMultiDay() {
    306  setTime("2025-06-20T00:00");
    307 
    308  let query = "multi day event";
    309  let expected = makeExpectedResult({
    310    date: "June 10 – 20, 2025",
    311    descriptionL10n: {
    312      id: "urlbar-result-dates-ends-today",
    313      args: { name: "Multi Day Event" },
    314    },
    315  });
    316 
    317  await checkDatesResults(query, expected);
    318 });
    319 
    320 // Test whether the date suggestion is before the other
    321 // isBestMatch suggestion.
    322 add_task(async function testTwoSuggestions() {
    323  // `other_suggestions` is nonsponsored so it depends on the `all` pref.
    324  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    325  await QuickSuggestTestUtils.forceSync();
    326 
    327  setTime("2025-03-01T00:00");
    328 
    329  // 1 date suggestion and 2 other suggestions match this, but
    330  // one of the two other suggestions should be deduped.
    331  let query = "event";
    332  let expectedDateSuggestion = makeExpectedResult({
    333    date: "Wednesday, March 5, 2025",
    334    descriptionL10n: {
    335      id: "urlbar-result-dates-countdown",
    336      args: { daysUntilStart: 4, name: "Event 1" },
    337    },
    338  });
    339 
    340  let expectedOtherSuggestion = {
    341    type: UrlbarUtils.RESULT_TYPE.URL,
    342    source: UrlbarUtils.RESULT_SOURCE.SEARCH,
    343    heuristic: false,
    344    isBestMatch: true,
    345    isRichSuggestion: true,
    346    suggestedIndex: 1,
    347    payload: {
    348      source: "rust",
    349      provider: "Dynamic",
    350      suggestionType: "other_suggestions",
    351      title: "Top Pick Suggestion 1",
    352      url: "https://foo.com/",
    353      telemetryType: "other_suggestions",
    354      description: "A suggestion that just so happens to have the same keyword",
    355      isManageable: true,
    356      isSponsored: false,
    357      helpUrl: QuickSuggest.HELP_URL,
    358    },
    359  };
    360 
    361  await checkDatesResults(query, [
    362    expectedDateSuggestion,
    363    expectedOtherSuggestion,
    364  ]);
    365 
    366  UrlbarPrefs.clear("suggest.quicksuggest.all");
    367  await QuickSuggestTestUtils.forceSync();
    368 });
    369 
    370 add_task(async function otherRegionsAndLocales() {
    371  let tests = [
    372    // DE region, de locale (should match)
    373    {
    374      region: "DE",
    375      locale: "de",
    376      matchingQuery: "de de event",
    377      expectedResultData: {
    378        date: "Donnerstag, 1. Januar 2026",
    379        description: "DE de Event",
    380      },
    381      nonMatchingQueries: [
    382        "de en-US event", // DE region, en-US locale
    383        "event 1", // US region, en-US locale
    384      ],
    385    },
    386 
    387    // DE region, en-US locale (should match)
    388    {
    389      region: "DE",
    390      locale: "en-US",
    391      matchingQuery: "de en-us event",
    392      expectedResultData: {
    393        date: "Friday, January 2, 2026",
    394        description: "DE en-US Event",
    395      },
    396      nonMatchingQueries: [
    397        "de de event", // DE region, de locale
    398        "event 1", // US region, en-US locale
    399      ],
    400    },
    401 
    402    // XX region, en-US locale (should not match)
    403    {
    404      region: "XX",
    405      locale: "en-US",
    406      matchingQuery: null,
    407      nonMatchingQueries: [
    408        "de de event", // DE region, de locale
    409        "de en-us event", // DE region, en-US locale
    410        "event 1", // US region, en-US locale
    411      ],
    412    },
    413 
    414    // US region, de locale (should not match)
    415    {
    416      region: "US",
    417      locale: "de",
    418      matchingQuery: null,
    419      nonMatchingQueries: [
    420        "de de event", // DE region, de locale
    421        "de en-us event", // DE region, en-US locale
    422        "event 1", // US region, en-US locale
    423      ],
    424    },
    425  ];
    426 
    427  for (let {
    428    region,
    429    locale,
    430    matchingQuery,
    431    expectedResultData,
    432    nonMatchingQueries,
    433  } of tests) {
    434    info("Doing subtest: " + JSON.stringify({ region, locale }));
    435 
    436    await QuickSuggestTestUtils.withRegionAndLocale({
    437      region,
    438      locale,
    439      callback: async () => {
    440        Assert.equal(
    441          UrlbarPrefs.get("quickSuggestEnabled"),
    442          !!matchingQuery,
    443          "quickSuggestEnabled should be enabled as expected"
    444        );
    445        Assert.equal(
    446          UrlbarPrefs.get("importantDates.featureGate"),
    447          !!matchingQuery,
    448          "importantDates.featureGate should be enabled as expected"
    449        );
    450 
    451        setTime("2025-03-01T00:00");
    452 
    453        if (matchingQuery) {
    454          info("Checking matching query: " + matchingQuery);
    455          await checkDatesResults(
    456            matchingQuery,
    457            makeExpectedResult(expectedResultData)
    458          );
    459        }
    460 
    461        for (let query of nonMatchingQueries) {
    462          info("Checking non-matching query: " + query);
    463          await checkDatesResults(query, null);
    464        }
    465      },
    466    });
    467  }
    468 });
    469 
    470 /**
    471 * Stubs the Date object of the system global to use timeStr
    472 * when the constructor is called without arguments.
    473 *
    474 * @param {string} timeStr
    475 *   An ISO time string.
    476 */
    477 function setTime(timeStr) {
    478  let DateStub = function (...args) {
    479    if (!args.length) {
    480      return new SystemDate(timeStr);
    481    }
    482    return new SystemDate(...args);
    483  };
    484  DateStub.prototype = SystemDate.prototype;
    485 
    486  Object.getOwnPropertyNames(SystemDate).forEach(prop => {
    487    const desc = Object.getOwnPropertyDescriptor(SystemDate, prop);
    488    Object.defineProperty(DateStub, prop, desc);
    489  });
    490 
    491  Cu.getGlobalForObject(QuickSuggestTestUtils).Date = DateStub;
    492 }
    493 
    494 async function checkDatesResults(query, expected) {
    495  info(
    496    "Doing query: " +
    497      JSON.stringify({
    498        query,
    499        expected,
    500      })
    501  );
    502 
    503  let queryContext = createContext(query, {
    504    providers: [UrlbarProviderQuickSuggest.name],
    505    isPrivate: false,
    506  });
    507  await check_results({
    508    context: queryContext,
    509    matches: expected ? [expected].flat() : [],
    510  });
    511 
    512  if (expected?.payload) {
    513    info("Check the highligts");
    514    let { value, highlights } =
    515      queryContext.results[0].getDisplayableValueAndHighlights("title", {
    516        tokens: queryContext.tokens,
    517      });
    518    Assert.equal(expected.payload.title, value);
    519    Assert.deepEqual([[0, expected.payload.title.length]], highlights);
    520  }
    521 }
    522 
    523 function makeExpectedResult({
    524  date,
    525  description,
    526  descriptionL10n,
    527  isSponsored = false,
    528  isBestMatch = true,
    529  isRichSuggestion = undefined,
    530 }) {
    531  let name = description ?? descriptionL10n.args.name;
    532  return {
    533    type: UrlbarUtils.RESULT_TYPE.SEARCH,
    534    source: UrlbarUtils.RESULT_SOURCE.SEARCH,
    535    heuristic: false,
    536    isBestMatch,
    537    suggestedIndex: 1,
    538    isRichSuggestion,
    539    payload: {
    540      title: date,
    541      engine: Services.search.defaultEngine.name,
    542      query: name,
    543      lowerCaseSuggestion: name.toLocaleLowerCase(),
    544      description,
    545      descriptionL10n,
    546      isSponsored,
    547      telemetryType: "important_dates",
    548      source: "rust",
    549      provider: "Dynamic",
    550      suggestionType: "important_dates",
    551      isManageable: true,
    552      isBlockable: true,
    553      helpUrl: QuickSuggest.HELP_URL,
    554      icon: "chrome://browser/skin/calendar-24.svg",
    555    },
    556  };
    557 }