Jelajahi Sumber

LibJS: Implement ECMA-402 Number.prototype.toLocaleString

Timothy Flynn 3 tahun lalu
induk
melakukan
c19c3205ff

+ 27 - 0
Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp

@@ -7,9 +7,12 @@
 
 #include <AK/Function.h>
 #include <AK/TypeCasts.h>
+#include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/Completion.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Intl/NumberFormat.h>
+#include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
 #include <LibJS/Runtime/NumberObject.h>
 #include <LibJS/Runtime/NumberPrototype.h>
 
@@ -37,6 +40,7 @@ void NumberPrototype::initialize(GlobalObject& object)
     Object::initialize(object);
     u8 attr = Attribute::Configurable | Attribute::Writable;
     define_native_function(vm.names.toFixed, to_fixed, 1, attr);
+    define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
     define_native_function(vm.names.toString, to_string, 1, attr);
     define_native_function(vm.names.valueOf, value_of, 0, attr);
 }
@@ -77,6 +81,29 @@ JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_fixed)
     return js_string(vm, String::formatted("{:0.{1}}", number, static_cast<size_t>(fraction_digits)));
 }
 
+// 18.2.1 Number.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sup-number.prototype.tolocalestring
+JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_locale_string)
+{
+    auto locales = vm.argument(0);
+    auto options = vm.argument(1);
+
+    // 1. Let x be ? thisNumberValue(this value).
+    auto number_value = TRY(this_number_value(global_object, vm.this_value(global_object)));
+
+    MarkedValueList arguments { vm.heap() };
+    arguments.append(locales);
+    arguments.append(options);
+
+    // 2. Let numberFormat be ? Construct(%NumberFormat%, « locales, options »).
+    auto* number_format = static_cast<Intl::NumberFormat*>(TRY(construct(global_object, *global_object.intl_number_format_constructor(), move(arguments))));
+
+    // 3. Return ? FormatNumeric(numberFormat, x).
+    // Note: Our implementation of FormatNumeric does not throw.
+    auto formatted = Intl::format_numeric(*number_format, number_value.as_double());
+
+    return js_string(vm, move(formatted));
+}
+
 // 21.1.3.6 Number.prototype.toString ( [ radix ] ), https://tc39.es/ecma262/#sec-number.prototype.tostring
 JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/NumberPrototype.h

@@ -19,6 +19,7 @@ public:
     virtual ~NumberPrototype() override;
 
     JS_DECLARE_NATIVE_FUNCTION(to_fixed);
+    JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
     JS_DECLARE_NATIVE_FUNCTION(to_string);
     JS_DECLARE_NATIVE_FUNCTION(value_of);
 };

+ 78 - 0
Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toLocaleString.js

@@ -0,0 +1,78 @@
+describe("errors", () => {
+    test("must be called with numeric |this|", () => {
+        [true, [], {}, Symbol("foo"), "bar", 1n].forEach(value => {
+            expect(() => Number.prototype.toLocaleString.call(value)).toThrowWithMessage(
+                TypeError,
+                "Not an object of type Number"
+            );
+        });
+    });
+});
+
+describe("correct behavior", () => {
+    test("length", () => {
+        expect(Number.prototype.toLocaleString).toHaveLength(0);
+    });
+});
+
+describe("special values", () => {
+    test("NaN", () => {
+        expect(NaN.toLocaleString()).toBe("NaN");
+        expect(NaN.toLocaleString("en")).toBe("NaN");
+        expect(NaN.toLocaleString("ar")).toBe("ليس رقم");
+    });
+
+    test("Infinity", () => {
+        expect(Infinity.toLocaleString()).toBe("∞");
+        expect(Infinity.toLocaleString("en")).toBe("∞");
+        expect(Infinity.toLocaleString("ar")).toBe("∞");
+    });
+});
+
+describe("styles", () => {
+    test("decimal", () => {
+        expect((12).toLocaleString("en")).toBe("12");
+        expect((12).toLocaleString("ar")).toBe("\u0661\u0662");
+    });
+
+    test("percent", () => {
+        expect((0.234).toLocaleString("en", { style: "percent" })).toBe("23%");
+        expect((0.234).toLocaleString("ar", { style: "percent" })).toBe("\u0662\u0663\u066a\u061c");
+    });
+
+    test("currency", () => {
+        expect(
+            (1.23).toLocaleString("en", {
+                style: "currency",
+                currency: "USD",
+                currencyDisplay: "name",
+            })
+        ).toBe("1.23 US dollars");
+
+        expect(
+            (1.23).toLocaleString("ar", {
+                style: "currency",
+                currency: "USD",
+                currencyDisplay: "name",
+            })
+        ).toBe("\u0661\u066b\u0662\u0663 دولار أمريكي");
+    });
+
+    test("unit", () => {
+        expect(
+            (1.23).toLocaleString("en", {
+                style: "unit",
+                unit: "kilometer-per-hour",
+                unitDisplay: "long",
+            })
+        ).toBe("1.23 kilometers per hour");
+
+        expect(
+            (1.23).toLocaleString("ar", {
+                style: "unit",
+                unit: "kilometer-per-hour",
+                unitDisplay: "long",
+            })
+        ).toBe("\u0661\u066b\u0662\u0663 كيلومتر في الساعة");
+    });
+});