summaryrefslogtreecommitdiffstats
path: root/intl/locale/tests
diff options
context:
space:
mode:
Diffstat (limited to 'intl/locale/tests')
-rw-r--r--intl/locale/tests/LangPackMatcherTestUtils.sys.mjs124
-rw-r--r--intl/locale/tests/gtest/TestAppDateTimeFormat.cpp310
-rw-r--r--intl/locale/tests/gtest/TestLocaleService.cpp173
-rw-r--r--intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp53
-rw-r--r--intl/locale/tests/gtest/TestOSPreferences.cpp205
-rw-r--r--intl/locale/tests/gtest/moz.build14
-rw-r--r--intl/locale/tests/sort/us-ascii_base.txt95
-rw-r--r--intl/locale/tests/sort/us-ascii_base_case_res.txt96
-rw-r--r--intl/locale/tests/sort/us-ascii_base_nocase_res.txt96
-rw-r--r--intl/locale/tests/sort/us-ascii_sort.txt78
-rw-r--r--intl/locale/tests/sort/us-ascii_sort_case_res.txt79
-rw-r--r--intl/locale/tests/sort/us-ascii_sort_nocase_res.txt79
-rw-r--r--intl/locale/tests/unit/data/chrome.manifest1
-rw-r--r--intl/locale/tests/unit/data/intl_on_workers_worker.js6
-rw-r--r--intl/locale/tests/unit/test_bug22310.js65
-rw-r--r--intl/locale/tests/unit/test_intl_on_workers.js29
-rw-r--r--intl/locale/tests/unit/test_langPackMatcher.js287
-rw-r--r--intl/locale/tests/unit/test_localeService.js240
-rw-r--r--intl/locale/tests/unit/test_localeService_negotiateLanguages.js205
-rw-r--r--intl/locale/tests/unit/test_osPreferences.js44
-rw-r--r--intl/locale/tests/unit/xpcshell.toml21
21 files changed, 2300 insertions, 0 deletions
diff --git a/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs b/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs
new file mode 100644
index 0000000000..4b18f1be13
--- /dev/null
+++ b/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs
@@ -0,0 +1,124 @@
+/* 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/. */
+
+import { LangPackMatcher } from "resource://gre/modules/LangPackMatcher.sys.mjs";
+
+/**
+ * LangPackMatcher.jsm calls out to to the addons store, which involves network requests.
+ * Other tests create a fake addons server, and install mock XPIs. At the time of this
+ * writing that infrastructure is not available for mochitests.
+ *
+ * Instead, this test mocks out APIs that have a side-effect, so the addons of the browser
+ * are never modified.
+ *
+ * The calls to get the app's locale and system's locale are also mocked so that the
+ * different language mismatch scenarios can be run through.
+ *
+ * The locales are BCP 47 identifiers:
+ *
+ * @param {{
+ * sandbox: SinonSandbox,
+ * systemLocale: string,
+ * appLocale, string,
+ * }}
+ */
+export function getAddonAndLocalAPIsMocker(testScope, sandbox) {
+ const { info } = testScope;
+ return function mockAddonAndLocaleAPIs({ systemLocale, appLocale }) {
+ info("Mocking LangPackMatcher.jsm APIs");
+
+ let resolveLangPacks;
+ const langPackPromise = new Promise(resolve => {
+ resolveLangPacks = availableLangpacks => {
+ info(
+ `Resolving which langpacks are available for download: ${JSON.stringify(
+ availableLangpacks
+ )}`
+ );
+ resolve(
+ availableLangpacks.map(locale => ({
+ guid: `langpack-${locale}@firefox.mozilla.org`,
+ type: "language",
+ hash: locale,
+ target_locale: locale,
+ current_compatible_version: {
+ files: [
+ {
+ platform: "all",
+ url: `http://example.com/${locale}.langpack.xpi`,
+ },
+ ],
+ },
+ }))
+ );
+ };
+ });
+
+ let resolveInstaller;
+ const installerPromise = new Promise(resolve => {
+ resolveInstaller = () => {
+ info("LangPack install finished.");
+ resolve();
+ };
+ });
+
+ const { mockable } = LangPackMatcher;
+ if (appLocale) {
+ const availableLocales = [appLocale];
+ if (appLocale !== "en-US") {
+ // Ensure the fallback behavior is accurately simulated for Firefox.
+ availableLocales.push("en-US");
+ }
+ sandbox
+ .stub(mockable, "getAvailableLocalesIncludingFallback")
+ .returns(availableLocales);
+ sandbox.stub(mockable, "getDefaultLocale").returns(appLocale);
+ sandbox.stub(mockable, "getAppLocaleAsBCP47").returns(appLocale);
+ sandbox.stub(mockable, "getLastFallbackLocale").returns("en-US");
+ }
+ if (systemLocale) {
+ sandbox.stub(mockable, "getSystemLocale").returns(systemLocale);
+ }
+
+ sandbox.stub(mockable, "getAvailableLangpacks").callsFake(() => {
+ info("Requesting which langpacks are available for download");
+ return langPackPromise;
+ });
+
+ sandbox.stub(mockable, "installLangPack").callsFake(langPack => {
+ info(`LangPack install started, but pending: ${langPack.target_locale}`);
+ return installerPromise;
+ });
+
+ sandbox.stub(mockable, "setRequestedAppLocales").callsFake(locales => {
+ info(
+ `Changing the browser's requested locales to: ${JSON.stringify(
+ locales
+ )}`
+ );
+ });
+
+ return {
+ /**
+ * Resolves the addons API call with available langpacks. Call with a list
+ * of BCP 47 identifiers.
+ *
+ * @type {(availableLangpacks: string[]) => {}}
+ */
+ resolveLangPacks,
+
+ /**
+ * Resolves the pending call to install a langpack.
+ *
+ * @type {() => {}}
+ */
+ resolveInstaller,
+
+ /**
+ * The mocked APIs.
+ */
+ mockable,
+ };
+ };
+}
diff --git a/intl/locale/tests/gtest/TestAppDateTimeFormat.cpp b/intl/locale/tests/gtest/TestAppDateTimeFormat.cpp
new file mode 100644
index 0000000000..a83b373428
--- /dev/null
+++ b/intl/locale/tests/gtest/TestAppDateTimeFormat.cpp
@@ -0,0 +1,310 @@
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+#include "mozilla/intl/DateTimeFormat.h"
+
+namespace mozilla::intl {
+using Style = DateTimeFormat::Style;
+using StyleBag = DateTimeFormat::StyleBag;
+using ComponentsBag = DateTimeFormat::ComponentsBag;
+
+static DateTimeFormat::StyleBag ToStyleBag(Maybe<DateTimeFormat::Style> date,
+ Maybe<DateTimeFormat::Style> time) {
+ DateTimeFormat::StyleBag style;
+ style.date = date;
+ style.time = time;
+ return style;
+}
+
+TEST(AppDateTimeFormat, FormatPRExplodedTime)
+{
+ PRTime prTime = 0;
+ PRExplodedTime prExplodedTime;
+ PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime);
+
+ AppDateTimeFormat::sLocale = new nsCString("en-US");
+ AppDateTimeFormat::DeleteCache();
+ StyleBag style = ToStyleBag(Some(Style::Long), Some(Style::Long));
+
+ nsAutoString formattedTime;
+ nsresult rv =
+ AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"12:00:00 AM") != kNotFound ||
+ formattedTime.Find(u"12:00:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"00:00:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 19, 0, 1, 0, 1970, 4, 0, {(19 * 60), 0}};
+
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"12:19:00 AM") != kNotFound ||
+ formattedTime.Find(u"12:19:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"00:19:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 0, 7, 1,
+ 0, 1970, 4, 0, {(6 * 60 * 60), (1 * 60 * 60)}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"7:00:00 AM") != kNotFound ||
+ formattedTime.Find(u"7:00:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"07:00:00") != kNotFound);
+
+ prExplodedTime = {
+ 0, 0, 29, 11, 1,
+ 0, 1970, 4, 0, {(10 * 60 * 60) + (29 * 60), (1 * 60 * 60)}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"January") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"11:29:00 AM") != kNotFound ||
+ formattedTime.Find(u"11:29:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"11:29:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 37, 23, 31, 11, 1969, 3, 364, {-(23 * 60), 0}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"December") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"31") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"11:37:00 PM") != kNotFound ||
+ formattedTime.Find(u"11:37:00\u202FPM") != kNotFound ||
+ formattedTime.Find(u"23:37:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 0, 17, 31, 11, 1969, 3, 364, {-(7 * 60 * 60), 0}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"December") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"31") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"5:00:00 PM") != kNotFound ||
+ formattedTime.Find(u"5:00:00\u202FPM") != kNotFound ||
+ formattedTime.Find(u"17:00:00") != kNotFound);
+
+ prExplodedTime = {
+ 0, 0, 47, 14, 31,
+ 11, 1969, 3, 364, {-((10 * 60 * 60) + (13 * 60)), (1 * 60 * 60)}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"December") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"31") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"2:47:00 PM") != kNotFound ||
+ formattedTime.Find(u"2:47:00\u202FPM") != kNotFound ||
+ formattedTime.Find(u"14:47:00") != kNotFound);
+}
+
+TEST(AppDateTimeFormat, DateFormatSelectors)
+{
+ PRTime prTime = 0;
+ PRExplodedTime prExplodedTime;
+ PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime);
+
+ AppDateTimeFormat::sLocale = new nsCString("en-US");
+ AppDateTimeFormat::DeleteCache();
+
+ nsAutoString formattedTime;
+
+ {
+ ComponentsBag components{};
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::TwoDigit);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("01/1970", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::Long);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("January 1970", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.month = Some(DateTimeFormat::Month::Long);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("January", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.weekday = Some(DateTimeFormat::Text::Short);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("Thu", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+}
+
+TEST(AppDateTimeFormat, FormatPRExplodedTimeForeign)
+{
+ PRTime prTime = 0;
+ PRExplodedTime prExplodedTime;
+ PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime);
+
+ AppDateTimeFormat::sLocale = new nsCString("de-DE");
+ AppDateTimeFormat::DeleteCache();
+ StyleBag style = ToStyleBag(Some(Style::Long), Some(Style::Long));
+
+ nsAutoString formattedTime;
+ nsresult rv =
+ AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"12:00:00 AM") != kNotFound ||
+ formattedTime.Find(u"12:00:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"00:00:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 19, 0, 1, 0, 1970, 4, 0, {(19 * 60), 0}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"12:19:00 AM") != kNotFound ||
+ formattedTime.Find(u"12:19:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"00:19:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 0, 7, 1,
+ 0, 1970, 4, 0, {(6 * 60 * 60), (1 * 60 * 60)}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"7:00:00 AM") != kNotFound ||
+ formattedTime.Find(u"7:00:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"07:00:00") != kNotFound);
+
+ prExplodedTime = {
+ 0, 0, 29, 11, 1,
+ 0, 1970, 4, 0, {(10 * 60 * 60) + (29 * 60), (1 * 60 * 60)}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"1.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Januar") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1970") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"11:29:00 AM") != kNotFound ||
+ formattedTime.Find(u"11:29:00\u202FAM") != kNotFound ||
+ formattedTime.Find(u"11:29:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 37, 23, 31, 11, 1969, 3, 364, {-(23 * 60), 0}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"31.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Dezember") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"11:37:00 PM") != kNotFound ||
+ formattedTime.Find(u"11:37:00\u202FPM") != kNotFound ||
+ formattedTime.Find(u"23:37:00") != kNotFound);
+
+ prExplodedTime = {0, 0, 0, 17, 31, 11, 1969, 3, 364, {-(7 * 60 * 60), 0}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"31.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Dezember") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"5:00:00 PM") != kNotFound ||
+ formattedTime.Find(u"5:00:00\u202FPM") != kNotFound ||
+ formattedTime.Find(u"17:00:00") != kNotFound);
+
+ prExplodedTime = {
+ 0, 0, 47, 14, 31,
+ 11, 1969, 3, 364, {-((10 * 60 * 60) + (13 * 60)), (1 * 60 * 60)}};
+ rv = AppDateTimeFormat::Format(style, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(formattedTime.Find(u"31.") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"Dezember") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"1969") != kNotFound);
+ ASSERT_TRUE(formattedTime.Find(u"2:47:00 PM") != kNotFound ||
+ formattedTime.Find(u"2:47:00\u202FPM") != kNotFound ||
+ formattedTime.Find(u"14:47:00") != kNotFound);
+}
+
+TEST(AppDateTimeFormat, DateFormatSelectorsForeign)
+{
+ PRTime prTime = 0;
+ PRExplodedTime prExplodedTime;
+ PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime);
+
+ AppDateTimeFormat::sLocale = new nsCString("de-DE");
+ AppDateTimeFormat::DeleteCache();
+
+ nsAutoString formattedTime;
+ {
+ ComponentsBag components{};
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::TwoDigit);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("01.1970", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::Long);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("Januar 1970", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.weekday = Some(DateTimeFormat::Text::Short);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("Do", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.weekday = Some(DateTimeFormat::Text::Long);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("Donnerstag", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.month = Some(DateTimeFormat::Month::Long);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("Januar", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+ {
+ ComponentsBag components{};
+ components.weekday = Some(DateTimeFormat::Text::Short);
+
+ nsresult rv =
+ AppDateTimeFormat::Format(components, &prExplodedTime, formattedTime);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_STREQ("Do", NS_ConvertUTF16toUTF8(formattedTime).get());
+ }
+}
+
+} // namespace mozilla::intl
diff --git a/intl/locale/tests/gtest/TestLocaleService.cpp b/intl/locale/tests/gtest/TestLocaleService.cpp
new file mode 100644
index 0000000000..2cf19727d6
--- /dev/null
+++ b/intl/locale/tests/gtest/TestLocaleService.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/intl/Locale.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/Collator.h"
+
+using namespace mozilla::intl;
+
+TEST(Intl_Locale_LocaleService, CanonicalizeLanguageId)
+{
+ nsCString locale("en-US.POSIX");
+ ASSERT_TRUE(LocaleService::CanonicalizeLanguageId(locale));
+ ASSERT_TRUE(locale.EqualsLiteral("en-US"));
+
+ locale.AssignLiteral("en-US_POSIX");
+ ASSERT_TRUE(LocaleService::CanonicalizeLanguageId(locale));
+ ASSERT_TRUE(locale.EqualsLiteral("en-US-posix"));
+
+ locale.AssignLiteral("en-US-POSIX");
+ ASSERT_TRUE(LocaleService::CanonicalizeLanguageId(locale));
+ ASSERT_TRUE(locale.EqualsLiteral("en-US-posix"));
+
+ locale.AssignLiteral("C");
+ ASSERT_FALSE(LocaleService::CanonicalizeLanguageId(locale));
+ ASSERT_TRUE(locale.EqualsLiteral("und"));
+
+ locale.AssignLiteral("");
+ ASSERT_FALSE(LocaleService::CanonicalizeLanguageId(locale));
+ ASSERT_TRUE(locale.EqualsLiteral("und"));
+}
+
+TEST(Intl_Locale_LocaleService, GetAppLocalesAsBCP47)
+{
+ nsTArray<nsCString> appLocales;
+ LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
+
+ ASSERT_FALSE(appLocales.IsEmpty());
+}
+
+TEST(Intl_Locale_LocaleService, GetAppLocalesAsLangTags)
+{
+ nsTArray<nsCString> appLocales;
+ LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
+
+ ASSERT_FALSE(appLocales.IsEmpty());
+}
+
+TEST(Intl_Locale_LocaleService, GetAppLocalesAsLangTags_lastIsPresent)
+{
+ nsAutoCString lastFallbackLocale;
+ LocaleService::GetInstance()->GetLastFallbackLocale(lastFallbackLocale);
+
+ nsTArray<nsCString> appLocales;
+ LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
+
+ ASSERT_TRUE(appLocales.Contains(lastFallbackLocale));
+}
+
+TEST(Intl_Locale_LocaleService, GetAppLocaleAsLangTag)
+{
+ nsTArray<nsCString> appLocales;
+ LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
+
+ nsAutoCString locale;
+ LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
+
+ ASSERT_TRUE(appLocales[0] == locale);
+}
+
+TEST(Intl_Locale_LocaleService, GetRegionalPrefsLocales)
+{
+ nsTArray<nsCString> rpLocales;
+ LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales);
+
+ int32_t len = rpLocales.Length();
+ ASSERT_TRUE(len > 0);
+}
+
+TEST(Intl_Locale_LocaleService, GetWebExposedLocales)
+{
+ const nsTArray<nsCString> spoofLocale{"de"_ns};
+ LocaleService::GetInstance()->SetAvailableLocales(spoofLocale);
+ LocaleService::GetInstance()->SetRequestedLocales(spoofLocale);
+
+ nsTArray<nsCString> pvLocales;
+
+ mozilla::Preferences::SetInt("privacy.spoof_english", 0);
+ LocaleService::GetInstance()->GetWebExposedLocales(pvLocales);
+ ASSERT_TRUE(pvLocales.Length() > 0);
+ ASSERT_TRUE(pvLocales[0].Equals("de"_ns));
+
+ mozilla::Preferences::SetCString("intl.locale.privacy.web_exposed", "zh-TW");
+ LocaleService::GetInstance()->GetWebExposedLocales(pvLocales);
+ ASSERT_TRUE(pvLocales.Length() > 0);
+ ASSERT_TRUE(pvLocales[0].Equals("zh-TW"_ns));
+
+ mozilla::Preferences::SetInt("privacy.spoof_english", 2);
+ LocaleService::GetInstance()->GetWebExposedLocales(pvLocales);
+ ASSERT_EQ(1u, pvLocales.Length());
+ ASSERT_TRUE(pvLocales[0].Equals("en-US"_ns));
+}
+
+TEST(Intl_Locale_LocaleService, GetRequestedLocales)
+{
+ nsTArray<nsCString> reqLocales;
+ LocaleService::GetInstance()->GetRequestedLocales(reqLocales);
+
+ int32_t len = reqLocales.Length();
+ ASSERT_TRUE(len > 0);
+}
+
+TEST(Intl_Locale_LocaleService, GetAvailableLocales)
+{
+ nsTArray<nsCString> availableLocales;
+ LocaleService::GetInstance()->GetAvailableLocales(availableLocales);
+
+ int32_t len = availableLocales.Length();
+ ASSERT_TRUE(len > 0);
+}
+
+TEST(Intl_Locale_LocaleService, GetPackagedLocales)
+{
+ nsTArray<nsCString> packagedLocales;
+ LocaleService::GetInstance()->GetPackagedLocales(packagedLocales);
+
+ int32_t len = packagedLocales.Length();
+ ASSERT_TRUE(len > 0);
+}
+
+TEST(Intl_Locale_LocaleService, GetDefaultLocale)
+{
+ nsAutoCString locStr;
+ LocaleService::GetInstance()->GetDefaultLocale(locStr);
+
+ ASSERT_FALSE(locStr.IsEmpty());
+ Locale loc;
+ ASSERT_TRUE(LocaleParser::TryParse(locStr, loc).isOk());
+}
+
+TEST(Intl_Locale_LocaleService, IsAppLocaleRTL)
+{
+ mozilla::Preferences::SetCString("intl.l10n.pseudo", "bidi");
+ ASSERT_TRUE(LocaleService::GetInstance()->IsAppLocaleRTL());
+ mozilla::Preferences::ClearUser("intl.l10n.pseudo");
+}
+
+TEST(Intl_Locale_LocaleService, TryCreateComponent)
+{
+ {
+ // Create a Collator with the app locale.
+ auto result = LocaleService::GetInstance()->TryCreateComponent<Collator>();
+ ASSERT_TRUE(result.isOk());
+ }
+ {
+ // Create a Collator with the "en" locale.
+ auto result =
+ LocaleService::GetInstance()->TryCreateComponentWithLocale<Collator>(
+ "en");
+ ASSERT_TRUE(result.isOk());
+ }
+ {
+ // Fallback to the app locale when an invalid one is used.
+ auto result =
+ LocaleService::GetInstance()->TryCreateComponentWithLocale<Collator>(
+ "$invalidName");
+ ASSERT_TRUE(result.isOk());
+ }
+}
diff --git a/intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp b/intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp
new file mode 100644
index 0000000000..c428e81c8d
--- /dev/null
+++ b/intl/locale/tests/gtest/TestLocaleServiceNegotiate.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/intl/LocaleService.h"
+
+using namespace mozilla::intl;
+
+TEST(Intl_Locale_LocaleService, Negotiate)
+{
+ nsTArray<nsCString> requestedLocales;
+ nsTArray<nsCString> availableLocales;
+ nsTArray<nsCString> supportedLocales;
+ nsAutoCString defaultLocale("en-US");
+ int32_t strategy = LocaleService::kLangNegStrategyFiltering;
+
+ requestedLocales.AppendElement("sr"_ns);
+
+ availableLocales.AppendElement("sr-Cyrl"_ns);
+ availableLocales.AppendElement("sr-Latn"_ns);
+
+ LocaleService::GetInstance()->NegotiateLanguages(
+ requestedLocales, availableLocales, defaultLocale, strategy,
+ supportedLocales);
+
+ ASSERT_TRUE(supportedLocales.Length() == 2);
+ ASSERT_TRUE(supportedLocales[0].EqualsLiteral("sr-Cyrl"));
+ ASSERT_TRUE(supportedLocales[1].EqualsLiteral("en-US"));
+}
+
+TEST(Intl_Locale_LocaleService, UseLSDefaultLocale)
+{
+ nsTArray<nsCString> requestedLocales;
+ nsTArray<nsCString> availableLocales;
+ nsTArray<nsCString> supportedLocales;
+ nsAutoCString defaultLocale("en-US");
+ int32_t strategy = LocaleService::kLangNegStrategyLookup;
+
+ requestedLocales.AppendElement("sr"_ns);
+
+ availableLocales.AppendElement("de"_ns);
+
+ LocaleService::GetInstance()->NegotiateLanguages(
+ requestedLocales, availableLocales, defaultLocale, strategy,
+ supportedLocales);
+
+ nsAutoCString lsDefaultLocale;
+ LocaleService::GetInstance()->GetDefaultLocale(lsDefaultLocale);
+ ASSERT_TRUE(supportedLocales.Length() == 1);
+ ASSERT_TRUE(supportedLocales[0].Equals(lsDefaultLocale));
+}
diff --git a/intl/locale/tests/gtest/TestOSPreferences.cpp b/intl/locale/tests/gtest/TestOSPreferences.cpp
new file mode 100644
index 0000000000..7e3b71582b
--- /dev/null
+++ b/intl/locale/tests/gtest/TestOSPreferences.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/OSPreferences.h"
+
+using namespace mozilla::intl;
+
+/**
+ * We test that on all platforms we test against (irrelevant of the tier),
+ * we will be able to retrieve at least a single locale out of the system.
+ *
+ * In theory, that may not be true, but if we encounter such platform we should
+ * decide how to handle this and special case and this test should make
+ * it not happen without us noticing.
+ */
+TEST(Intl_Locale_OSPreferences, GetSystemLocales)
+{
+ nsTArray<nsCString> systemLocales;
+ ASSERT_TRUE(NS_SUCCEEDED(
+ OSPreferences::GetInstance()->GetSystemLocales(systemLocales)));
+
+ ASSERT_FALSE(systemLocales.IsEmpty());
+}
+
+/**
+ * We test that on all platforms we test against (irrelevant of the tier),
+ * we will be able to retrieve at least a single locale out of the system.
+ *
+ * In theory, that may not be true, but if we encounter such platform we should
+ * decide how to handle this and special case and this test should make
+ * it not happen without us noticing.
+ */
+TEST(Intl_Locale_OSPreferences, GetRegionalPrefsLocales)
+{
+ nsTArray<nsCString> rgLocales;
+ ASSERT_TRUE(NS_SUCCEEDED(
+ OSPreferences::GetInstance()->GetRegionalPrefsLocales(rgLocales)));
+
+ ASSERT_FALSE(rgLocales.IsEmpty());
+}
+
+/**
+ * We test that on all platforms we test against,
+ * we will be able to retrieve a date and time pattern.
+ *
+ * This may come back empty on platforms where we don't have platforms
+ * bindings for, so effectively, we're testing for crashes. We should
+ * never crash.
+ */
+TEST(Intl_Locale_OSPreferences, GetDateTimePattern)
+{
+ nsAutoCString pattern;
+ OSPreferences* osprefs = OSPreferences::GetInstance();
+
+ struct Test {
+ int dateStyle;
+ int timeStyle;
+ const char* locale;
+ };
+ Test tests[] = {{0, 0, ""}, {1, 0, "pl"}, {2, 0, "de-DE"}, {3, 0, "fr"},
+ {4, 0, "ar"},
+
+ {0, 1, ""}, {0, 2, "it"}, {0, 3, ""}, {0, 4, "ru"},
+
+ {4, 1, ""}, {3, 2, "cs"}, {2, 3, ""}, {1, 4, "ja"}};
+
+ for (unsigned i = 0; i < mozilla::ArrayLength(tests); i++) {
+ const Test& t = tests[i];
+ if (NS_SUCCEEDED(osprefs->GetDateTimePattern(
+ t.dateStyle, t.timeStyle, nsDependentCString(t.locale), pattern))) {
+ ASSERT_TRUE((t.dateStyle == 0 && t.timeStyle == 0) || !pattern.IsEmpty());
+ }
+ }
+
+ // If the locale is not specified, we should get the pattern corresponding to
+ // the first regional prefs locale.
+ AutoTArray<nsCString, 10> rpLocales;
+ LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales);
+ ASSERT_TRUE(rpLocales.Length() > 0);
+
+ nsAutoCString rpLocalePattern;
+ ASSERT_TRUE(NS_SUCCEEDED(
+ osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleLong,
+ mozIOSPreferences::dateTimeFormatStyleLong,
+ rpLocales[0], rpLocalePattern)));
+ ASSERT_TRUE(NS_SUCCEEDED(
+ osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleLong,
+ mozIOSPreferences::dateTimeFormatStyleLong,
+ nsDependentCString(""), pattern)));
+ ASSERT_EQ(rpLocalePattern, pattern);
+}
+
+/**
+ * Test that is possible to override the OS defaults through a pref.
+ */
+TEST(Intl_Locale_OSPreferences, GetDateTimePatternPrefOverrides)
+{
+ nsresult nr;
+ nsAutoCString default_pattern, pattern;
+ OSPreferences* osprefs = OSPreferences::GetInstance();
+
+ struct {
+ const char* DatePref;
+ const char* TimePref;
+ int32_t DateTimeFormatStyle;
+ } configs[] = {{"intl.date_time.pattern_override.date_short",
+ "intl.date_time.pattern_override.time_short",
+ mozIOSPreferences::dateTimeFormatStyleShort},
+ {"intl.date_time.pattern_override.date_medium",
+ "intl.date_time.pattern_override.time_medium",
+ mozIOSPreferences::dateTimeFormatStyleMedium},
+ {"intl.date_time.pattern_override.date_long",
+ "intl.date_time.pattern_override.time_long",
+ mozIOSPreferences::dateTimeFormatStyleLong},
+ {"intl.date_time.pattern_override.date_full",
+ "intl.date_time.pattern_override.time_full",
+ mozIOSPreferences::dateTimeFormatStyleFull}};
+
+ for (const auto& config : configs) {
+ // Get default value for the OS
+ nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle,
+ mozIOSPreferences::dateTimeFormatStyleNone,
+ nsDependentCString(""), default_pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+
+ // Override date format
+ mozilla::Preferences::SetCString(config.DatePref, "yy-MM");
+ nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle,
+ mozIOSPreferences::dateTimeFormatStyleNone,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_TRUE(pattern.EqualsASCII("yy-MM"));
+
+ // Override time format
+ mozilla::Preferences::SetCString(config.TimePref, "HH:mm");
+ nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleNone,
+ config.DateTimeFormatStyle,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_TRUE(pattern.EqualsASCII("HH:mm"));
+
+ // Override both
+ nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle,
+ config.DateTimeFormatStyle,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_TRUE(pattern.Find("yy-MM") != kNotFound);
+ ASSERT_TRUE(pattern.Find("HH:mm") != kNotFound);
+
+ // Clear overrides, we should get the default value back.
+ mozilla::Preferences::ClearUser(config.DatePref);
+ mozilla::Preferences::ClearUser(config.TimePref);
+ nr = osprefs->GetDateTimePattern(config.DateTimeFormatStyle,
+ mozIOSPreferences::dateTimeFormatStyleNone,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_EQ(default_pattern, pattern);
+ }
+
+ // Test overriding connector
+ nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort,
+ mozIOSPreferences::dateTimeFormatStyleShort,
+ nsDependentCString(""), default_pattern);
+
+ mozilla::Preferences::SetCString("intl.date_time.pattern_override.date_short",
+ "yyyy-MM-dd");
+ mozilla::Preferences::SetCString("intl.date_time.pattern_override.time_short",
+ "HH:mm:ss");
+ mozilla::Preferences::SetCString(
+ "intl.date_time.pattern_override.connector_short", "{1} {0}");
+ nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort,
+ mozIOSPreferences::dateTimeFormatStyleShort,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_TRUE(pattern.EqualsASCII("yyyy-MM-dd HH:mm:ss"));
+
+ // Reset to date and time to defaults
+ mozilla::Preferences::ClearUser("intl.date_time.pattern_override.date_short");
+ mozilla::Preferences::ClearUser("intl.date_time.pattern_override.time_short");
+
+ // Invalid patterns are ignored
+ mozilla::Preferences::SetCString(
+ "intl.date_time.pattern_override.connector_short", "hello, world!");
+ nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort,
+ mozIOSPreferences::dateTimeFormatStyleShort,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_EQ(default_pattern, pattern);
+
+ // Clearing the override results in getting the default pattern back.
+ mozilla::Preferences::ClearUser(
+ "intl.date_time.pattern_override.connector_short");
+ nr = osprefs->GetDateTimePattern(mozIOSPreferences::dateTimeFormatStyleShort,
+ mozIOSPreferences::dateTimeFormatStyleShort,
+ nsDependentCString(""), pattern);
+ ASSERT_NS_SUCCEEDED(nr);
+ ASSERT_EQ(default_pattern, pattern);
+}
diff --git a/intl/locale/tests/gtest/moz.build b/intl/locale/tests/gtest/moz.build
new file mode 100644
index 0000000000..bd2a2b8f86
--- /dev/null
+++ b/intl/locale/tests/gtest/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestAppDateTimeFormat.cpp",
+ "TestLocaleService.cpp",
+ "TestLocaleServiceNegotiate.cpp",
+ "TestOSPreferences.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/intl/locale/tests/sort/us-ascii_base.txt b/intl/locale/tests/sort/us-ascii_base.txt
new file mode 100644
index 0000000000..3bc7fbc7be
--- /dev/null
+++ b/intl/locale/tests/sort/us-ascii_base.txt
@@ -0,0 +1,95 @@
+
+!
+"
+#
+$
+%
+&
+'
+(
+)
+*
++
+,
+-
+.
+/
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+:
+;
+<
+=
+>
+?
+@
+A
+B
+C
+D
+E
+F
+G
+H
+I
+J
+K
+L
+M
+N
+O
+P
+Q
+R
+S
+T
+U
+V
+W
+X
+Y
+Z
+[
+\
+]
+^
+_
+`
+a
+b
+c
+d
+e
+f
+g
+h
+i
+j
+k
+l
+m
+n
+o
+p
+q
+r
+s
+t
+u
+v
+w
+x
+y
+z
+{
+|
+}
+~
diff --git a/intl/locale/tests/sort/us-ascii_base_case_res.txt b/intl/locale/tests/sort/us-ascii_base_case_res.txt
new file mode 100644
index 0000000000..3e526d226b
--- /dev/null
+++ b/intl/locale/tests/sort/us-ascii_base_case_res.txt
@@ -0,0 +1,96 @@
+'
+-
+
+!
+"
+#
+$
+%
+&
+(
+)
+*
+,
+.
+/
+:
+;
+?
+@
+[
+\
+]
+^
+_
+`
+{
+|
+}
+~
++
+<
+=
+>
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+a
+A
+b
+B
+c
+C
+d
+D
+e
+E
+f
+F
+g
+G
+h
+H
+i
+I
+j
+J
+k
+K
+l
+L
+m
+M
+n
+N
+o
+O
+p
+P
+q
+Q
+r
+R
+s
+S
+t
+T
+u
+U
+v
+V
+w
+W
+x
+X
+y
+Y
+z
+Z
+
diff --git a/intl/locale/tests/sort/us-ascii_base_nocase_res.txt b/intl/locale/tests/sort/us-ascii_base_nocase_res.txt
new file mode 100644
index 0000000000..2d18096db5
--- /dev/null
+++ b/intl/locale/tests/sort/us-ascii_base_nocase_res.txt
@@ -0,0 +1,96 @@
+'
+-
+
+!
+"
+#
+$
+%
+&
+(
+)
+*
+,
+.
+/
+:
+;
+?
+@
+[
+\
+]
+^
+_
+`
+{
+|
+}
+~
++
+<
+=
+>
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+A
+a
+B
+b
+C
+c
+d
+D
+e
+E
+f
+F
+g
+G
+h
+H
+i
+I
+j
+J
+k
+K
+L
+l
+m
+M
+n
+N
+O
+o
+p
+P
+q
+Q
+r
+R
+s
+S
+t
+T
+u
+U
+v
+V
+w
+W
+X
+x
+Y
+y
+Z
+z
+
diff --git a/intl/locale/tests/sort/us-ascii_sort.txt b/intl/locale/tests/sort/us-ascii_sort.txt
new file mode 100644
index 0000000000..b6962f8de8
--- /dev/null
+++ b/intl/locale/tests/sort/us-ascii_sort.txt
@@ -0,0 +1,78 @@
+ludwig van beethoven
+Ludwig van Beethoven
+Ludwig van beethoven
+Jane
+jane
+JANE
+jAne
+jaNe
+janE
+JAne
+JaNe
+JanE
+JANe
+JaNE
+JAnE
+jANE
+Umberto Eco
+Umberto eco
+umberto eco
+umberto Eco
+UMBERTO ECO
+ace
+bash
+*ace
+!ace
+%ace
+~ace
+#ace
+cork
+denizen
+[denizen]
+(denizen)
+{denizen}
+/denizen/
+#denizen#
+$denizen$
+@denizen
+elf
+full
+gnome
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese
+hint
+Internationalization
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization
+Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizatio
+n
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion
+jostle
+kin
+Laymen
+lumens
+Lumens
+motleycrew
+motley crew
+niven's creative talents
+nivens creative talents
+opie
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies
+quilt's
+quilts
+quilt
+Rondo
+street
+tamale oxidization and iodization in rapid progress
+tamale oxidization and iodization in rapid Progress
+until
+vera
+Wobble
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname
+yearn
+zodiac
+
diff --git a/intl/locale/tests/sort/us-ascii_sort_case_res.txt b/intl/locale/tests/sort/us-ascii_sort_case_res.txt
new file mode 100644
index 0000000000..2720a83426
--- /dev/null
+++ b/intl/locale/tests/sort/us-ascii_sort_case_res.txt
@@ -0,0 +1,79 @@
+!ace
+#ace
+#denizen#
+$denizen$
+%ace
+(denizen)
+*ace
+/denizen/
+@denizen
+[denizen]
+{denizen}
+~ace
+ace
+bash
+cork
+denizen
+elf
+full
+gnome
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese
+hint
+Internationalization
+jane
+janE
+jaNe
+jAne
+jANE
+Jane
+JanE
+JaNe
+JaNE
+JAne
+JAnE
+JANe
+JANE
+jostle
+kin
+Laymen
+ludwig van beethoven
+Ludwig van beethoven
+Ludwig van Beethoven
+lumens
+Lumens
+motley crew
+motleycrew
+nivens creative talents
+niven's creative talents
+opie
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies
+quilt
+quilts
+quilt's
+Rondo
+street
+tamale oxidization and iodization in rapid progress
+tamale oxidization and iodization in rapid Progress
+umberto eco
+umberto Eco
+Umberto eco
+Umberto Eco
+UMBERTO ECO
+until
+veda
+Veda
+vera
+Vera
+Wobble
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme
+yearn
+Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization
+zodiac
diff --git a/intl/locale/tests/sort/us-ascii_sort_nocase_res.txt b/intl/locale/tests/sort/us-ascii_sort_nocase_res.txt
new file mode 100644
index 0000000000..fa3426d4e9
--- /dev/null
+++ b/intl/locale/tests/sort/us-ascii_sort_nocase_res.txt
@@ -0,0 +1,79 @@
+!ace
+#ace
+#denizen#
+$denizen$
+%ace
+(denizen)
+*ace
+/denizen/
+@denizen
+[denizen]
+{denizen}
+~ace
+ace
+bash
+cork
+denizen
+elf
+full
+gnome
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese
+hint
+Internationalization
+JANe
+jANE
+JAnE
+JaNE
+Jane
+JanE
+JaNe
+JAne
+janE
+jaNe
+jAne
+JANE
+jane
+jostle
+kin
+Laymen
+Ludwig van beethoven
+Ludwig van Beethoven
+ludwig van beethoven
+Lumens
+lumens
+motley crew
+motleycrew
+nivens creative talents
+niven's creative talents
+opie
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies
+quilt
+quilts
+quilt's
+Rondo
+street
+tamale oxidization and iodization in rapid Progress
+tamale oxidization and iodization in rapid progress
+Umberto eco
+Umberto Eco
+umberto eco
+UMBERTO ECO
+umberto Eco
+until
+Veda
+veda
+Vera
+vera
+Wobble
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme
+yearn
+Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization
+zodiac
diff --git a/intl/locale/tests/unit/data/chrome.manifest b/intl/locale/tests/unit/data/chrome.manifest
new file mode 100644
index 0000000000..a8678deef3
--- /dev/null
+++ b/intl/locale/tests/unit/data/chrome.manifest
@@ -0,0 +1 @@
+content locale ./
diff --git a/intl/locale/tests/unit/data/intl_on_workers_worker.js b/intl/locale/tests/unit/data/intl_on_workers_worker.js
new file mode 100644
index 0000000000..e6f01e71a8
--- /dev/null
+++ b/intl/locale/tests/unit/data/intl_on_workers_worker.js
@@ -0,0 +1,6 @@
+/* eslint-env worker */
+
+self.onmessage = function (data) {
+ let myLocale = Intl.NumberFormat().resolvedOptions().locale;
+ self.postMessage(myLocale);
+};
diff --git a/intl/locale/tests/unit/test_bug22310.js b/intl/locale/tests/unit/test_bug22310.js
new file mode 100644
index 0000000000..c14296d931
--- /dev/null
+++ b/intl/locale/tests/unit/test_bug22310.js
@@ -0,0 +1,65 @@
+String.prototype.has = function (s) {
+ return this.includes(s);
+};
+
+function dt(locale) {
+ var date = new Date("2008-06-30T13:56:34");
+ const dtOptions = {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ };
+ return date.toLocaleString(locale, dtOptions);
+}
+
+var all_passed = true;
+const tests = [
+ [dt("en-US").has("June"), "month name in en-US"],
+ [dt("en-US").has("2008"), "year in en-US"],
+ [dt("da").has("jun"), "month name in da"],
+ [dt("da-DK") == dt("da"), "da same as da-DK"],
+ [
+ dt("en-GB").has("30") &&
+ dt("en-GB").has("June") &&
+ dt("en-GB").indexOf("30") < dt("en-GB").indexOf("June"),
+ "day before month in en-GB",
+ ],
+ [
+ dt("en-US").has("30") &&
+ dt("en-US").has("June") &&
+ dt("en-US").indexOf("30") > dt("en-US").indexOf("June"),
+ "month before day in en-US",
+ ],
+ [dt("ja-JP").has("\u5E746\u670830\u65E5"), "year month and day in ja-JP"],
+ // The Firefox locale code ja-JP-mac will be resolved to a BCP47-compliant
+ // tag ja-JP-x-lvariant-mac by uloc_toLanguageTag
+ [
+ dt("ja-JP") == dt("ja-JP-x-lvariant-mac"),
+ "ja-JP-x-lvariant-mac same as ja-JP",
+ ],
+ [dt("nn-NO").has("juni"), "month name in nn-NO"],
+ [dt("nb-NO").has("juni"), "month name in nb-NO"],
+ // Bug 1261775 - failures on win10
+ //[dt("no-NO").has("30. juni"), "month name in no-NO"],
+ [dt("sv-SE").has("30 jun"), "month name in sv-SE"],
+ [dt("kok").has("\u091C\u0942\u0928"), "month name in kok"],
+ [dt("ta-IN").has("\u0B9C\u0BC2\u0BA9\u0BCD"), "month name in ta-IN"],
+ [!!dt("ab-CD").length, "fallback for ab-CD"],
+];
+
+function one_test(testcase, msg) {
+ if (!testcase) {
+ all_passed = false;
+ dump("Unexpected date format: " + msg + "\n");
+ }
+}
+
+function run_test() {
+ for (var i = 0; i < tests.length; ++i) {
+ one_test(tests[i][0], tests[i][1]);
+ }
+ Assert.ok(all_passed);
+}
diff --git a/intl/locale/tests/unit/test_intl_on_workers.js b/intl/locale/tests/unit/test_intl_on_workers.js
new file mode 100644
index 0000000000..b5e05c4678
--- /dev/null
+++ b/intl/locale/tests/unit/test_intl_on_workers.js
@@ -0,0 +1,29 @@
+function run_test() {
+ do_load_manifest("data/chrome.manifest");
+
+ if (typeof Intl !== "object") {
+ dump("Intl not enabled, skipping test\n");
+ equal(true, true);
+ return;
+ }
+
+ let mainThreadLocale = Intl.NumberFormat().resolvedOptions().locale;
+ let testWorker = new Worker(
+ "chrome://locale/content/intl_on_workers_worker.js"
+ );
+ testWorker.onmessage = function (e) {
+ try {
+ let workerLocale = e.data;
+ equal(
+ mainThreadLocale,
+ workerLocale,
+ "Worker should inherit Intl locale from main thread."
+ );
+ } finally {
+ do_test_finished();
+ }
+ };
+
+ do_test_pending();
+ testWorker.postMessage("go!");
+}
diff --git a/intl/locale/tests/unit/test_langPackMatcher.js b/intl/locale/tests/unit/test_langPackMatcher.js
new file mode 100644
index 0000000000..03adec7bff
--- /dev/null
+++ b/intl/locale/tests/unit/test_langPackMatcher.js
@@ -0,0 +1,287 @@
+/* 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/. */
+
+const { getAddonAndLocalAPIsMocker } = ChromeUtils.importESModule(
+ "resource://testing-common/LangPackMatcherTestUtils.sys.mjs"
+);
+const { LangPackMatcher } = ChromeUtils.importESModule(
+ "resource://gre/modules/LangPackMatcher.sys.mjs"
+);
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+const sandbox = sinon.createSandbox();
+const mockAddonAndLocaleAPIs = getAddonAndLocalAPIsMocker(this, sandbox);
+
+add_task(function initSandbox() {
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+});
+
+add_task(function test_appLocaleLanguageMismatch() {
+ sandbox.restore();
+ mockAddonAndLocaleAPIs({
+ systemLocale: "es-ES",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "es-ES",
+ systemLocale: { baseName: "es-ES", language: "es", region: "ES" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "language-mismatch",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "Español (ES)",
+ appLanguage: "English (US)",
+ },
+ });
+});
+
+add_task(function test_appLocaleRegionMismatch() {
+ sandbox.restore();
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "en-CA",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "en-CA",
+ systemLocale: { baseName: "en-CA", language: "en", region: "CA" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "region-mismatch",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "English (CA)",
+ appLanguage: "English (US)",
+ },
+ });
+});
+
+add_task(function test_appLocaleScriptMismatch() {
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "zh-Hans-CN",
+ appLocale: "zh-CN",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "zh-Hans-CN",
+ systemLocale: { baseName: "zh-Hans-CN", language: "zh", region: "CN" },
+ appLocaleRaw: "zh-CN",
+ appLocale: { baseName: "zh-CN", language: "zh", region: "CN" },
+ matchType: "match",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "Chinese (Hans, China)",
+ appLanguage: "简体中文",
+ },
+ });
+});
+
+add_task(function test_appLocaleInvalidSystem() {
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "Not valid",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "Not valid",
+ systemLocale: null,
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "unknown",
+ canLiveReload: null,
+ displayNames: { systemLanguage: null, appLanguage: "English (US)" },
+ });
+});
+
+add_task(function test_bidiSwitchDisabled() {
+ Services.prefs.setBoolPref(
+ "intl.multilingual.liveReloadBidirectional",
+ false
+ );
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "ar-EG",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "ar-EG",
+ systemLocale: { baseName: "ar-EG", language: "ar", region: "EG" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "language-mismatch",
+ canLiveReload: false,
+ displayNames: {
+ systemLanguage: "Arabic (Egypt)",
+ appLanguage: "English (US)",
+ },
+ });
+ Services.prefs.clearUserPref("intl.multilingual.liveReloadBidirectional");
+});
+
+add_task(async function test_bidiSwitchEnabled() {
+ Services.prefs.setBoolPref("intl.multilingual.liveReloadBidirectional", true);
+ sandbox.restore();
+ // Script mismatch:
+ mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "ar-EG",
+ appLocale: "en-US",
+ });
+
+ deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
+ systemLocaleRaw: "ar-EG",
+ systemLocale: { baseName: "ar-EG", language: "ar", region: "EG" },
+ appLocaleRaw: "en-US",
+ appLocale: { baseName: "en-US", language: "en", region: "US" },
+ matchType: "language-mismatch",
+ canLiveReload: true,
+ displayNames: {
+ systemLanguage: "Arabic (Egypt)",
+ appLanguage: "English (US)",
+ },
+ });
+
+ Services.prefs.clearUserPref("intl.multilingual.liveReloadBidirectional");
+});
+
+function shuffle(array) {
+ return array
+ .map(value => ({ value, sort: Math.random() }))
+ .sort((a, b) => a.sort - b.sort)
+ .map(({ value }) => value);
+}
+
+add_task(async function test_negotiateLangPacks() {
+ const negotiations = [
+ {
+ // Exact match found.
+ systemLocale: "en-US",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en-US",
+ expectedDisplayName: "English (US)",
+ },
+ {
+ // Region-less match.
+ systemLocale: "en-CA",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en",
+ expectedDisplayName: "English",
+ },
+ {
+ // Fallback to a different region.
+ systemLocale: "en-CA",
+ availableLangPacks: ["en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en-US",
+ expectedDisplayName: "English (US)",
+ },
+ {
+ // Match with a script. zh-Hans-CN is the locale used with simplified
+ // Chinese scripts, while zh-CN uses the Latin script.
+ systemLocale: "zh-Hans-CN",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-TW", "zh-Hans-CN"],
+ expectedLangPack: "zh-Hans-CN",
+ expectedDisplayName: "Chinese (Hans, China)",
+ },
+ {
+ // Match excluding script but matching region. zh-Hant-TW should
+ // match zh-TW before zh-CN.
+ systemLocale: "zh-Hant-TW",
+ availableLangPacks: ["en", "zh", "zh-CN", "zh-TW"],
+ expectedLangPack: "zh-TW",
+ expectedDisplayName: "正體中文",
+ },
+ {
+ // No reasonable match could be found.
+ systemLocale: "tlh", // Klingon
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: null,
+ expectedDisplayName: null,
+ },
+ {
+ // Weird, but valid locale identifiers.
+ systemLocale: "en-US-u-hc-h23-ca-islamic-civil-ss-true",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: "en-US",
+ expectedDisplayName: "English (US)",
+ },
+ {
+ // Invalid system locale
+ systemLocale: "Not valid",
+ availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
+ expectedLangPack: null,
+ expectedDisplayName: null,
+ },
+ ];
+
+ for (const {
+ systemLocale,
+ availableLangPacks,
+ expectedLangPack,
+ expectedDisplayName,
+ } of negotiations) {
+ sandbox.restore();
+ const { resolveLangPacks } = mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale,
+ });
+
+ const promise = LangPackMatcher.negotiateLangPackForLanguageMismatch();
+ // Shuffle the order to ensure that this test doesn't require on ordering of the
+ // langpack responses.
+ resolveLangPacks(shuffle(availableLangPacks));
+ const { langPack, langPackDisplayName } = await promise;
+ equal(
+ langPack?.target_locale,
+ expectedLangPack,
+ `Resolve the systemLocale "${systemLocale}" with available langpacks: ${JSON.stringify(
+ availableLangPacks
+ )}`
+ );
+ equal(
+ langPackDisplayName,
+ expectedDisplayName,
+ "The display name matches."
+ );
+ }
+});
+
+add_task(async function test_ensureLangPackInstalled() {
+ sandbox.restore();
+ const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
+ sandbox,
+ systemLocale: "es-ES",
+ appLocale: "en-US",
+ });
+
+ const negotiatePromise =
+ LangPackMatcher.negotiateLangPackForLanguageMismatch();
+ resolveLangPacks(["es-ES"]);
+ const { langPack } = await negotiatePromise;
+
+ const installPromise1 = LangPackMatcher.ensureLangPackInstalled(langPack);
+ const installPromise2 = LangPackMatcher.ensureLangPackInstalled(langPack);
+
+ resolveInstaller(["fake langpack"]);
+
+ info("Ensure both installers resolve when called twice in a row.");
+ await installPromise1;
+ await installPromise2;
+ ok(true, "Both were called.");
+});
diff --git a/intl/locale/tests/unit/test_localeService.js b/intl/locale/tests/unit/test_localeService.js
new file mode 100644
index 0000000000..ae2d949e80
--- /dev/null
+++ b/intl/locale/tests/unit/test_localeService.js
@@ -0,0 +1,240 @@
+/* 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/. */
+
+const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
+ Ci.mozIOSPreferences
+);
+
+const localeService = Services.locale;
+
+/**
+ * Make sure the locale service can be instantiated.
+ */
+
+add_test(function test_defaultLocale() {
+ const defaultLocale = localeService.defaultLocale;
+ Assert.ok(defaultLocale.length !== 0, "Default locale is not empty");
+ run_next_test();
+});
+
+add_test(function test_lastFallbackLocale() {
+ const lastFallbackLocale = localeService.lastFallbackLocale;
+ Assert.ok(lastFallbackLocale === "en-US", "Last fallback locale is en-US");
+ run_next_test();
+});
+
+add_test(function test_appLocalesAsLangTags() {
+ const appLocale = localeService.appLocaleAsLangTag;
+ Assert.ok(appLocale != "", "appLocale is non-empty");
+
+ const appLocales = localeService.appLocalesAsLangTags;
+ Assert.ok(Array.isArray(appLocales), "appLocales returns an array");
+
+ Assert.ok(
+ appLocale == appLocales[0],
+ "appLocale matches first entry in appLocales"
+ );
+
+ const enUSLocales = appLocales.filter(loc => loc === "en-US");
+ Assert.ok(enUSLocales.length == 1, "en-US is present exactly one time");
+
+ run_next_test();
+});
+
+const PREF_REQUESTED_LOCALES = "intl.locale.requested";
+const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed";
+
+add_test(function test_requestedLocales() {
+ const requestedLocales = localeService.requestedLocales;
+ Assert.ok(
+ Array.isArray(requestedLocales),
+ "requestedLocales returns an array"
+ );
+
+ run_next_test();
+});
+
+/**
+ * In this test we verify that after we set an observer on the LocaleService
+ * event for requested locales change, it will be fired when the
+ * pref for matchOS is set to true.
+ *
+ * Then, we test that when the matchOS is set to true, we will retrieve
+ * OS locale from requestedLocales.
+ */
+add_test(function test_requestedLocales_matchOS() {
+ do_test_pending();
+
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "ar-IR");
+
+ const observer = {
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case REQ_LOC_CHANGE_EVENT:
+ const reqLocs = localeService.requestedLocales;
+ Assert.ok(reqLocs[0] === osPrefs.systemLocale);
+ Services.obs.removeObserver(observer, REQ_LOC_CHANGE_EVENT);
+ do_test_finished();
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, REQ_LOC_CHANGE_EVENT);
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "");
+
+ run_next_test();
+});
+
+/**
+ * In this test we verify that after we set an observer on the LocaleService
+ * event for requested locales change, it will be fired when the
+ * pref for browser UI locale changes.
+ */
+add_test(function test_requestedLocales_onChange() {
+ do_test_pending();
+
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "ar-IR");
+
+ const observer = {
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case REQ_LOC_CHANGE_EVENT:
+ const reqLocs = localeService.requestedLocales;
+ Assert.ok(reqLocs[0] === "sr-RU");
+ Services.obs.removeObserver(observer, REQ_LOC_CHANGE_EVENT);
+ do_test_finished();
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, REQ_LOC_CHANGE_EVENT);
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "sr-RU");
+
+ run_next_test();
+});
+
+add_test(function test_requestedLocale() {
+ Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "tlh");
+
+ let requestedLocale = localeService.requestedLocale;
+ Assert.ok(
+ requestedLocale === "tlh",
+ "requestedLocale returns the right value"
+ );
+
+ Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
+
+ run_next_test();
+});
+
+add_test(function test_requestedLocales() {
+ localeService.requestedLocales = ["de-AT", "de-DE", "de-CH"];
+
+ let locales = localeService.requestedLocales;
+ Assert.ok(locales[0] === "de-AT");
+ Assert.ok(locales[1] === "de-DE");
+ Assert.ok(locales[2] === "de-CH");
+
+ run_next_test();
+});
+
+add_test(function test_isAppLocaleRTL() {
+ Assert.ok(typeof localeService.isAppLocaleRTL === "boolean");
+
+ run_next_test();
+});
+
+add_test(function test_isAppLocaleRTL_pseudo() {
+ let avLocales = localeService.availableLocales;
+ let reqLocales = localeService.requestedLocales;
+
+ localeService.availableLocales = ["en-US"];
+ localeService.requestedLocales = ["en-US"];
+ Services.prefs.setCharPref("intl.l10n.pseudo", "");
+
+ Assert.ok(localeService.isAppLocaleRTL === false);
+
+ Services.prefs.setCharPref("intl.l10n.pseudo", "bidi");
+ Assert.ok(localeService.isAppLocaleRTL === true);
+
+ Services.prefs.setCharPref("intl.l10n.pseudo", "accented");
+ Assert.ok(localeService.isAppLocaleRTL === false);
+
+ // Clean up
+ localeService.availableLocales = avLocales;
+ localeService.requestedLocales = reqLocales;
+ Services.prefs.clearUserPref("intl.l10n.pseudo");
+
+ run_next_test();
+});
+
+add_test(function test_packagedLocales() {
+ const locales = localeService.packagedLocales;
+ Assert.ok(locales.length !== 0, "Packaged locales are empty");
+ run_next_test();
+});
+
+add_test(function test_availableLocales() {
+ const avLocales = localeService.availableLocales;
+ localeService.availableLocales = ["und", "ar-IR"];
+
+ let locales = localeService.availableLocales;
+ Assert.ok(locales.length == 2);
+ Assert.ok(locales[0] === "und");
+ Assert.ok(locales[1] === "ar-IR");
+
+ localeService.availableLocales = avLocales;
+
+ run_next_test();
+});
+
+/**
+ * This test verifies that all values coming from the pref are sanitized.
+ */
+add_test(function test_requestedLocales_sanitize() {
+ Services.prefs.setStringPref(
+ PREF_REQUESTED_LOCALES,
+ "de,2,#$@#,pl,ąó,!a2,DE-at,,;"
+ );
+
+ let locales = localeService.requestedLocales;
+ Assert.equal(locales[0], "de");
+ Assert.equal(locales[1], "pl");
+ Assert.equal(locales[2], "de-AT");
+ Assert.equal(locales.length, 3);
+
+ Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
+
+ run_next_test();
+});
+
+add_test(function test_handle_ja_JP_mac() {
+ const bkpAvLocales = localeService.availableLocales;
+
+ localeService.availableLocales = ["ja-JP-mac", "en-US"];
+
+ localeService.requestedLocales = ["ja-JP-mac"];
+
+ let reqLocales = localeService.requestedLocales;
+ Assert.equal(reqLocales[0], "ja-JP-macos");
+
+ let avLocales = localeService.availableLocales;
+ Assert.equal(avLocales[0], "ja-JP-macos");
+
+ let appLocales = localeService.appLocalesAsBCP47;
+ Assert.equal(appLocales[0], "ja-JP-macos");
+
+ let appLocalesAsLT = localeService.appLocalesAsLangTags;
+ Assert.equal(appLocalesAsLT[0], "ja-JP-mac");
+
+ Assert.equal(localeService.appLocaleAsLangTag, "ja-JP-mac");
+
+ localeService.availableLocales = bkpAvLocales;
+
+ run_next_test();
+});
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
+});
diff --git a/intl/locale/tests/unit/test_localeService_negotiateLanguages.js b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js
new file mode 100644
index 0000000000..f929bc3a43
--- /dev/null
+++ b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js
@@ -0,0 +1,205 @@
+/* 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/. */
+
+const localeService = Services.locale;
+
+const data = {
+ filtering: {
+ "exact match": [
+ [["en"], ["en"], ["en"]],
+ [["en-US"], ["en-US"], ["en-US"]],
+ [["en-Latn-US"], ["en-Latn-US"], ["en-Latn-US"]],
+ [["en-Latn-US-windows"], ["en-Latn-US-windows"], ["en-Latn-US-windows"]],
+ [["fr-FR"], ["de", "it", "fr-FR"], ["fr-FR"]],
+ [
+ ["fr", "pl", "de-DE"],
+ ["pl", "en-US", "de-DE"],
+ ["pl", "de-DE"],
+ ],
+ ],
+ "available as range": [
+ [["en-US"], ["en"], ["en"]],
+ [["en-Latn-US"], ["en-US"], ["en-US"]],
+ [["en-US-windows"], ["en-US"], ["en-US"]],
+ [
+ ["fr-CA", "de-DE"],
+ ["fr", "it", "de"],
+ ["fr", "de"],
+ ],
+ [["ja-JP-windows"], ["ja"], ["ja"]],
+ [
+ ["en-Latn-GB", "en-Latn-IN"],
+ ["en-IN", "en-GB"],
+ ["en-GB", "en-IN"],
+ ],
+ ],
+ "should match on likely subtag": [
+ [["en"], ["en-GB", "de", "en-US"], ["en-US", "en-GB"]],
+ [
+ ["en"],
+ ["en-Latn-GB", "de", "en-Latn-US"],
+ ["en-Latn-US", "en-Latn-GB"],
+ ],
+ [["fr"], ["fr-CA", "fr-FR"], ["fr-FR", "fr-CA"]],
+ [["az-IR"], ["az-Latn", "az-Arab"], ["az-Arab"]],
+ [["sr-RU"], ["sr-Cyrl", "sr-Latn"], ["sr-Latn"]],
+ [["sr"], ["sr-Latn", "sr-Cyrl"], ["sr-Cyrl"]],
+ [["zh-GB"], ["zh-Hans", "zh-Hant"], ["zh-Hant"]],
+ [["sr", "ru"], ["sr-Latn", "ru"], ["ru"]],
+ [["sr-RU"], ["sr-Latn-RO", "sr-Cyrl"], ["sr-Latn-RO"]],
+ ],
+ "should match likelySubtag region over other regions": [
+ [["en-CA"], ["en-ZA", "en-GB", "en-US"], ["en-US", "en-ZA", "en-GB"]],
+ ],
+ "should match cross-region": [
+ [["en"], ["en-US"], ["en-US"]],
+ [["en-US"], ["en-GB"], ["en-GB"]],
+ [["en-Latn-US"], ["en-Latn-GB"], ["en-Latn-GB"]],
+ ],
+ "should match cross-variant": [
+ [["en-US-linux"], ["en-US-windows"], ["en-US-windows"]],
+ ],
+ "should prioritize properly": [
+ // exact match first
+ [
+ ["en-US"],
+ ["en-US-windows", "en", "en-US"],
+ ["en-US", "en", "en-US-windows"],
+ ],
+ // available as range second
+ [["en-Latn-US"], ["en-GB", "en-US"], ["en-US", "en-GB"]],
+ // likely subtags third
+ [["en"], ["en-Cyrl-US", "en-Latn-US"], ["en-Latn-US"]],
+ // variant range fourth
+ [
+ ["en-US-macos"],
+ ["en-US-windows", "en-GB-macos"],
+ ["en-US-windows", "en-GB-macos"],
+ ],
+ // regional range fifth
+ [["en-US-macos"], ["en-GB-windows"], ["en-GB-windows"]],
+ [["en-US"], ["en-GB", "en"], ["en", "en-GB"]],
+ [
+ ["fr-CA-macos", "de-DE"],
+ ["de-DE", "fr-FR-windows"],
+ ["fr-FR-windows", "de-DE"],
+ ],
+ ],
+ "should handle default locale properly": [
+ [["fr"], ["de", "it"], []],
+ [["fr"], ["de", "it"], "en-US", ["en-US"]],
+ [["fr"], ["de", "en-US"], "en-US", ["en-US"]],
+ [
+ ["fr", "de-DE"],
+ ["de-DE", "fr-CA"],
+ "en-US",
+ ["fr-CA", "de-DE", "en-US"],
+ ],
+ ],
+ "should handle all matches on the 1st higher than any on the 2nd": [
+ [
+ ["fr-CA-macos", "de-DE"],
+ ["de-DE", "fr-FR-windows"],
+ ["fr-FR-windows", "de-DE"],
+ ],
+ ],
+ "should handle cases and underscores, returning the form given in the 'available' list":
+ [
+ [["fr_FR"], ["fr-FR"], ["fr-FR"]],
+ [["fr_fr"], ["fr-FR"], ["fr-FR"]],
+ [["fr_Fr"], ["fr-fR"], ["fr-fR"]],
+ [["fr_lAtN_fr"], ["fr-Latn-FR"], ["fr-Latn-FR"]],
+ [["fr_FR"], ["fr_FR"], ["fr_FR"]],
+ [["fr-FR"], ["fr_FR"], ["fr_FR"]],
+ [["fr_Cyrl_FR_macos"], ["fr_Cyrl_fr-macos"], ["fr_Cyrl_fr-macos"]],
+ ],
+ "should handle mozilla specific 3-letter variants": [
+ [
+ ["ja-JP-mac", "de-DE"],
+ ["ja-JP-mac", "de-DE"],
+ ["ja-JP-mac", "de-DE"],
+ ],
+ ],
+ "should not crash on invalid input": [
+ [["fą-FŻ"], ["ór_Fń"], []],
+ [["2"], ["ąóżł"], []],
+ [[[""]], ["fr-FR"], []],
+ ],
+ "should not match on invalid input": [[["en"], ["ťŮ"], []]],
+ },
+ matching: {
+ "should match only one per requested": [
+ [
+ ["fr", "en"],
+ ["en-US", "fr-FR", "en", "fr"],
+ null,
+ localeService.langNegStrategyMatching,
+ ["fr", "en"],
+ ],
+ ],
+ },
+ lookup: {
+ "should match only one": [
+ [
+ ["fr-FR", "en"],
+ ["en-US", "fr-FR", "en", "fr"],
+ "en-US",
+ localeService.langNegStrategyLookup,
+ ["fr-FR"],
+ ],
+ ],
+ },
+};
+
+function run_test() {
+ const nl = localeService.negotiateLanguages;
+
+ const json = JSON.stringify;
+ for (const strategy in data) {
+ for (const groupName in data[strategy]) {
+ const group = data[strategy][groupName];
+ for (const test of group) {
+ const requested = test[0];
+ const available = test[1];
+ const defaultLocale = test.length > 3 ? test[2] : undefined;
+ const strategyInner = test.length > 4 ? test[3] : undefined;
+ const supported = test[test.length - 1];
+
+ const result = nl(test[0], test[1], defaultLocale, strategyInner);
+ deepEqual(
+ result,
+ supported,
+ `\nExpected ${json(requested)} * ${json(available)} = ${json(
+ supported
+ )}.\n`
+ );
+ }
+ }
+ }
+
+ // Verify that we error out when requested or available is not an array.
+ for (const [req, avail] in [
+ [null, ["fr-FR"]],
+ [undefined, ["fr-FR"]],
+ [2, ["fr-FR"]],
+ ["fr-FR", ["fr-FR"]],
+ [["fr-FR"], null],
+ [["fr-FR"], undefined],
+ [["fr-FR"], 2],
+ [["fr-FR"], "fr-FR"],
+ [[null], []],
+ [[undefined], []],
+ [[undefined], [null]],
+ [[undefined], [undefined]],
+ [[null], [null]],
+ [[[]], [[2]]],
+ ]) {
+ Assert.throws(
+ () => {
+ nl(req, avail);
+ },
+ err => err.result == Cr.NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY
+ );
+ }
+}
diff --git a/intl/locale/tests/unit/test_osPreferences.js b/intl/locale/tests/unit/test_osPreferences.js
new file mode 100644
index 0000000000..6d1bb637ff
--- /dev/null
+++ b/intl/locale/tests/unit/test_osPreferences.js
@@ -0,0 +1,44 @@
+/* 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/. */
+
+function run_test() {
+ const osprefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
+ Ci.mozIOSPreferences
+ );
+
+ const systemLocale = osprefs.systemLocale;
+ Assert.ok(systemLocale != "", "systemLocale is non-empty");
+
+ const systemLocales = osprefs.systemLocales;
+ Assert.ok(Array.isArray(systemLocales), "systemLocales returns an array");
+
+ Assert.ok(
+ systemLocale == systemLocales[0],
+ "systemLocale matches first entry in systemLocales"
+ );
+
+ const rgLocales = osprefs.regionalPrefsLocales;
+ Assert.ok(Array.isArray(rgLocales), "regionalPrefsLocales returns an array");
+
+ const getDateTimePatternTests = [
+ [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleNone, ""],
+ [osprefs.dateTimeFormatStyleShort, osprefs.dateTimeFormatStyleNone, ""],
+ [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleLong, "ar"],
+ [osprefs.dateTimeFormatStyleFull, osprefs.dateTimeFormatStyleMedium, "ru"],
+ ];
+
+ for (let i = 0; i < getDateTimePatternTests.length; i++) {
+ const test = getDateTimePatternTests[i];
+
+ const pattern = osprefs.getDateTimePattern(...test);
+ if (
+ test[0] !== osprefs.dateTimeFormatStyleNone &&
+ test[1] !== osprefs.dateTImeFormatStyleNone
+ ) {
+ Assert.greater(pattern.length, 0, "pattern is not empty.");
+ }
+ }
+
+ Assert.ok(1, "osprefs didn't crash");
+}
diff --git a/intl/locale/tests/unit/xpcshell.toml b/intl/locale/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..bbce8bd46e
--- /dev/null
+++ b/intl/locale/tests/unit/xpcshell.toml
@@ -0,0 +1,21 @@
+[DEFAULT]
+head =""
+support-files = [
+ "data/intl_on_workers_worker.js",
+ "data/chrome.manifest",
+]
+
+["test_bug22310.js"]
+skip-if = ["os != 'win' && os != 'mac'"]
+
+["test_intl_on_workers.js"]
+skip-if = ["os == 'android'"] # bug 1309447
+
+["test_langPackMatcher.js"]
+
+["test_localeService.js"]
+
+["test_localeService_negotiateLanguages.js"]
+
+["test_osPreferences.js"]
+skip-if = ["os == 'android'"] # bug 1344596