diff options
author | Timothy Flynn <trflynn89@pm.me> | 2022-01-26 22:44:01 -0500 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-01-27 21:16:44 +0000 |
commit | a2e791277ed64fd78465da06517f409a2d738e95 (patch) | |
tree | 516db33d81c6518c9becd2eb7729a4848b1f55c6 | |
parent | 9c5d7e515ce935e32dbec31e494a9e4daffbc87d (diff) | |
download | serenity-a2e791277ed64fd78465da06517f409a2d738e95.zip |
LibJS: Implement Intl.RelativeTimeFormat.prototype.formatToParts
5 files changed, 210 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp index c2653605cc..95722547fa 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp @@ -6,6 +6,7 @@ #include <AK/StringBuilder.h> #include <LibJS/Runtime/AbstractOperations.h> +#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/Intl/NumberFormat.h> #include <LibJS/Runtime/Intl/NumberFormatConstructor.h> @@ -313,4 +314,46 @@ ThrowCompletionOr<String> format_relative_time(GlobalObject& global_object, Rela return result.build(); } +// 17.1.6 FormatRelativeTimeToParts ( relativeTimeFormat, value, unit ), https://tc39.es/ecma402/#sec-FormatRelativeTimeToParts +ThrowCompletionOr<Array*> format_relative_time_to_parts(GlobalObject& global_object, RelativeTimeFormat& relative_time_format, double value, StringView unit) +{ + auto& vm = global_object.vm(); + + // 1. Let parts be ? PartitionRelativeTimePattern(relativeTimeFormat, value, unit). + auto parts = TRY(partition_relative_time_pattern(global_object, relative_time_format, value, unit)); + + // 2. Let result be ArrayCreate(0). + auto* result = MUST(Array::create(global_object, 0)); + + // 3. Let n be 0. + size_t n = 0; + + // 4. For each Record { [[Type]], [[Value]], [[Unit]] } part in parts, do + for (auto& part : parts) { + // a. Let O be OrdinaryObjectCreate(%Object.prototype%). + auto* object = Object::create(global_object, global_object.object_prototype()); + + // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). + MUST(object->create_data_property_or_throw(vm.names.type, js_string(vm, part.type))); + + // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). + MUST(object->create_data_property_or_throw(vm.names.value, js_string(vm, move(part.value)))); + + // d. If part.[[Unit]] is not empty, then + if (!part.unit.is_empty()) { + // i. Perform ! CreateDataPropertyOrThrow(O, "unit", part.[[Unit]]). + MUST(object->create_data_property_or_throw(vm.names.unit, js_string(vm, part.unit))); + } + + // e. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O). + MUST(result->create_data_property_or_throw(n, object)); + + // f. Increment n by 1. + ++n; + } + + // 5. Return result. + return result; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h index e19ea4bd3e..57495591f3 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h @@ -82,5 +82,6 @@ ThrowCompletionOr<Unicode::TimeUnit> singular_relative_time_unit(GlobalObject& g ThrowCompletionOr<Vector<PatternPartitionWithUnit>> partition_relative_time_pattern(GlobalObject& global_object, RelativeTimeFormat& relative_time_format, double value, StringView unit); Vector<PatternPartitionWithUnit> make_parts_list(StringView pattern, StringView unit, Vector<PatternPartition> parts); ThrowCompletionOr<String> format_relative_time(GlobalObject& global_object, RelativeTimeFormat& relative_time_format, double value, StringView unit); +ThrowCompletionOr<Array*> format_relative_time_to_parts(GlobalObject& global_object, RelativeTimeFormat& relative_time_format, double value, StringView unit); } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp index b0c152cf9e..33c902b55c 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h> @@ -26,6 +27,7 @@ void RelativeTimeFormatPrototype::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.format, format, 2, attr); + define_native_function(vm.names.formatToParts, format_to_parts, 2, attr); define_native_function(vm.names.resolvedOptions, resolved_options, 0, attr); } @@ -47,6 +49,23 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::format) return js_string(vm, move(formatted)); } +// 17.4.4 Intl.RelativeTimeFormat.prototype.formatToParts ( value, unit ), https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat.prototype.formatToParts +JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::format_to_parts) +{ + // 1. Let relativeTimeFormat be the this value. + // 2. Perform ? RequireInternalSlot(relativeTimeFormat, [[InitializedRelativeTimeFormat]]). + auto* relative_time_format = TRY(typed_this_object(global_object)); + + // 3. Let value be ? ToNumber(value). + auto value = TRY(vm.argument(0).to_number(global_object)); + + // 4. Let unit be ? ToString(unit). + auto unit = TRY(vm.argument(1).to_string(global_object)); + + // 5. Return ? FormatRelativeTimeToParts(relativeTimeFormat, value, unit). + return TRY(format_relative_time_to_parts(global_object, *relative_time_format, value.as_double(), unit)); +} + // 17.4.5 Intl.RelativeTimeFormat.prototype.resolvedOptions ( ), https://tc39.es/ecma402/#sec-intl.relativetimeformat.prototype.resolvedoptions JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::resolved_options) { diff --git a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h index c7dbf0eabf..eed456463b 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h @@ -21,6 +21,7 @@ public: private: JS_DECLARE_NATIVE_FUNCTION(format); + JS_DECLARE_NATIVE_FUNCTION(format_to_parts); JS_DECLARE_NATIVE_FUNCTION(resolved_options); }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.formatToParts.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.formatToParts.js new file mode 100644 index 0000000000..d9adc0f169 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.formatToParts.js @@ -0,0 +1,146 @@ +describe("errors", () => { + test("called on non-RelativeimeFormat object", () => { + expect(() => { + Intl.RelativeTimeFormat.prototype.formatToParts(1); + }).toThrowWithMessage(TypeError, "Not an object of type Intl.RelativeTimeFormat"); + }); + + test("called with value that cannot be converted to a number", () => { + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(Symbol.hasInstance, "year"); + }).toThrowWithMessage(TypeError, "Cannot convert symbol to number"); + + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(1n, "year"); + }).toThrowWithMessage(TypeError, "Cannot convert BigInt to number"); + }); + + test("called with unit that cannot be converted to a number", () => { + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(1, Symbol.hasInstance); + }).toThrowWithMessage(TypeError, "Cannot convert symbol to string"); + }); + + test("called with non-finite value", () => { + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(Infinity, "year"); + }).toThrowWithMessage(RangeError, "Number must not be NaN or Infinity"); + + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(-Infinity, "year"); + }).toThrowWithMessage(RangeError, "Number must not be NaN or Infinity"); + + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(NaN, "year"); + }).toThrowWithMessage(RangeError, "Number must not be NaN or Infinity"); + }); + + test("called with non-sanctioned unit", () => { + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(1); + }).toThrowWithMessage(RangeError, "Unit undefined is not a valid time unit"); + + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(1, null); + }).toThrowWithMessage(RangeError, "Unit null is not a valid time unit"); + + expect(() => { + new Intl.RelativeTimeFormat().formatToParts(1, "hello!"); + }).toThrowWithMessage(RangeError, "Unit hello! is not a valid time unit"); + }); +}); + +const en = new Intl.RelativeTimeFormat("en", { style: "long", numeric: "always" }); +const ar = new Intl.RelativeTimeFormat("ar", { style: "narrow", numeric: "auto" }); + +describe("correct behavior", () => { + test("second", () => { + expect(en.formatToParts(5, "second")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "second" }, + { type: "literal", value: " seconds" }, + ]); + + expect(ar.formatToParts(-1, "second")).toEqual([ + { type: "literal", value: "قبل ثانية واحدة" }, + ]); + }); + + test("minute", () => { + expect(en.formatToParts(5, "minute")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "minute" }, + { type: "literal", value: " minutes" }, + ]); + + expect(ar.formatToParts(-1, "minute")).toEqual([ + { type: "literal", value: "قبل دقيقة واحدة" }, + ]); + }); + + test("hour", () => { + expect(en.formatToParts(5, "hour")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "hour" }, + { type: "literal", value: " hours" }, + ]); + + expect(ar.formatToParts(-1, "hour")).toEqual([ + { type: "literal", value: "قبل ساعة واحدة" }, + ]); + }); + + test("day", () => { + expect(en.formatToParts(5, "day")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "day" }, + { type: "literal", value: " days" }, + ]); + + expect(ar.formatToParts(-1, "day")).toEqual([{ type: "literal", value: "أمس" }]); + }); + + test("week", () => { + expect(en.formatToParts(5, "week")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "week" }, + { type: "literal", value: " weeks" }, + ]); + + expect(ar.formatToParts(-1, "week")).toEqual([ + { type: "literal", value: "الأسبوع الماضي" }, + ]); + }); + + test("month", () => { + expect(en.formatToParts(5, "month")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "month" }, + { type: "literal", value: " months" }, + ]); + + expect(ar.formatToParts(-1, "month")).toEqual([{ type: "literal", value: "الشهر الماضي" }]); + }); + + test("quarter", () => { + expect(en.formatToParts(5, "quarter")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "quarter" }, + { type: "literal", value: " quarters" }, + ]); + + expect(ar.formatToParts(-1, "quarter")).toEqual([ + { type: "literal", value: "الربع الأخير" }, + ]); + }); + + test("year", () => { + expect(en.formatToParts(5, "year")).toEqual([ + { type: "literal", value: "in " }, + { type: "integer", value: "5", unit: "year" }, + { type: "literal", value: " years" }, + ]); + + expect(ar.formatToParts(-1, "year")).toEqual([{ type: "literal", value: "السنة الماضية" }]); + }); +}); |