summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2022-01-26 22:44:01 -0500
committerLinus Groh <mail@linusgroh.de>2022-01-27 21:16:44 +0000
commita2e791277ed64fd78465da06517f409a2d738e95 (patch)
tree516db33d81c6518c9becd2eb7729a4848b1f55c6
parent9c5d7e515ce935e32dbec31e494a9e4daffbc87d (diff)
downloadserenity-a2e791277ed64fd78465da06517f409a2d738e95.zip
LibJS: Implement Intl.RelativeTimeFormat.prototype.formatToParts
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.cpp43
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormat.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.cpp19
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/RelativeTimeFormatPrototype.h1
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/RelativeTimeFormat/RelativeTimeFormat.prototype.formatToParts.js146
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: "السنة الماضية" }]);
+ });
+});