Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: browser/components/urlbar/tests/quicksuggest/unit/xpcshell.toml
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Tests addon quick suggest results.
"use strict";
ChromeUtils.defineESModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs",
ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.sys.mjs",
});
AddonTestUtils.init(this, false);
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"42",
"42"
);
// TODO: Firefox no longer uses `rating` and `number_of_ratings` but they are
// still present in Merino and RS suggestions, so they are included here for
// greater accuracy. We should remove them from Merino, RS, and tests.
const MERINO_SUGGESTIONS = [
{
provider: "amo",
icon: "icon",
url: "https://example.com/merino-addon",
title: "title",
description: "description",
custom_details: {
amo: {
rating: "5",
number_of_ratings: "1234567",
guid: "test@addon",
},
},
},
];
const REMOTE_SETTINGS_RESULTS = [
{
type: "amo-suggestions",
attachment: [
{
url: "https://example.com/first-addon",
guid: "first@addon",
icon: "https://example.com/first-addon.svg",
title: "First Addon",
rating: "4.7",
keywords: ["first", "1st", "two words", "aa b c"],
description: "Description for the First Addon",
number_of_ratings: 1256,
score: 0.25,
},
{
url: "https://example.com/second-addon",
guid: "second@addon",
icon: "https://example.com/second-addon.svg",
title: "Second Addon",
rating: "1.7",
keywords: ["second", "2nd"],
description: "Description for the Second Addon",
number_of_ratings: 256,
score: 0.25,
},
{
url: "https://example.com/third-addon",
guid: "third@addon",
icon: "https://example.com/third-addon.svg",
title: "Third Addon",
rating: "3.7",
keywords: ["third", "3rd"],
description: "Description for the Third Addon",
number_of_ratings: 3,
score: 0.25,
},
{
guid: "fourth@addon",
icon: "https://example.com/fourth-addon.svg",
title: "Fourth Addon",
rating: "4.7",
keywords: ["fourth", "4th"],
description: "Description for the Fourth Addon",
number_of_ratings: 4,
score: 0.25,
},
],
},
];
add_setup(async function init() {
await AddonTestUtils.promiseStartupManager();
// Disable search suggestions so we don't hit the network.
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
await QuickSuggestTestUtils.ensureQuickSuggestInit({
remoteSettingsRecords: REMOTE_SETTINGS_RESULTS,
merinoSuggestions: MERINO_SUGGESTIONS,
prefs: [["suggest.quicksuggest.all", true]],
});
});
add_task(async function telemetryType() {
Assert.equal(
QuickSuggest.getFeature("AddonSuggestions").getSuggestionTelemetryType({}),
"amo",
"Telemetry type should be 'amo'"
);
});
// When quick suggest prefs are disabled, addon suggestions should be disabled.
add_task(async function prefsDisabled() {
let prefs = [
"quicksuggest.enabled",
"addons.featureGate",
"suggest.quicksuggest.all",
"suggest.addons",
];
for (let pref of prefs) {
info("Testing pref: " + pref);
// Before disabling the pref, first make sure the suggestion is added.
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
suggestion: MERINO_SUGGESTIONS[0],
source: "merino",
provider: "amo",
}),
],
});
// Now disable the pref.
UrlbarPrefs.set(pref, false);
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [],
});
UrlbarPrefs.set(pref, true);
await QuickSuggestTestUtils.forceSync();
}
});
// Check wheather the addon suggestions will be shown by the setup of Nimbus
// variable.
add_task(async function nimbus() {
// Disable the fature gate.
UrlbarPrefs.set("addons.featureGate", false);
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [],
});
// Enable by Nimbus.
const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({
addonsFeatureGate: true,
});
await QuickSuggestTestUtils.forceSync();
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
suggestion: MERINO_SUGGESTIONS[0],
source: "merino",
provider: "amo",
}),
],
});
await cleanUpNimbusEnable();
// Enable locally.
UrlbarPrefs.set("addons.featureGate", true);
await QuickSuggestTestUtils.forceSync();
// Disable by Nimbus.
const cleanUpNimbusDisable = await UrlbarTestUtils.initNimbusFeature({
addonsFeatureGate: false,
});
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [],
});
await cleanUpNimbusDisable();
// Revert.
UrlbarPrefs.clear("addons.featureGate");
await QuickSuggestTestUtils.forceSync();
});
add_task(async function hideIfAlreadyInstalled() {
// Show suggestion.
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
suggestion: MERINO_SUGGESTIONS[0],
source: "merino",
provider: "amo",
}),
],
});
// Install an addon for the suggestion.
const xpi = ExtensionTestCommon.generateXPI({
manifest: {
browser_specific_settings: {
gecko: { id: "test@addon" },
},
},
});
const addon = await AddonManager.installTemporaryAddon(xpi);
// Show suggestion for the addon installed.
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [],
});
await addon.uninstall();
xpi.remove(false);
});
add_task(async function remoteSettings() {
const testCases = [
{
input: "f",
expected: null,
},
{
input: "fi",
expected: null,
},
{
input: "fir",
expected: null,
},
{
input: "firs",
expected: null,
},
{
input: "first",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "1st",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "t",
expected: null,
},
{
input: "tw",
expected: null,
},
{
input: "two",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "two ",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "two w",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "two wo",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "two wor",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "two word",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "two words",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "aa",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "aa ",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "aa b",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "aa b ",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "aa b c",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
},
{
input: "second",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1],
}),
},
{
input: "2nd",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1],
}),
},
{
input: "third",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[2],
}),
},
{
input: "3rd",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[2],
}),
},
{
input: "fourth",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[3],
setUtmParams: false,
}),
},
{
input: "FoUrTh",
expected: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[3],
setUtmParams: false,
}),
},
];
// Disable Merino so we trigger only remote settings suggestions.
UrlbarPrefs.set("quicksuggest.online.enabled", false);
for (let { input, expected } of testCases) {
await check_results({
context: createContext(input, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: expected ? [expected] : [],
});
}
UrlbarPrefs.clear("quicksuggest.online.enabled");
});
add_task(async function merinoIsTopPick() {
const suggestion = JSON.parse(JSON.stringify(MERINO_SUGGESTIONS[0]));
// is_top_pick is specified as false.
suggestion.is_top_pick = false;
MerinoTestUtils.server.response.body.suggestions = [suggestion];
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
suggestion,
source: "merino",
provider: "amo",
}),
],
});
// is_top_pick is undefined.
delete suggestion.is_top_pick;
MerinoTestUtils.server.response.body.suggestions = [suggestion];
await check_results({
context: createContext("test", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [
makeExpectedResult({
suggestion,
source: "merino",
provider: "amo",
}),
],
});
});
// Tests the "Dismiss" command: a dismissed suggestion shouldn't be added.
add_task(async function dismiss() {
// Disable Merino suggestions to make this task simpler.
UrlbarPrefs.set("quicksuggest.online.enabled", false);
await doDismissOneTest({
result: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
command: "dismiss",
feature: QuickSuggest.getFeature("AddonSuggestions"),
queriesForDismissals: [
{
query: REMOTE_SETTINGS_RESULTS[0].attachment[0].keywords[0],
},
],
queriesForOthers: [
{
query: REMOTE_SETTINGS_RESULTS[0].attachment[1].keywords[0],
expectedResults: [
makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1],
}),
],
},
],
});
UrlbarPrefs.clear("quicksuggest.online.enabled");
});
// Tests the "Not interested" command: all addon suggestions should be disabled
// and not added anymore.
add_task(async function notInterested() {
await doDismissAllTest({
result: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
command: "not_interested",
feature: QuickSuggest.getFeature("AddonSuggestions"),
pref: "suggest.addons",
queries: [
{
query: REMOTE_SETTINGS_RESULTS[0].attachment[0].keywords[0],
},
{
query: REMOTE_SETTINGS_RESULTS[0].attachment[1].keywords[0],
expectedResults: [
makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1],
}),
],
},
],
});
});
// Tests the "show less frequently" behavior.
add_task(async function showLessFrequently() {
await doShowLessFrequentlyTest({
feature: QuickSuggest.getFeature("AddonSuggestions"),
keyword: "two words",
minKeywordLengthPref: "addons.minKeywordLength",
showLessFrequentlyCountPref: "addons.showLessFrequentlyCount",
expectedResult: makeExpectedResult({
suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0],
}),
});
});
// The `Amo` Rust provider should be passed to the Rust component when querying
// depending on whether addon suggestions are enabled.
add_task(async function rustProviders() {
await doRustProvidersTests({
searchString: "first",
tests: [
{
prefs: {
"suggest.addons": true,
},
expectedUrls: ["https://example.com/first-addon"],
},
{
prefs: {
"suggest.addons": false,
},
expectedUrls: [],
},
],
});
UrlbarPrefs.clear("suggest.addons");
await QuickSuggestTestUtils.forceSync();
});
function makeExpectedResult({
suggestion,
source,
provider,
setUtmParams = true,
}) {
return QuickSuggestTestUtils.amoResult({
source,
provider,
setUtmParams,
title: suggestion.title,
description: suggestion.description,
url: suggestion.url,
originalUrl: suggestion.url,
icon: suggestion.icon,
});
}
/**
* Tests the `show_less_frequently` command for a given feature.
*
* @param {object} options
* Options object.
* @param {SuggestFeature} options.feature
* The feature being tested.
* @param {SuggestFeature} options.keyword
* The keyword to search for. Its length must be at least 3, and each prefix
* starting at `keyword.length - 3` must match a suggestion.
* @param {object} options.expectedResult
* The result that is expected to match the keyword and its prefixes.
* @param {string} options.minKeywordLengthPref
* The name of the pref for the min keyword length being tested.
* @param {string} options.showLessFrequentlyCountPref
* The name of the pref for the "show less frequently" count being tested.
*/
async function doShowLessFrequentlyTest({
feature,
keyword,
expectedResult,
minKeywordLengthPref,
showLessFrequentlyCountPref,
}) {
let showLessFrequentlyCap = 3;
if (keyword.length < showLessFrequentlyCap) {
throw new Error(
"keyword must be long enough to 'Show less frequently' enough times"
);
}
await QuickSuggestTestUtils.withConfig({
config: { show_less_frequently_cap: showLessFrequentlyCap },
callback: async () => {
// Do `showLessFrequentlyCap` searches and trigger the
// `show_less_frequently` command after each one. In each iteration, the
// length of `searchString` increases by 1.
for (let count = 0; count < showLessFrequentlyCap; count++) {
let searchString = keyword.substring(
0,
keyword.length - showLessFrequentlyCap + count + 1
);
info(
"Doing search: " +
JSON.stringify({
count,
searchString,
})
);
// Check prefs and other values before searching.
Assert.equal(
UrlbarPrefs.get(minKeywordLengthPref),
count == 0 ? 0 : searchString.length,
"minKeywordLength pref should be correct before search " + count
);
Assert.equal(
feature.showLessFrequentlyCount,
count,
"showLessFrequentlyCount should be correct before search " + count
);
Assert.equal(
UrlbarPrefs.get(showLessFrequentlyCountPref),
count,
"showLessFrequentlyCount pref should be correct before search " +
count
);
Assert.ok(
feature.canShowLessFrequently,
"canShowLessFrequently should be correct before search " + count
);
// Do the search.
await check_results({
context: createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [expectedResult],
});
// Trigger the command.
triggerCommand({
feature,
searchString,
command: "show_less_frequently",
result: expectedResult,
expectedCountsByCall: {
acknowledgeFeedback: 1,
invalidateResultMenuCommands:
count == showLessFrequentlyCap - 1 ? 1 : 0,
},
});
// The same search should now match nothing.
await check_results({
context: createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: [],
});
}
// Check prefs and other values now that all searches are done.
Assert.equal(
UrlbarPrefs.get(minKeywordLengthPref),
keyword.length + 1,
"minKeywordLength pref should be correct after all searches"
);
Assert.equal(
feature.showLessFrequentlyCount,
showLessFrequentlyCap,
"showLessFrequentlyCount should be correct after all searches"
);
Assert.equal(
UrlbarPrefs.get(showLessFrequentlyCountPref),
showLessFrequentlyCap,
"showLessFrequentlyCap pref should be correct after all searches"
);
Assert.ok(
!feature.canShowLessFrequently,
"canShowLessFrequently should be correct after all searches"
);
},
});
UrlbarPrefs.clear(minKeywordLengthPref);
UrlbarPrefs.clear(showLessFrequentlyCountPref);
}