diff options
author | Timothy Flynn <trflynn89@pm.me> | 2022-01-25 11:50:14 -0500 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-01-25 19:02:59 +0000 |
commit | a2e31ed73687c975d552c4a6a8da7147a7efe0e9 (patch) | |
tree | 8e3243b0c264bfbd75545862c63d3d7e9278a1d5 /Userland/Libraries | |
parent | 79fdec85de20c35eba3ef34cdea12ab678c31586 (diff) | |
download | serenity-a2e31ed73687c975d552c4a6a8da7147a7efe0e9.zip |
LibJS: Implement the Intl.RelativeTimeFormat constructor
Diffstat (limited to 'Userland/Libraries')
4 files changed, 168 insertions, 1 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp index eed3a02a24..01988e7245 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp @@ -4,6 +4,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <LibJS/Runtime/AbstractOperations.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Intl/AbstractOperations.h> +#include <LibJS/Runtime/Intl/NumberFormat.h> +#include <LibJS/Runtime/Intl/NumberFormatConstructor.h> #include <LibJS/Runtime/Intl/RelativeTimeFormat.h> namespace JS::Intl { @@ -44,4 +49,79 @@ StringView RelativeTimeFormat::numeric_string() const } } +ThrowCompletionOr<RelativeTimeFormat*> initialize_relative_time_format(GlobalObject& global_object, RelativeTimeFormat& relative_time_format, Value locales_value, Value options_value) +{ + auto& vm = global_object.vm(); + + // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). + auto requested_locales = TRY(canonicalize_locale_list(global_object, locales_value)); + + // 2. Set options to ? CoerceOptionsToObject(options). + auto* options = TRY(coerce_options_to_object(global_object, options_value)); + + // 3. Let opt be a new Record. + LocaleOptions opt {}; + + // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). + auto matcher = TRY(get_option(global_object, *options, vm.names.localeMatcher, Value::Type::String, AK::Array { "lookup"sv, "best fit"sv }, "best fit"sv)); + + // 5. Set opt.[[LocaleMatcher]] to matcher. + opt.locale_matcher = matcher; + + // 6. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined). + auto numbering_system = TRY(get_option(global_object, *options, vm.names.numberingSystem, Value::Type::String, {}, Empty {})); + + // 7. If numberingSystem is not undefined, then + if (!numbering_system.is_undefined()) { + // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. + if (!Unicode::is_type_identifier(numbering_system.as_string().string())) + return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, numbering_system, "numberingSystem"sv); + + // 8. Set opt.[[nu]] to numberingSystem. + opt.nu = numbering_system.as_string().string(); + } + + // 9. Let localeData be %RelativeTimeFormat%.[[LocaleData]]. + // 10. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData). + auto result = resolve_locale(requested_locales, opt, RelativeTimeFormat::relevant_extension_keys()); + + // 11. Let locale be r.[[locale]]. + auto locale = move(result.locale); + + // 12. Set relativeTimeFormat.[[Locale]] to locale. + relative_time_format.set_locale(locale); + + // 13. Set relativeTimeFormat.[[DataLocale]] to r.[[dataLocale]]. + relative_time_format.set_data_locale(move(result.data_locale)); + + // 14. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]]. + if (result.nu.has_value()) + relative_time_format.set_numbering_system(result.nu.release_value()); + + // 15. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long"). + auto style = TRY(get_option(global_object, *options, vm.names.style, Value::Type::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv)); + + // 16. Set relativeTimeFormat.[[Style]] to style. + relative_time_format.set_style(style.as_string().string()); + + // 17. Let numeric be ? GetOption(options, "numeric", "string", « "always", "auto" », "always"). + auto numeric = TRY(get_option(global_object, *options, vm.names.numeric, Value::Type::String, { "always"sv, "auto"sv }, "always"sv)); + + // 18. Set relativeTimeFormat.[[Numeric]] to numeric. + relative_time_format.set_numeric(numeric.as_string().string()); + + MarkedValueList arguments { vm.heap() }; + arguments.append(js_string(vm, locale)); + + // 19. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%NumberFormat%, « locale »). + auto* number_format = MUST(construct(global_object, *global_object.intl_number_format_constructor(), move(arguments))); + relative_time_format.set_number_format(static_cast<NumberFormat*>(number_format)); + + // 20. Let relativeTimeFormat.[[PluralRules]] be ! Construct(%PluralRules%, « locale »). + // FIXME: We do not yet support Intl.PluralRules. + + // 21. Return relativeTimeFormat. + return &relative_time_format; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h index 1622320b83..3cf6863638 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h @@ -9,6 +9,7 @@ #include <AK/Array.h> #include <AK/String.h> #include <AK/StringView.h> +#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Object.h> #include <LibUnicode/Locale.h> @@ -64,4 +65,6 @@ private: NumberFormat* m_number_format { nullptr }; // [[NumberFormat]] }; +ThrowCompletionOr<RelativeTimeFormat*> initialize_relative_time_format(GlobalObject& global_object, RelativeTimeFormat& relative_time_format, Value locales_value, Value options_value); + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp index 76b481da29..bfb3bfc8c9 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatConstructor.cpp @@ -38,13 +38,17 @@ ThrowCompletionOr<Value> RelativeTimeFormatConstructor::call() // 17.2.1 Intl.RelativeTimeFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat ThrowCompletionOr<Object*> RelativeTimeFormatConstructor::construct(FunctionObject& new_target) { + auto& vm = this->vm(); auto& global_object = this->global_object(); + auto locales = vm.argument(0); + auto options = vm.argument(1); + // 2. Let relativeTimeFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%RelativeTimeFormat.prototype%", « [[InitializedRelativeTimeFormat]], [[Locale]], [[DataLocale]], [[Style]], [[Numeric]], [[NumberFormat]], [[NumberingSystem]], [[PluralRules]] »). auto* relative_time_format = TRY(ordinary_create_from_constructor<RelativeTimeFormat>(global_object, new_target, &GlobalObject::intl_relative_time_format_prototype)); // 3. Return ? InitializeRelativeTimeFormat(relativeTimeFormat, locales, options). - return relative_time_format; + return TRY(initialize_relative_time_format(global_object, *relative_time_format, locales, options)); } } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.js index e755270127..cb10a4497a 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.js @@ -7,10 +7,90 @@ describe("errors", () => { "Intl.RelativeTimeFormat constructor must be called with 'new'" ); }); + + test("structurally invalid tag", () => { + expect(() => { + new Intl.RelativeTimeFormat("root"); + }).toThrowWithMessage(RangeError, "root is not a structurally valid language tag"); + + expect(() => { + new Intl.RelativeTimeFormat("en-"); + }).toThrowWithMessage(RangeError, "en- is not a structurally valid language tag"); + + expect(() => { + new Intl.RelativeTimeFormat("Latn"); + }).toThrowWithMessage(RangeError, "Latn is not a structurally valid language tag"); + + expect(() => { + new Intl.RelativeTimeFormat("en-u-aa-U-aa"); + }).toThrowWithMessage(RangeError, "en-u-aa-U-aa is not a structurally valid language tag"); + }); + + test("options is an invalid type", () => { + expect(() => { + new Intl.RelativeTimeFormat("en", null); + }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); + }); + + test("localeMatcher option is invalid", () => { + expect(() => { + new Intl.RelativeTimeFormat("en", { localeMatcher: "hello!" }); + }).toThrowWithMessage(RangeError, "hello! is not a valid value for option localeMatcher"); + }); + + test("numberingSystem option is invalid", () => { + expect(() => { + new Intl.RelativeTimeFormat("en", { numberingSystem: "hello!" }); + }).toThrowWithMessage(RangeError, "hello! is not a valid value for option numberingSystem"); + }); + + test("style option is invalid", () => { + expect(() => { + new Intl.RelativeTimeFormat("en", { style: "hello!" }); + }).toThrowWithMessage(RangeError, "hello! is not a valid value for option style"); + }); + + test("numeric option is invalid", () => { + expect(() => { + new Intl.RelativeTimeFormat("en", { numeric: "hello!" }); + }).toThrowWithMessage(RangeError, "hello! is not a valid value for option numeric"); + }); }); describe("normal behavior", () => { test("length is 0", () => { expect(Intl.RelativeTimeFormat).toHaveLength(0); }); + + test("all valid localeMatcher options", () => { + ["lookup", "best fit"].forEach(localeMatcher => { + expect(() => { + new Intl.RelativeTimeFormat("en", { localeMatcher: localeMatcher }); + }).not.toThrow(); + }); + }); + + test("valid numberingSystem options", () => { + ["latn", "arab", "abc-def-ghi"].forEach(numberingSystem => { + expect(() => { + new Intl.RelativeTimeFormat("en", { numberingSystem: numberingSystem }); + }).not.toThrow(); + }); + }); + + test("all valid style options", () => { + ["long", "short", "narrow"].forEach(style => { + expect(() => { + new Intl.RelativeTimeFormat("en", { style: style }); + }).not.toThrow(); + }); + }); + + test("all valid numeric options", () => { + ["always", "auto"].forEach(numeric => { + expect(() => { + new Intl.RelativeTimeFormat("en", { numeric: numeric }); + }).not.toThrow(); + }); + }); }); |