diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 0ec9e8c5ee..91084eaca6 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -175,6 +175,7 @@ namespace JS { P(get) \ P(getBigInt64) \ P(getBigUint64) \ + P(getCanonicalLocales) \ P(getDate) \ P(getDay) \ P(getFloat32) \ diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp index 8829143e68..a540568b81 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp @@ -4,7 +4,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include +#include #include #include @@ -27,6 +29,27 @@ void Intl::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_direct_property(vm.names.DisplayNames, global_object.intl_display_names_constructor(), attr); + + define_native_function(vm.names.getCanonicalLocales, get_canonical_locales, 1, attr); +} + +// 8.3.1 Intl.getCanonicalLocales ( locales ), https://tc39.es/ecma402/#sec-intl.getcanonicallocales +JS_DEFINE_NATIVE_FUNCTION(Intl::get_canonical_locales) +{ + auto locales = vm.argument(0); + + // 1. Let ll be ? CanonicalizeLocaleList(locales). + auto locale_list = canonicalize_locale_list(global_object, locales); + if (vm.exception()) + return {}; + + MarkedValueList marked_locale_list { vm.heap() }; + marked_locale_list.ensure_capacity(locale_list.size()); + for (auto& locale : locale_list) + marked_locale_list.append(js_string(vm, move(locale))); + + // 2. Return CreateArrayFromList(ll). + return Array::create_from(global_object, marked_locale_list); } } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Intl.h b/Userland/Libraries/LibJS/Runtime/Intl/Intl.h index b4c09740f5..fa1f69673e 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Intl.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/Intl.h @@ -17,6 +17,9 @@ public: explicit Intl(GlobalObject&); virtual void initialize(GlobalObject&) override; virtual ~Intl() override = default; + +private: + JS_DECLARE_NATIVE_FUNCTION(get_canonical_locales); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/Intl.getCanonicalLocales.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/Intl.getCanonicalLocales.js new file mode 100644 index 0000000000..c7e64c1468 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/Intl.getCanonicalLocales.js @@ -0,0 +1,76 @@ +describe("errors", () => { + test("invalid tag", () => { + expect(() => { + Intl.getCanonicalLocales("EN_US"); + }).toThrowWithMessage(RangeError, "EN_US is not a structurally valid language tag"); + }); + + test("root tag", () => { + expect(() => { + Intl.getCanonicalLocales("root"); + }).toThrowWithMessage(RangeError, "root is not a structurally valid language tag"); + }); + + test("no language tag", () => { + expect(() => { + Intl.getCanonicalLocales("Latn"); + }).toThrowWithMessage(RangeError, "Latn is not a structurally valid language tag"); + }); + + test("duplicate variant subtags", () => { + expect(() => { + Intl.getCanonicalLocales("en-POSIX-POSIX"); + }).toThrowWithMessage( + RangeError, + "en-POSIX-POSIX is not a structurally valid language tag" + ); + }); + + test("improperly placed separator", () => { + expect(() => { + Intl.getCanonicalLocales("en-"); + }).toThrowWithMessage(RangeError, "en- is not a structurally valid language tag"); + + expect(() => { + Intl.getCanonicalLocales("-en"); + }).toThrowWithMessage(RangeError, "-en is not a structurally valid language tag"); + + expect(() => { + Intl.getCanonicalLocales("en--US"); + }).toThrowWithMessage(RangeError, "en--US is not a structurally valid language tag"); + }); + + test("non string or object locale", () => { + expect(() => { + Intl.getCanonicalLocales([true]); + }).toThrowWithMessage(TypeError, "true is neither an object nor a string"); + }); +}); + +describe("normal behavior", () => { + test("length is 1", () => { + expect(Intl.getCanonicalLocales).toHaveLength(1); + }); + + test("valid locales", () => { + expect(Intl.getCanonicalLocales([])).toEqual([]); + expect(Intl.getCanonicalLocales("EN-US")).toEqual(["en-US"]); + expect(Intl.getCanonicalLocales(["EN-US"])).toEqual(["en-US"]); + expect(Intl.getCanonicalLocales(["EN-US", "Fr"])).toEqual(["en-US", "fr"]); + expect(Intl.getCanonicalLocales("EN-lATN-US")).toEqual(["en-Latn-US"]); + expect(Intl.getCanonicalLocales("EN-US-POSIX")).toEqual(["en-US-posix"]); + expect(Intl.getCanonicalLocales("EN-LATN-US-POSIX")).toEqual(["en-Latn-US-posix"]); + }); + + test("duplicate locales", () => { + expect(Intl.getCanonicalLocales(["EN-US", "en-US", "en-us"])).toEqual(["en-US"]); + }); + + test("non-array object", () => { + expect(Intl.getCanonicalLocales({})).toEqual([]); + expect(Intl.getCanonicalLocales({ en: 123 })).toEqual([]); + expect(Intl.getCanonicalLocales(undefined)).toEqual([]); + expect(Intl.getCanonicalLocales(true)).toEqual([]); + expect(Intl.getCanonicalLocales(123)).toEqual([]); + }); +});