From 07f12b108b4af594eb677972c4ddc89616d168b4 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 8 Sep 2021 21:34:27 -0400 Subject: [PATCH] LibJS: Implement a nearly empty Intl.NumberFormat object This adds plumbing for the Intl.NumberFormat object, constructor, and prototype. --- Userland/Libraries/LibJS/CMakeLists.txt | 3 + Userland/Libraries/LibJS/Forward.h | 3 +- .../Libraries/LibJS/Runtime/GlobalObject.cpp | 2 + .../Libraries/LibJS/Runtime/Intl/Intl.cpp | 2 + .../LibJS/Runtime/Intl/NumberFormat.cpp | 229 ++++++++++++++++++ .../LibJS/Runtime/Intl/NumberFormat.h | 173 +++++++++++++ .../Runtime/Intl/NumberFormatConstructor.cpp | 53 ++++ .../Runtime/Intl/NumberFormatConstructor.h | 28 +++ .../Runtime/Intl/NumberFormatPrototype.cpp | 28 +++ .../Runtime/Intl/NumberFormatPrototype.h | 22 ++ .../NumberFormat.@@toStringTag.js | 3 + .../Intl/NumberFormat/NumberFormat.js | 5 + 12 files changed, 550 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.@@toStringTag.js create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 5237a433705..a2585a6479d 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -83,6 +83,9 @@ set(SOURCES Runtime/Intl/Locale.cpp Runtime/Intl/LocaleConstructor.cpp Runtime/Intl/LocalePrototype.cpp + Runtime/Intl/NumberFormat.cpp + Runtime/Intl/NumberFormatConstructor.cpp + Runtime/Intl/NumberFormatPrototype.cpp Runtime/IteratorOperations.cpp Runtime/IteratorPrototype.cpp Runtime/JSONObject.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 642cf470290..234456b15b9 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -79,7 +79,8 @@ #define JS_ENUMERATE_INTL_OBJECTS \ __JS_ENUMERATE(DisplayNames, display_names, DisplayNamesPrototype, DisplayNamesConstructor) \ __JS_ENUMERATE(ListFormat, list_format, ListFormatPrototype, ListFormatConstructor) \ - __JS_ENUMERATE(Locale, locale, LocalePrototype, LocaleConstructor) + __JS_ENUMERATE(Locale, locale, LocalePrototype, LocaleConstructor) \ + __JS_ENUMERATE(NumberFormat, number_format, NumberFormatPrototype, NumberFormatConstructor) #define JS_ENUMERATE_TEMPORAL_OBJECTS \ __JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor) \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 19ea8700aa9..2d9d6b5ebf4 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -48,6 +48,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp index 325ab02c37a..800dcbcaf68 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/Intl.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace JS::Intl { @@ -33,6 +34,7 @@ void Intl::initialize(GlobalObject& global_object) define_direct_property(vm.names.DisplayNames, global_object.intl_display_names_constructor(), attr); define_direct_property(vm.names.ListFormat, global_object.intl_list_format_constructor(), attr); define_direct_property(vm.names.Locale, global_object.intl_locale_constructor(), attr); + define_direct_property(vm.names.NumberFormat, global_object.intl_number_format_constructor(), attr); define_native_function(vm.names.getCanonicalLocales, get_canonical_locales, 1, attr); } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp new file mode 100644 index 00000000000..ea6ae93a21c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS::Intl { + +// 15 NumberFormat Objects, https://tc39.es/ecma402/#numberformat-objects +NumberFormat::NumberFormat(Object& prototype) + : Object(prototype) +{ +} + +void NumberFormat::set_style(StringView style) +{ + if (style == "decimal"sv) + m_style = Style::Decimal; + else if (style == "percent"sv) + m_style = Style::Percent; + else if (style == "currency"sv) + m_style = Style::Currency; + else if (style == "unit"sv) + m_style = Style::Unit; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::style_string() const +{ + switch (m_style) { + case Style::Decimal: + return "decimal"sv; + case Style::Percent: + return "percent"sv; + case Style::Currency: + return "currency"sv; + case Style::Unit: + return "unit"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void NumberFormat::set_currency_display(StringView currency_display) +{ + if (currency_display == "code"sv) + m_currency_display = CurrencyDisplay::Code; + else if (currency_display == "symbol"sv) + m_currency_display = CurrencyDisplay::Symbol; + else if (currency_display == "narrowSymbol"sv) + m_currency_display = CurrencyDisplay::NarrowSymbol; + else if (currency_display == "name"sv) + m_currency_display = CurrencyDisplay::Name; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::currency_display_string() const +{ + VERIFY(m_currency_display.has_value()); + + switch (*m_currency_display) { + case CurrencyDisplay::Code: + return "code"sv; + case CurrencyDisplay::Symbol: + return "symbol"sv; + case CurrencyDisplay::NarrowSymbol: + return "narrowSymbol"sv; + case CurrencyDisplay::Name: + return "name"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void NumberFormat::set_currency_sign(StringView currency_sign) +{ + if (currency_sign == "standard"sv) + m_currency_sign = CurrencySign::Standard; + else if (currency_sign == "accounting"sv) + m_currency_sign = CurrencySign::Accounting; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::currency_sign_string() const +{ + VERIFY(m_currency_sign.has_value()); + + switch (*m_currency_sign) { + case CurrencySign::Standard: + return "standard"sv; + case CurrencySign::Accounting: + return "accounting"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void NumberFormat::set_unit_display(StringView unit_display) +{ + if (unit_display == "short"sv) + m_unit_display = UnitDisplay::Short; + else if (unit_display == "narrow"sv) + m_unit_display = UnitDisplay::Narrow; + else if (unit_display == "long"sv) + m_unit_display = UnitDisplay::Long; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::unit_display_string() const +{ + VERIFY(m_unit_display.has_value()); + + switch (*m_unit_display) { + case UnitDisplay::Short: + return "short"sv; + case UnitDisplay::Narrow: + return "narrow"sv; + case UnitDisplay::Long: + return "long"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +StringView NumberFormat::rounding_type_string() const +{ + switch (m_rounding_type) { + case RoundingType::SignificantDigits: + return "significantDigits"sv; + case RoundingType::FractionDigits: + return "fractionDigits"sv; + case RoundingType::CompactRounding: + return "compactRounding"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void NumberFormat::set_notation(StringView notation) +{ + if (notation == "standard"sv) + m_notation = Notation::Standard; + else if (notation == "scientific"sv) + m_notation = Notation::Scientific; + else if (notation == "engineering"sv) + m_notation = Notation::Engineering; + else if (notation == "compact"sv) + m_notation = Notation::Compact; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::notation_string() const +{ + switch (m_notation) { + case Notation::Standard: + return "standard"sv; + case Notation::Scientific: + return "scientific"sv; + case Notation::Engineering: + return "engineering"sv; + case Notation::Compact: + return "compact"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void NumberFormat::set_compact_display(StringView compact_display) +{ + if (compact_display == "short"sv) + m_compact_display = CompactDisplay::Short; + else if (compact_display == "long"sv) + m_compact_display = CompactDisplay::Long; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::compact_display_string() const +{ + VERIFY(m_compact_display.has_value()); + + switch (*m_compact_display) { + case CompactDisplay::Short: + return "short"sv; + case CompactDisplay::Long: + return "long"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +void NumberFormat::set_sign_display(StringView sign_display) +{ + if (sign_display == "auto"sv) + m_sign_display = SignDisplay::Auto; + else if (sign_display == "never"sv) + m_sign_display = SignDisplay::Never; + else if (sign_display == "always"sv) + m_sign_display = SignDisplay::Always; + else if (sign_display == "exceptZero"sv) + m_sign_display = SignDisplay::ExceptZero; + else + VERIFY_NOT_REACHED(); +} + +StringView NumberFormat::sign_display_string() const +{ + switch (m_sign_display) { + case SignDisplay::Auto: + return "auto"sv; + case SignDisplay::Never: + return "never"sv; + case SignDisplay::Always: + return "always"sv; + case SignDisplay::ExceptZero: + return "exceptZero"sv; + default: + VERIFY_NOT_REACHED(); + } +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h new file mode 100644 index 00000000000..e4187c9e690 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS::Intl { + +class NumberFormat final : public Object { + JS_OBJECT(NumberFormat, Object); + +public: + enum class Style { + Invalid, + Decimal, + Percent, + Currency, + Unit, + }; + + enum class CurrencyDisplay { + Code, + Symbol, + NarrowSymbol, + Name, + }; + + enum class CurrencySign { + Standard, + Accounting, + }; + + enum class UnitDisplay { + Short, + Narrow, + Long, + }; + + enum class RoundingType { + Invalid, + SignificantDigits, + FractionDigits, + CompactRounding, + }; + + enum class Notation { + Invalid, + Standard, + Scientific, + Engineering, + Compact, + }; + + enum class CompactDisplay { + Short, + Long, + }; + + enum class SignDisplay { + Invalid, + Auto, + Never, + Always, + ExceptZero, + }; + + NumberFormat(Object& prototype); + virtual ~NumberFormat() override = default; + + String const& locale() const { return m_locale; } + void set_locale(String locale) { m_locale = move(locale); } + + String const& data_locale() const { return m_data_locale; } + void set_data_locale(String data_locale) { m_data_locale = move(data_locale); } + + String const& numbering_system() const { return m_numbering_system; } + void set_numbering_system(String numbering_system) { m_numbering_system = move(numbering_system); } + + Style style() const { return m_style; } + StringView style_string() const; + void set_style(StringView style); + + bool has_currency() const { return m_currency.has_value(); } + String const& currency() const { return m_currency.value(); } + void set_currency(String currency) { m_currency = move(currency); } + + bool has_currency_display() const { return m_currency_display.has_value(); } + CurrencyDisplay currency_display() const { return *m_currency_display; } + StringView currency_display_string() const; + void set_currency_display(StringView currency_display); + + bool has_currency_sign() const { return m_currency_sign.has_value(); } + CurrencySign currency_sign() const { return *m_currency_sign; } + StringView currency_sign_string() const; + void set_currency_sign(StringView set_currency_sign); + + bool has_unit() const { return m_unit.has_value(); } + String const& unit() const { return m_unit.value(); } + void set_unit(String unit) { m_unit = move(unit); } + + bool has_unit_display() const { return m_unit_display.has_value(); } + UnitDisplay unit_display() const { return *m_unit_display; } + StringView unit_display_string() const; + void set_unit_display(StringView unit_display); + + int min_integer_digits() const { return m_min_integer_digits; } + void set_min_integer_digits(int min_integer_digits) { m_min_integer_digits = min_integer_digits; } + + bool has_min_fraction_digits() const { return m_min_fraction_digits.has_value(); } + int min_fraction_digits() const { return *m_min_fraction_digits; } + void set_min_fraction_digits(int min_fraction_digits) { m_min_fraction_digits = min_fraction_digits; } + + bool has_max_fraction_digits() const { return m_max_fraction_digits.has_value(); } + int max_fraction_digits() const { return *m_max_fraction_digits; } + void set_max_fraction_digits(int max_fraction_digits) { m_max_fraction_digits = max_fraction_digits; } + + bool has_min_significant_digits() const { return m_min_significant_digits.has_value(); } + int min_significant_digits() const { return *m_min_significant_digits; } + void set_min_significant_digits(int min_significant_digits) { m_min_significant_digits = min_significant_digits; } + + bool has_max_significant_digits() const { return m_max_significant_digits.has_value(); } + int max_significant_digits() const { return *m_max_significant_digits; } + void set_max_significant_digits(int max_significant_digits) { m_max_significant_digits = max_significant_digits; } + + bool use_grouping() const { return m_use_grouping; } + void set_use_grouping(bool use_grouping) { m_use_grouping = use_grouping; } + + RoundingType rounding_type() const { return m_rounding_type; } + StringView rounding_type_string() const; + void set_rounding_type(RoundingType rounding_type) { m_rounding_type = rounding_type; } + + Notation notation() const { return m_notation; } + StringView notation_string() const; + void set_notation(StringView notation); + + bool has_compact_display() const { return m_compact_display.has_value(); } + CompactDisplay compact_display() const { return *m_compact_display; } + StringView compact_display_string() const; + void set_compact_display(StringView compact_display); + + SignDisplay sign_display() const { return m_sign_display; } + StringView sign_display_string() const; + void set_sign_display(StringView sign_display); + +private: + String m_locale; // [[Locale]] + String m_data_locale; // [[DataLocale]] + String m_numbering_system; // [[NumberingSystem]] + Style m_style { Style::Invalid }; // [[Style]] + Optional m_currency {}; // [[Currency]] + Optional m_currency_display {}; // [[CurrencyDisplay]] + Optional m_currency_sign {}; // [[CurrencySign]] + Optional m_unit {}; // [[Unit]] + Optional m_unit_display {}; // [[UnitDisplay]] + int m_min_integer_digits { 0 }; // [[MinimumIntegerDigits]] + Optional m_min_fraction_digits {}; // [[MinimumFractionDigits]] + Optional m_max_fraction_digits {}; // [[MaximumFractionDigits]] + Optional m_min_significant_digits {}; // [[MinimumSignificantDigits]] + Optional m_max_significant_digits {}; // [[MaximumSignificantDigits]] + bool m_use_grouping { false }; // [[UseGrouping]] + RoundingType m_rounding_type { RoundingType::Invalid }; // [[RoundingType]] + Notation m_notation { Notation::Invalid }; // [[Notation]] + Optional m_compact_display {}; // [[CompactDisplay]] + SignDisplay m_sign_display { SignDisplay::Invalid }; // [[SignDisplay]] +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp new file mode 100644 index 00000000000..30649d6de0f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS::Intl { + +// 15.2 The Intl.NumberFormat Constructor, https://tc39.es/ecma402/#sec-intl-numberformat-constructor +NumberFormatConstructor::NumberFormatConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.NumberFormat.as_string(), *global_object.function_prototype()) +{ +} + +void NumberFormatConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 15.3.1 Intl.NumberFormat.prototype, https://tc39.es/ecma402/#sec-intl.numberformat.prototype + define_direct_property(vm.names.prototype, global_object.intl_number_format_prototype(), 0); + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); +} + +// 15.2.1 Intl.NumberFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.numberformat +Value NumberFormatConstructor::call() +{ + // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. + return construct(*this); +} + +// 15.2.1 Intl.NumberFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.numberformat +Value NumberFormatConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 2. Let numberFormat be ? OrdinaryCreateFromConstructor(newTarget, "%NumberFormat.prototype%", « [[InitializedNumberFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[Unit]], [[UnitDisplay]], [[Currency]], [[CurrencyDisplay]], [[CurrencySign]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[Notation]], [[CompactDisplay]], [[UseGrouping]], [[SignDisplay]], [[BoundFormat]] »). + auto* number_format = ordinary_create_from_constructor(global_object, new_target, &GlobalObject::intl_number_format_prototype); + if (vm.exception()) + return {}; + + // 5. Return numberFormat. + return number_format; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.h new file mode 100644 index 00000000000..c7f57b3ffeb --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Intl { + +class NumberFormatConstructor final : public NativeFunction { + JS_OBJECT(NumberFormatConstructor, NativeFunction); + +public: + explicit NumberFormatConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~NumberFormatConstructor() override = default; + + virtual Value call() override; + virtual Value construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp new file mode 100644 index 00000000000..da9e01bb3e3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Intl { + +// 15.4 Properties of the Intl.NumberFormat Prototype Object, https://tc39.es/ecma402/#sec-properties-of-intl-numberformat-prototype-object +NumberFormatPrototype::NumberFormatPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void NumberFormatPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); + + auto& vm = this->vm(); + + // 15.4.2 Intl.NumberFormat.prototype [ @@toStringTag ], https://tc39.es/ecma402/#sec-intl.numberformat.prototype-@@tostringtag + define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl.NumberFormat"), Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.h new file mode 100644 index 00000000000..a9bd3190061 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Intl { + +class NumberFormatPrototype final : public Object { + JS_OBJECT(NumberFormatPrototype, Object); + +public: + explicit NumberFormatPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~NumberFormatPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.@@toStringTag.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.@@toStringTag.js new file mode 100644 index 00000000000..61b748b00b2 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.@@toStringTag.js @@ -0,0 +1,3 @@ +test("basic functionality", () => { + expect(Intl.NumberFormat.prototype[Symbol.toStringTag]).toBe("Intl.NumberFormat"); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js new file mode 100644 index 00000000000..8b51e776fff --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js @@ -0,0 +1,5 @@ +describe("normal behavior", () => { + test("length is 0", () => { + expect(Intl.NumberFormat).toHaveLength(0); + }); +});