summaryrefslogtreecommitdiffstats
path: root/toolkit/components/search/tests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/search/tests')
-rw-r--r--toolkit/components/search/tests/xpcshell/data/search-config-v2.json2
-rw-r--r--toolkit/components/search/tests/xpcshell/data1/search-config-v2.json8
-rw-r--r--toolkit/components/search/tests/xpcshell/head_search.js95
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js2
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js26
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js26
-rw-r--r--toolkit/components/search/tests/xpcshell/test_appProvided_engine.js182
-rw-r--r--toolkit/components/search/tests/xpcshell/test_appProvided_icons.js69
-rw-r--r--toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js324
-rw-r--r--toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js42
-rw-r--r--toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js61
-rw-r--r--toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js142
-rw-r--r--toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js5
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchSuggest.js4
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js4
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js2
-rw-r--r--toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js85
-rw-r--r--toolkit/components/search/tests/xpcshell/test_settings_persist.js2
-rw-r--r--toolkit/components/search/tests/xpcshell/xpcshell.toml15
19 files changed, 1014 insertions, 82 deletions
diff --git a/toolkit/components/search/tests/xpcshell/data/search-config-v2.json b/toolkit/components/search/tests/xpcshell/data/search-config-v2.json
index 569e16dfe4..bca7cc3bdf 100644
--- a/toolkit/components/search/tests/xpcshell/data/search-config-v2.json
+++ b/toolkit/components/search/tests/xpcshell/data/search-config-v2.json
@@ -112,6 +112,7 @@
"recordType": "engine",
"identifier": "engine-resourceicon",
"base": {
+ "classification": "general",
"name": "engine-resourceicon",
"urls": {
"search": {
@@ -151,6 +152,7 @@
"recordType": "engine",
"identifier": "engine-reordered",
"base": {
+ "classification": "general",
"name": "Test search engine (Reordered)",
"urls": {
"search": {
diff --git a/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json b/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json
index 98bdfa26ff..ac5f7f77cd 100644
--- a/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json
+++ b/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json
@@ -9,7 +9,7 @@
"searchTermParamName": "q"
}
},
- "classification": "unknown"
+ "classification": "general"
},
"variants": [{ "environment": { "allRegionsAndLocales": true } }],
"identifier": "engine1",
@@ -24,7 +24,7 @@
"searchTermParamName": "q"
}
},
- "classification": "unknown"
+ "classification": "general"
},
"variants": [{ "environment": { "allRegionsAndLocales": true } }],
"identifier": "engine2",
@@ -39,7 +39,7 @@
"searchTermParamName": "q"
}
},
- "classification": "unknown"
+ "classification": "general"
},
"variants": [
{
@@ -58,7 +58,7 @@
"searchTermParamName": "q"
}
},
- "classification": "unknown"
+ "classification": "general"
},
"variants": [
{
diff --git a/toolkit/components/search/tests/xpcshell/head_search.js b/toolkit/components/search/tests/xpcshell/head_search.js
index 1c0504e277..72bc90185c 100644
--- a/toolkit/components/search/tests/xpcshell/head_search.js
+++ b/toolkit/components/search/tests/xpcshell/head_search.js
@@ -307,6 +307,101 @@ async function setupRemoteSettings() {
}
/**
+ * Reads the specified file from the data directory and returns its contents as
+ * an Uint8Array.
+ *
+ * @param {string} filename
+ * The name of the file to read.
+ * @returns {Promise<Uint8Array>}
+ * The contents of the file in an Uint8Array.
+ */
+async function getFileDataBuffer(filename) {
+ return IOUtils.read(PathUtils.join(do_get_cwd().path, "data", filename));
+}
+
+/**
+ * Creates a mock attachment record for use in remote settings related tests.
+ *
+ * @param {object} item
+ * An object containing the details of the attachment.
+ * @param {string} item.filename
+ * The name of the attachmnet file in the data directory.
+ * @param {string[]} item.engineIdentifiers
+ * The engine identifiers for the attachment.
+ * @param {number} item.imageSize
+ * The size of the image.
+ * @param {string} [item.id]
+ * The ID to use for the record. If not provided, a new UUID will be generated.
+ * @param {number} [item.lastModified]
+ * The last modified time for the record. Defaults to the current time.
+ */
+async function mockRecordWithAttachment({
+ filename,
+ engineIdentifiers,
+ imageSize,
+ id = Services.uuid.generateUUID().toString(),
+ lastModified = Date.now(),
+}) {
+ let buffer = await getFileDataBuffer(filename);
+
+ // Generate a hash.
+ let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ hasher.init(Ci.nsICryptoHash.SHA256);
+ hasher.update(buffer, buffer.length);
+
+ let hash = hasher.finish(false);
+ hash = Array.from(hash, (_, i) =>
+ ("0" + hash.charCodeAt(i).toString(16)).slice(-2)
+ ).join("");
+
+ let record = {
+ id,
+ engineIdentifiers,
+ imageSize,
+ attachment: {
+ hash,
+ location: `${filename}`,
+ filename,
+ size: buffer.byteLength,
+ mimetype: "application/json",
+ },
+ last_modified: lastModified,
+ };
+
+ let attachment = {
+ record,
+ blob: new Blob([buffer]),
+ };
+
+ return { record, attachment };
+}
+
+/**
+ * Inserts an attachment record into the remote settings collection.
+ *
+ * @param {RemoteSettingsClient} client
+ * The remote settings client to use.
+ * @param {object} item
+ * An object containing the details of the attachment - see mockRecordWithAttachment.
+ * @param {boolean} [addAttachmentToCache]
+ * Whether to add the attachment file to the cache. Defaults to true.
+ */
+async function insertRecordIntoCollection(
+ client,
+ item,
+ addAttachmentToCache = true
+) {
+ let { record, attachment } = await mockRecordWithAttachment(item);
+ await client.db.create(record);
+ if (addAttachmentToCache) {
+ await client.attachments.cacheImpl.set(record.id, attachment);
+ }
+ await client.db.importChanges({}, record.last_modified);
+}
+
+/**
* Helper function that sets up a server and respnds to region
* fetch requests.
*
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
index 72c4d4f04f..c58ac1c25b 100644
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
@@ -115,7 +115,7 @@ class SearchConfigTest {
async setup(version = "42.0") {
if (SearchUtils.newSearchConfigEnabled) {
updateAppInfo({
- name: "XPCShell",
+ name: "firefox",
ID: "xpcshell@tests.mozilla.org",
version,
platformVersion: version,
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js
index 4024385729..eab28a7ba9 100644
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js
@@ -14,6 +14,9 @@ const test = new SearchConfigTest({
{
locales: ["fr"],
},
+ {
+ regions: ["be", "ch", "es", "fr", "it", "nl"],
+ },
],
},
details: [
@@ -28,9 +31,30 @@ const test = new SearchConfigTest({
});
add_setup(async function () {
- await test.setup();
+ if (SearchUtils.newSearchConfigEnabled) {
+ await test.setup();
+ } else {
+ await test.setup("124.0");
+ }
});
add_task(async function test_searchConfig_qwant() {
await test.run();
});
+
+add_task(
+ { skip_if: () => SearchUtils.newSearchConfigEnabled },
+ async function test_searchConfig_qwant_pre124() {
+ const version = "123.0";
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ version,
+ version
+ );
+ // For pre-124, Qwant is not available in the extra regions.
+ test._config.available.included.pop();
+
+ await test.run();
+ }
+);
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
index 86686b62f7..f727d60719 100644
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
@@ -172,6 +172,32 @@ add_task(
add_task(
{ skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_search_config_valid_partner_codes() {
+ delete SearchUtils.newSearchConfigEnabled;
+ SearchUtils.newSearchConfigEnabled = true;
+
+ let selector = new SearchEngineSelector(() => {});
+
+ for (let entry of await selector.getEngineConfiguration()) {
+ if (entry.recordType == "engine") {
+ for (let variant of entry.variants) {
+ if (
+ "partnerCode" in variant &&
+ "distributions" in variant.environment
+ ) {
+ Assert.ok(
+ variant.telemetrySuffix,
+ `${entry.identifier} should have a telemetrySuffix when a distribution is specified with a partnerCode.`
+ );
+ }
+ }
+ }
+ }
+ }
+);
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
async function test_search_config_override_validates_to_schema() {
let selector = new SearchEngineSelector(() => {});
diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_engine.js b/toolkit/components/search/tests/xpcshell/test_appProvided_engine.js
new file mode 100644
index 0000000000..56297a9d2c
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_appProvided_engine.js
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that ensure application provided engines have all base fields set up
+ * correctly from the search configuration.
+ */
+
+"use strict";
+
+let CONFIG = [
+ {
+ identifier: "testEngine",
+ recordType: "engine",
+ base: {
+ aliases: ["testEngine1", "testEngine2"],
+ charset: "EUC-JP",
+ classification: "general",
+ name: "testEngine name",
+ partnerCode: "pc",
+ urls: {
+ search: {
+ base: "https://example.com/1",
+ // Method defaults to GET
+ params: [
+ { name: "partnerCode", value: "{partnerCode}" },
+ { name: "starbase", value: "Regula I" },
+ { name: "experiment", value: "Genesis" },
+ {
+ name: "accessPoint",
+ searchAccessPoint: {
+ addressbar: "addressbar",
+ contextmenu: "contextmenu",
+ homepage: "homepage",
+ newtab: "newtab",
+ searchbar: "searchbar",
+ },
+ },
+ ],
+ searchTermParamName: "search",
+ },
+ suggestions: {
+ base: "https://example.com/2",
+ method: "POST",
+ searchTermParamName: "suggestions",
+ },
+ trending: {
+ base: "https://example.com/3",
+ searchTermParamName: "trending",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ },
+ {
+ identifier: "testOtherValuesEngine",
+ recordType: "engine",
+ base: {
+ classification: "unknown",
+ name: "testOtherValuesEngine name",
+ urls: {
+ search: {
+ base: "https://example.com/1",
+ searchTermParamName: "search",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine_no_initial_icon",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
+add_setup(async function () {
+ await SearchTestUtils.useTestEngines("simple-engines", null, CONFIG);
+ await Services.search.init();
+});
+
+add_task(async function test_engine_with_all_params_set() {
+ let engine = Services.search.getEngineById(
+ "testEngine@search.mozilla.orgdefault"
+ );
+ Assert.ok(engine, "Should have found the engine");
+
+ Assert.equal(
+ engine.name,
+ "testEngine name",
+ "Should have the correct engine name"
+ );
+ Assert.deepEqual(
+ engine.aliases,
+ ["@testEngine1", "@testEngine2"],
+ "Should have the correct aliases"
+ );
+ Assert.ok(
+ engine.isGeneralPurposeEngine,
+ "Should be a general purpose engine"
+ );
+ Assert.equal(
+ engine.wrappedJSObject.queryCharset,
+ "EUC-JP",
+ "Should have the correct encoding"
+ );
+
+ let submission = engine.getSubmission("test");
+ Assert.equal(
+ submission.uri.spec,
+ "https://example.com/1?partnerCode=pc&starbase=Regula%20I&experiment=Genesis&accessPoint=searchbar&search=test",
+ "Should have the correct search URL"
+ );
+ Assert.ok(!submission.postData, "Should not have postData for a GET url");
+
+ let suggestSubmission = engine.getSubmission(
+ "test",
+ SearchUtils.URL_TYPE.SUGGEST_JSON
+ );
+ Assert.equal(
+ suggestSubmission.uri.spec,
+ "https://example.com/2",
+ "Should have the correct suggestion URL"
+ );
+ Assert.equal(
+ suggestSubmission.postData.data.data,
+ "suggestions=test",
+ "Should have the correct postData for a POST URL"
+ );
+
+ let trendingSubmission = engine.getSubmission(
+ "test",
+ SearchUtils.URL_TYPE.TRENDING_JSON
+ );
+ Assert.equal(
+ trendingSubmission.uri.spec,
+ "https://example.com/3?trending=test"
+ );
+ Assert.ok(!submission.postData, "Should not have postData for a GET url");
+});
+
+add_task(async function test_engine_with_some_params_set() {
+ let engine = Services.search.getEngineById(
+ "testOtherValuesEngine@search.mozilla.orgdefault"
+ );
+ Assert.ok(engine, "Should have found the engine");
+
+ Assert.equal(
+ engine.name,
+ "testOtherValuesEngine name",
+ "Should have the correct engine name"
+ );
+ Assert.deepEqual(engine.aliases, [], "Should have no aliases");
+ Assert.ok(
+ !engine.isGeneralPurposeEngine,
+ "Should not be a general purpose engine"
+ );
+ Assert.equal(
+ engine.wrappedJSObject.queryCharset,
+ "UTF-8",
+ "Should default to UTF-8 charset"
+ );
+ Assert.equal(
+ engine.getSubmission("test").uri.spec,
+ "https://example.com/1?search=test",
+ "Should have the correct search URL"
+ );
+ Assert.equal(
+ engine.getSubmission("test", SearchUtils.URL_TYPE.SUGGEST_JSON),
+ null,
+ "Should not have a suggestions URL"
+ );
+ Assert.equal(
+ engine.getSubmission("test", SearchUtils.URL_TYPE.TRENDING_JSON),
+ null,
+ "Should not have a trending URL"
+ );
+});
diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js b/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js
index e4d8033993..f6cc8b2415 100644
--- a/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js
+++ b/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js
@@ -53,7 +53,11 @@ let TESTS = [
icons: [
{
filename: "remoteIcon.ico",
- engineIdentifiers: ["engine_non_default_sized_icon"],
+ engineIdentifiers: [
+ // This also tests multiple engine idenifiers works.
+ "enterprise_shuttle",
+ "engine_non_default_sized_icon",
+ ],
imageSize: 32,
},
],
@@ -77,64 +81,6 @@ let TESTS = [
},
];
-async function getFileDataBuffer(filename) {
- let data = await IOUtils.read(
- PathUtils.join(do_get_cwd().path, "data", filename)
- );
- return new TextEncoder().encode(data).buffer;
-}
-
-async function mockRecordWithAttachment({
- filename,
- engineIdentifiers,
- imageSize,
-}) {
- let buffer = await getFileDataBuffer(filename);
-
- let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance(
- Ci.nsIArrayBufferInputStream
- );
- stream.setData(buffer, 0, buffer.byteLength);
-
- // Generate a hash.
- let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
- Ci.nsICryptoHash
- );
- hasher.init(Ci.nsICryptoHash.SHA256);
- hasher.updateFromStream(stream, -1);
- let hash = hasher.finish(false);
- hash = Array.from(hash, (_, i) =>
- ("0" + hash.charCodeAt(i).toString(16)).slice(-2)
- ).join("");
-
- let record = {
- id: Services.uuid.generateUUID().toString(),
- engineIdentifiers,
- imageSize,
- attachment: {
- hash,
- location: `main-workspace/search-config-icons/${filename}`,
- filename,
- size: buffer.byteLength,
- mimetype: "application/json",
- },
- };
-
- let attachment = {
- record,
- blob: new Blob([buffer]),
- };
-
- return { record, attachment };
-}
-
-async function insertRecordIntoCollection(client, db, item) {
- let { record, attachment } = await mockRecordWithAttachment(item);
- await db.create(record);
- await client.attachments.cacheImpl.set(record.id, attachment);
- await db.importChanges({}, Date.now());
-}
-
add_setup(async function () {
let client = RemoteSettings("search-config-icons");
let db = client.db;
@@ -159,10 +105,7 @@ add_setup(async function () {
if ("icons" in test) {
for (let icon of test.icons) {
- await insertRecordIntoCollection(client, db, {
- ...icon,
- id: test.engineId,
- });
+ await insertRecordIntoCollection(client, { ...icon });
}
}
}
diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js b/toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js
new file mode 100644
index 0000000000..4159b9f0fb
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js
@@ -0,0 +1,324 @@
+/* Any copyright is dedicated to the Public Domain.
+https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests to ensure that icons for application provided engines are correctly
+ * updated from remote settings.
+ */
+
+"use strict";
+
+// A skeleton configuration that gets filled in from TESTS during `add_setup`.
+let CONFIG = [
+ {
+ identifier: "engine_no_initial_icon",
+ recordType: "engine",
+ base: {
+ name: "engine_no_initial_icon name",
+ urls: {
+ search: {
+ base: "https://example.com/1",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ },
+ {
+ identifier: "engine_icon_updates",
+ recordType: "engine",
+ base: {
+ name: "engine_icon_updates name",
+ urls: {
+ search: {
+ base: "https://example.com/2",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ },
+ {
+ identifier: "engine_icon_not_local",
+ recordType: "engine",
+ base: {
+ name: "engine_icon_not_local name",
+ urls: {
+ search: {
+ base: "https://example.com/3",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ },
+ {
+ identifier: "engine_icon_out_of_date",
+ recordType: "engine",
+ base: {
+ name: "engine_icon_out_of_date name",
+ urls: {
+ search: {
+ base: "https://example.com/4",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine_no_initial_icon",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
+async function assertIconMatches(actualIconData, expectedIcon) {
+ let expectedBuffer = new Uint8Array(await getFileDataBuffer(expectedIcon));
+
+ Assert.equal(
+ actualIconData.length,
+ expectedBuffer.length,
+ "Should have received matching buffer lengths for the expected icon"
+ );
+ Assert.ok(
+ actualIconData.every((value, index) => value === expectedBuffer[index]),
+ "Should have received matching data for the expected icon"
+ );
+}
+
+async function assertEngineIcon(engineName, expectedIcon) {
+ let engine = Services.search.getEngineByName(engineName);
+ let engineIconURL = await engine.getIconURL(16);
+
+ if (expectedIcon) {
+ Assert.notEqual(
+ engineIconURL,
+ null,
+ "Should have an icon URL for the engine."
+ );
+
+ let response = await fetch(engineIconURL);
+ let buffer = new Uint8Array(await response.arrayBuffer());
+
+ await assertIconMatches(buffer, expectedIcon);
+ } else {
+ Assert.equal(
+ engineIconURL,
+ null,
+ "Should not have an icon URL for the engine."
+ );
+ }
+}
+
+let originalIconId = Services.uuid.generateUUID().toString();
+let client;
+
+add_setup(async function setup() {
+ useHttpServer();
+ SearchTestUtils.useMockIdleService();
+
+ client = RemoteSettings("search-config-icons");
+ await client.db.clear();
+
+ sinon.stub(client.attachments, "_baseAttachmentsURL").returns(gDataUrl);
+
+ // Add some initial records and attachments into the remote settings collection.
+ await insertRecordIntoCollection(client, {
+ id: originalIconId,
+ filename: "remoteIcon.ico",
+ // This uses a wildcard match to test the icon is still applied correctly.
+ engineIdentifiers: ["engine_icon_upd*"],
+ imageSize: 16,
+ });
+ // This attachment is not cached, so we don't have it locally.
+ await insertRecordIntoCollection(
+ client,
+ {
+ id: Services.uuid.generateUUID().toString(),
+ filename: "bigIcon.ico",
+ engineIdentifiers: [
+ // This also tests multiple engine idenifiers works.
+ "enterprise",
+ "next_generation",
+ "engine_icon_not_local",
+ ],
+ imageSize: 16,
+ },
+ false
+ );
+
+ // Add a record that is out of date, and update it with a newer one, but don't
+ // cache the attachment for the new one.
+ let outOfDateRecordId = Services.uuid.generateUUID().toString();
+ await insertRecordIntoCollection(
+ client,
+ {
+ id: outOfDateRecordId,
+ filename: "remoteIcon.ico",
+ engineIdentifiers: ["engine_icon_out_of_date"],
+ imageSize: 16,
+ // 10 minutes ago.
+ lastModified: Date.now() - 600000,
+ },
+ true
+ );
+ let { record } = await mockRecordWithAttachment({
+ id: outOfDateRecordId,
+ filename: "bigIcon.ico",
+ engineIdentifiers: ["engine_icon_out_of_date"],
+ imageSize: 16,
+ });
+ await client.db.update(record);
+ await client.db.importChanges({}, record.lastModified);
+
+ await SearchTestUtils.useTestEngines("simple-engines", null, CONFIG);
+ await Services.search.init();
+
+ // Testing that an icon is not local generates a `Could not find {id}...`
+ // message.
+ consoleAllowList.push("Could not find");
+});
+
+add_task(async function test_icon_added_unknown_engine() {
+ // If the engine is unknown, and this is a new icon, we should still download
+ // the icon, in case the engine is added to the configuration later.
+ let newIconId = Services.uuid.generateUUID().toString();
+
+ let mock = await mockRecordWithAttachment({
+ id: newIconId,
+ filename: "bigIcon.ico",
+ engineIdentifiers: ["engine_unknown"],
+ imageSize: 16,
+ });
+ await client.db.update(mock.record, Date.now());
+
+ await client.emit("sync", {
+ data: {
+ current: [mock.record],
+ created: [mock.record],
+ updated: [],
+ deleted: [],
+ },
+ });
+
+ SearchTestUtils.idleService._fireObservers("idle");
+
+ let icon;
+ await TestUtils.waitForCondition(async () => {
+ try {
+ icon = await client.attachments.get(mock.record);
+ } catch (ex) {
+ // Do nothing.
+ }
+ return !!icon;
+ }, "Should have loaded the icon into the attachments store.");
+
+ await assertIconMatches(new Uint8Array(icon.buffer), "bigIcon.ico");
+});
+
+add_task(async function test_icon_added_existing_engine() {
+ // If the engine is unknown, and this is a new icon, we should still download
+ // it, in case the engine is added to the configuration later.
+ let newIconId = Services.uuid.generateUUID().toString();
+
+ let mock = await mockRecordWithAttachment({
+ id: newIconId,
+ filename: "bigIcon.ico",
+ engineIdentifiers: ["engine_no_initial_icon"],
+ imageSize: 16,
+ });
+ await client.db.update(mock.record, Date.now());
+
+ let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+
+ await client.emit("sync", {
+ data: {
+ current: [mock.record],
+ created: [mock.record],
+ updated: [],
+ deleted: [],
+ },
+ });
+
+ SearchTestUtils.idleService._fireObservers("idle");
+
+ await promiseEngineUpdated;
+ await assertEngineIcon("engine_no_initial_icon name", "bigIcon.ico");
+});
+
+add_task(async function test_icon_updated() {
+ // Test that when an update for an engine icon is received, the engine is
+ // correctly updated.
+
+ // Check the engine has the expected icon to start with.
+ await assertEngineIcon("engine_icon_updates name", "remoteIcon.ico");
+
+ // Update the icon for the engine.
+ let mock = await mockRecordWithAttachment({
+ id: originalIconId,
+ filename: "bigIcon.ico",
+ engineIdentifiers: ["engine_icon_upd*"],
+ imageSize: 16,
+ });
+ await client.db.update(mock.record, Date.now());
+
+ let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+
+ await client.emit("sync", {
+ data: {
+ current: [mock.record],
+ created: [],
+ updated: [{ new: mock.record }],
+ deleted: [],
+ },
+ });
+ SearchTestUtils.idleService._fireObservers("idle");
+
+ await promiseEngineUpdated;
+ await assertEngineIcon("engine_icon_updates name", "bigIcon.ico");
+});
+
+add_task(async function test_icon_not_local() {
+ // Tests that a download is queued and triggered when the icon for an engine
+ // is not in either the local dump nor the cache.
+
+ await assertEngineIcon("engine_icon_not_local name", null);
+
+ // A download should have been queued, so fire idle to trigger it.
+ let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ SearchTestUtils.idleService._fireObservers("idle");
+ await promiseEngineUpdated;
+
+ await assertEngineIcon("engine_icon_not_local name", "bigIcon.ico");
+});
+
+add_task(async function test_icon_out_of_date() {
+ // Tests that a download is queued and triggered when the icon for an engine
+ // is not in either the local dump nor the cache.
+
+ await assertEngineIcon("engine_icon_out_of_date name", "remoteIcon.ico");
+
+ // A download should have been queued, so fire idle to trigger it.
+ let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification(
+ SearchUtils.MODIFIED_TYPE.CHANGED,
+ SearchUtils.TOPIC_ENGINE_MODIFIED
+ );
+ SearchTestUtils.idleService._fireObservers("idle");
+ await promiseEngineUpdated;
+
+ await assertEngineIcon("engine_icon_out_of_date name", "bigIcon.ico");
+});
diff --git a/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js b/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js
index 12cb6568e7..2f269cc016 100644
--- a/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js
+++ b/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js
@@ -17,6 +17,15 @@
let appDefault;
let appPrivateDefault;
+async function getSearchConfig() {
+ let workDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ let configFileName =
+ "file://" + PathUtils.join(workDir.path, "data", "search-config-v2.json");
+
+ let response = await fetch(configFileName);
+ return response.json();
+}
+
add_setup(async function () {
useHttpServer();
await SearchTestUtils.useTestEngines();
@@ -292,10 +301,33 @@ add_task(async function test_default_fallback_remove_default_no_visible() {
add_task(
async function test_default_fallback_remove_default_no_visible_or_general() {
- // Reset.
Services.search.restoreDefaultEngines();
- Services.search.defaultEngine = Services.search.defaultPrivateEngine =
- appPrivateDefault;
+
+ // For this test, we need to change any general search engines to unknown,
+ // so that we can test what happens in the unlikely event that there are no
+ // general search engines.
+ if (SearchUtils.newSearchConfigEnabled) {
+ let searchConfig = await getSearchConfig();
+ for (let entry of searchConfig.data) {
+ if (
+ entry.recordType == "engine" &&
+ entry.base.classification == "general"
+ ) {
+ entry.base.classification = "unknown";
+ }
+ }
+ const settings = await RemoteSettings(SearchUtils.SETTINGS_KEY);
+ settings.get.returns(searchConfig.data);
+ Services.search.wrappedJSObject.reset();
+ await Services.search.init();
+
+ appPrivateDefault = await Services.search.getDefaultPrivate();
+
+ Services.search.defaultEngine = appPrivateDefault;
+ } else {
+ Services.search.defaultEngine = Services.search.defaultPrivateEngine =
+ appPrivateDefault;
+ }
// Remove all but the default engine.
let visibleEngines = await Services.search.getVisibleEngines();
@@ -310,7 +342,9 @@ add_task(
"Should only have one visible engine"
);
- SearchUtils.GENERAL_SEARCH_ENGINE_IDS.clear();
+ if (!SearchUtils.newSearchConfigEnabled) {
+ SearchUtils.GENERAL_SEARCH_ENGINE_IDS.clear();
+ }
const observer = new SearchObserver(
[
diff --git a/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js b/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js
index bf56984cde..401392b955 100644
--- a/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js
+++ b/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js
@@ -388,6 +388,55 @@ const CONFIG_VERSIONS = [
},
];
+const CONFIG_DEVICE_TYPE_LAYOUT = [
+ {
+ recordType: "engine",
+ identifier: "engine-no-device-type",
+ base: {},
+ variants: [
+ {
+ environment: {
+ allRegionsAndLocales: true,
+ },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-single-device-type",
+ base: {},
+ variants: [
+ {
+ environment: {
+ allRegionsAndLocales: true,
+ deviceType: ["tablet"],
+ },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-multiple-device-type",
+ base: {},
+ variants: [
+ {
+ environment: {
+ allRegionsAndLocales: true,
+ deviceType: ["tablet", "smartphone"],
+ },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
const engineSelector = new SearchEngineSelector();
let settings;
let settingOverrides;
@@ -793,3 +842,15 @@ add_task(async function test_engine_selector_does_not_match_optional_engines() {
"Should match engines where optional flag is false or undefined"
);
});
+
+add_task(async function test_engine_selector_match_device_type() {
+ await assertActualEnginesEqualsExpected(
+ CONFIG_DEVICE_TYPE_LAYOUT,
+ {
+ locale: "en-CA",
+ region: "CA",
+ },
+ ["engine-no-device-type"],
+ "Should only match engines with no device type."
+ );
+});
diff --git a/toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js b/toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js
new file mode 100644
index 0000000000..ed61362ca6
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const CONFIG = [
+ {
+ recordType: "engine",
+ identifier: "engine-1",
+ base: {},
+ variants: [
+ {
+ environment: {
+ allRegionsAndLocales: true,
+ },
+ partnerCode: "variant-partner-code",
+ subVariants: [
+ {
+ environment: { regions: ["CA", "FR"] },
+ telemetrySuffix: "subvariant-telemetry",
+ },
+ {
+ environment: { regions: ["GB", "FR"] },
+ partnerCode: "subvariant-partner-code",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
+const engineSelector = new SearchEngineSelector();
+let settings;
+let configStub;
+
+/**
+ * This function asserts if the actual engines returned equals the expected
+ * engines.
+ *
+ * @param {object} config
+ * A fake search config containing engines.
+ * @param {object} userEnv
+ * A fake user's environment including locale and region, experiment, etc.
+ * @param {Array} expectedEngines
+ * The array of expected engines to be returned from the fake config.
+ * @param {string} message
+ * The assertion message.
+ */
+async function assertActualEnginesEqualsExpected(
+ config,
+ userEnv,
+ expectedEngines,
+ message
+) {
+ engineSelector._configuration = null;
+ configStub.returns(config);
+ let { engines } = await engineSelector.fetchEngineConfiguration(userEnv);
+
+ Assert.deepEqual(engines, expectedEngines, message);
+}
+
+add_setup(async function () {
+ settings = await RemoteSettings(SearchUtils.NEW_SETTINGS_KEY);
+ configStub = sinon.stub(settings, "get");
+});
+
+add_task(async function test_no_subvariants_match() {
+ await assertActualEnginesEqualsExpected(
+ CONFIG,
+ {
+ locale: "fi",
+ region: "FI",
+ },
+ [
+ {
+ identifier: "engine-1",
+ partnerCode: "variant-partner-code",
+ },
+ ],
+ "Should match no subvariants."
+ );
+});
+
+add_task(async function test_matching_subvariant_with_properties() {
+ await assertActualEnginesEqualsExpected(
+ CONFIG,
+ {
+ locale: "en-GB",
+ region: "GB",
+ },
+ [
+ {
+ identifier: "engine-1",
+ partnerCode: "subvariant-partner-code",
+ },
+ ],
+ "Should match subvariant with subvariant properties."
+ );
+});
+
+add_task(async function test_matching_variant_and_subvariant_with_properties() {
+ await assertActualEnginesEqualsExpected(
+ CONFIG,
+ {
+ locale: "en-CA",
+ region: "CA",
+ },
+ [
+ {
+ identifier: "engine-1",
+ partnerCode: "variant-partner-code",
+ telemetrySuffix: "subvariant-telemetry",
+ },
+ ],
+ "Should match subvariant with subvariant properties."
+ );
+});
+
+add_task(async function test_matching_two_subvariant_with_properties() {
+ await assertActualEnginesEqualsExpected(
+ CONFIG,
+ {
+ locale: "fr",
+ region: "FR",
+ },
+ [
+ {
+ identifier: "engine-1",
+ partnerCode: "subvariant-partner-code",
+ },
+ ],
+ "Should match the last subvariant with subvariant properties."
+ );
+});
diff --git a/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js b/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js
index 0fd57f2094..51a7f0de09 100644
--- a/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js
+++ b/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js
@@ -122,7 +122,7 @@ add_task(async function test_no_variants_match() {
);
});
-add_task(async function test_match_and_apply_all_variants() {
+add_task(async function test_match_and_apply_last_variants() {
await assertActualEnginesEqualsExpected(
CONFIG,
{
@@ -133,11 +133,10 @@ add_task(async function test_match_and_apply_all_variants() {
{
identifier: "engine-1",
urls: { search: { params: [{ name: "partner-code", value: "foo" }] } },
- telemetrySuffix: "telemetry",
searchTermParamName: "search-param",
},
],
- "Should match all variants and apply each variant property cumulatively."
+ "Should match and apply last variant."
);
});
diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
index 4a30eb741a..d24970534f 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
@@ -3,7 +3,7 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
/**
- * Testing search suggestions from SearchSuggestionController.jsm.
+ * Testing search suggestions from SearchSuggestionController.sys.mjs.
*/
"use strict";
@@ -23,7 +23,7 @@ const SEARCH_TELEMETRY_LATENCY = "SEARCH_SUGGESTIONS_LATENCY_MS";
// We must make sure the FormHistoryStartup component is
// initialized in order for it to respond to FormHistory
-// requests from nsFormAutoComplete.js.
+// requests from FormHistoryAutoComplete.sys.mjs.
var formHistoryStartup = Cc[
"@mozilla.org/satchel/form-history-startup;1"
].getService(Ci.nsIObserver);
diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js
index 042c74d86a..bb4dda9e3f 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js
@@ -2,7 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
- * Test that search suggestions from SearchSuggestionController.jsm don't store
+ * Test that search suggestions from SearchSuggestionController.sys.mjs don't store
* cookies.
*/
@@ -14,7 +14,7 @@ const { SearchSuggestionController } = ChromeUtils.importESModule(
// We must make sure the FormHistoryStartup component is
// initialized in order for it to respond to FormHistory
-// requests from nsFormAutoComplete.js.
+// requests from FormHistoryAutoComplete.sys.mjs.
var formHistoryStartup = Cc[
"@mozilla.org/satchel/form-history-startup;1"
].getService(Ci.nsIObserver);
diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js
index 063f3ada49..b7750c0f30 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js
@@ -2,7 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
- * Test that search suggestions from SearchSuggestionController.jsm operate
+ * Test that search suggestions from SearchSuggestionController.sys.mjs operate
* correctly in private mode.
*/
diff --git a/toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js b/toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js
new file mode 100644
index 0000000000..56746a614f
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test to verify search-config-v2 preference is correctly toggled via a Nimbus
+ variable. */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
+ ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
+ ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs",
+ ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
+ AppProvidedSearchEngine:
+ "resource://gre/modules/AppProvidedSearchEngine.sys.mjs",
+});
+
+add_task(async function test_nimbus_experiment_enabled() {
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.search.newSearchConfig.enabled"),
+ false,
+ "newSearchConfig.enabled PREF should initially be false."
+ );
+
+ await ExperimentManager.onStartup();
+ await ExperimentAPI.ready();
+
+ let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig(
+ {
+ featureId: "search",
+ value: {
+ newSearchConfigEnabled: true,
+ },
+ },
+ { isRollout: true }
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.search.newSearchConfig.enabled"),
+ true,
+ "After toggling the Nimbus variable, the current value of newSearchConfig.enabled PREF should be true."
+ );
+
+ Assert.equal(
+ SearchUtils.newSearchConfigEnabled,
+ true,
+ "After toggling the Nimbus variable, newSearchConfig.enabled should be cached as true."
+ );
+
+ await AddonTestUtils.promiseStartupManager();
+ await Services.search.init();
+ await SearchTestUtils.useTestEngines();
+
+ let { engines: engines2 } =
+ await Services.search.wrappedJSObject._fetchEngineSelectorEngines();
+
+ Assert.ok(
+ engines2.some(engine => engine.identifier),
+ "Engines in the search-config-v2 format should have an identifier."
+ );
+
+ let appProvidedEngines =
+ await Services.search.wrappedJSObject.getAppProvidedEngines();
+
+ Assert.ok(
+ appProvidedEngines.every(
+ engine => engine instanceof AppProvidedSearchEngine
+ ),
+ "All application provided engines for search-config-v2 should be instances of AppProvidedSearchEngine."
+ );
+
+ await doExperimentCleanup();
+
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.search.newSearchConfig.enabled"),
+ false,
+ "After experiment unenrollment, the newSearchConfig.enabled should be false."
+ );
+
+ Assert.equal(
+ SearchUtils.newSearchConfigEnabled,
+ true,
+ "After experiment unenrollment, newSearchConfig.enabled should be cached as true."
+ );
+});
diff --git a/toolkit/components/search/tests/xpcshell/test_settings_persist.js b/toolkit/components/search/tests/xpcshell/test_settings_persist.js
index 5c2cbd85c4..e3310a1fa2 100644
--- a/toolkit/components/search/tests/xpcshell/test_settings_persist.js
+++ b/toolkit/components/search/tests/xpcshell/test_settings_persist.js
@@ -81,7 +81,7 @@ add_setup(async function () {
registerCleanupFunction(AddonTestUtils.promiseShutdownManager);
await AddonTestUtils.promiseStartupManager();
// This is only needed as otherwise events will not be properly notified
- // due to https://searchfox.org/mozilla-central/source/toolkit/components/search/SearchUtils.jsm#186
+ // due to https://searchfox.org/mozilla-central/rev/5f0a7ca8968ac5cef8846e1d970ef178b8b76dcc/toolkit/components/search/SearchSettings.sys.mjs#41-42
let settingsFileWritten = promiseAfterSettings();
await Services.search.init(false);
Services.search.wrappedJSObject._removeObservers();
diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.toml b/toolkit/components/search/tests/xpcshell/xpcshell.toml
index 7dd023cbec..899ac2d711 100644
--- a/toolkit/components/search/tests/xpcshell/xpcshell.toml
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.toml
@@ -78,9 +78,18 @@ support-files = [
["test_appDefaultEngine.js"]
+["test_appProvided_engine.js"]
+prefs = ["browser.search.newSearchConfig.enabled=true"]
+support-files = [
+ "../../schema/search-config-v2-schema.json",
+]
+
["test_appProvided_icons.js"]
prefs = ["browser.search.newSearchConfig.enabled=true"]
+["test_appProvided_icons_updates.js"]
+prefs = ["browser.search.newSearchConfig.enabled=true"]
+
["test_async.js"]
["test_config_engine_params.js"]
@@ -128,6 +137,8 @@ tags = "remotesettings searchmain"
["test_engine_selector_environment.js"]
+["test_engine_selector_subvariants.js"]
+
["test_engine_selector_variants.js"]
["test_engine_set_alias.js"]
@@ -254,6 +265,10 @@ support-files = [
["test_searchUrlDomain.js"]
+["test_search_config_v2_nimbus.js"]
+prefs = ["browser.search.newSearchConfig.enabled=false"]
+skip-if = ["appname == 'thunderbird'"] # Test relies on normandy.
+
["test_selectedEngine.js"]
["test_sendSubmissionURL.js"]