summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2022-07-12 13:18:23 -0400
committerLinus Groh <mail@linusgroh.de>2022-07-13 19:22:26 +0100
commit33698b961542618e64c6aede5213c3b0624399d0 (patch)
tree3f1d9c6a0bc910fbdf1979e831133596d65c048e /Userland/Libraries/LibJS
parentcd8bcd06c69e51a1782c01ab367eb011253f01df (diff)
downloadserenity-33698b961542618e64c6aede5213c3b0624399d0.zip
LibJS+js: Parse new constructor options from Intl.NumberFormat V3
This contains minimal changes to parse newly added and modified options from the Intl.NumberFormat V3 proposal, while maintaining main spec behavior in Intl.NumberFormat.prototype.format. The parsed options are reflected only in Intl.NumberFormat.prototype.resolvedOptions and the js REPL.
Diffstat (limited to 'Userland/Libraries/LibJS')
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h3
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp33
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h9
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp123
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h69
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp174
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp25
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js122
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js88
10 files changed, 578 insertions, 70 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
index 3f31a1bef4..fa43ef2a49 100644
--- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
+++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h
@@ -402,6 +402,7 @@ namespace JS {
P(round) \
P(roundingIncrement) \
P(roundingMode) \
+ P(roundingPriority) \
P(script) \
P(seal) \
P(second) \
@@ -514,6 +515,7 @@ namespace JS {
P(toZonedDateTime) \
P(toZonedDateTimeISO) \
P(trace) \
+ P(trailingZeroDisplay) \
P(trim) \
P(trimEnd) \
P(trimLeft) \
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
index 0cf24d2edb..4bf8692948 100644
--- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
+++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
@@ -43,6 +43,9 @@
M(IntlInvalidDateTimeFormatOption, "Option {} cannot be set when also providing {}") \
M(IntlInvalidKey, "{} is not a valid key") \
M(IntlInvalidLanguageTag, "{} is not a structurally valid language tag") \
+ M(IntlInvalidRoundingIncrement, "{} is not a valid rounding increment") \
+ M(IntlInvalidRoundingIncrementForFractionDigits, "{} is not a valid rounding increment for inequal min/max fraction digits") \
+ M(IntlInvalidRoundingIncrementForRoundingType, "{} is not a valid rounding increment for rounding type {}") \
M(IntlInvalidTime, "Time value must be between -8.64E15 and 8.64E15") \
M(IntlInvalidUnit, "Unit {} is not a valid time unit") \
M(IntlStartRangeAfterEndRange, "Range start {} is greater than range end {}") \
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp
index 403a774493..b22bf44c7c 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp
@@ -605,6 +605,39 @@ ThrowCompletionOr<Object*> coerce_options_to_object(GlobalObject& global_object,
// NOTE: 9.2.13 GetOption has been removed and is being pulled in from ECMA-262 in the Temporal proposal.
+// 1.2.12 GetStringOrBooleanOption ( options, property, values, trueValue, falsyValue, fallback ), https://tc39.es/proposal-intl-numberformat-v3/out/negotiation/proposed.html#sec-getstringorbooleanoption
+ThrowCompletionOr<StringOrBoolean> get_string_or_boolean_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, Span<StringView const> values, StringOrBoolean true_value, StringOrBoolean falsy_value, StringOrBoolean fallback)
+{
+ // 1. Let value be ? Get(options, property).
+ auto value = TRY(options.get(property));
+
+ // 2. If value is undefined, return fallback.
+ if (value.is_undefined())
+ return fallback;
+
+ // 3. If value is true, return trueValue.
+ if (value.is_boolean() && value.as_bool())
+ return true_value;
+
+ // 4. Let valueBoolean be ToBoolean(value).
+ auto value_boolean = value.to_boolean();
+
+ // 5. If valueBoolean is false, return falsyValue.
+ if (!value_boolean)
+ return falsy_value;
+
+ // 6. Let value be ? ToString(value).
+ auto value_string = TRY(value.to_string(global_object));
+
+ // 7. If values does not contain an element equal to value, return fallback.
+ auto it = find(values.begin(), values.end(), value_string);
+ if (it == values.end())
+ return fallback;
+
+ // 8. Return value.
+ return StringOrBoolean { *it };
+}
+
// 9.2.14 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption
ThrowCompletionOr<Optional<int>> default_number_option(GlobalObject& global_object, Value value, int minimum, int maximum, Optional<int> fallback)
{
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h
index 561d909aef..93122774b2 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h
+++ b/Userland/Libraries/LibJS/Runtime/Intl/AbstractOperations.h
@@ -65,6 +65,8 @@ constexpr auto extra_sanctioned_single_unit_identifiers()
return AK::Array { "microsecond"sv, "nanosecond"sv };
}
+using StringOrBoolean = Variant<StringView, bool>;
+
Optional<Unicode::LocaleID> is_structurally_valid_language_tag(StringView locale);
String canonicalize_unicode_locale_id(Unicode::LocaleID& locale);
bool is_well_formed_currency_code(StringView currency);
@@ -77,10 +79,17 @@ Vector<String> lookup_supported_locales(Vector<String> const& requested_locales)
Vector<String> best_fit_supported_locales(Vector<String> const& requested_locales);
ThrowCompletionOr<Array*> supported_locales(GlobalObject&, Vector<String> const& requested_locales, Value options);
ThrowCompletionOr<Object*> coerce_options_to_object(GlobalObject& global_object, Value options);
+ThrowCompletionOr<StringOrBoolean> get_string_or_boolean_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, Span<StringView const> values, StringOrBoolean true_value, StringOrBoolean falsy_value, StringOrBoolean fallback);
ThrowCompletionOr<Optional<int>> default_number_option(GlobalObject& global_object, Value value, int minimum, int maximum, Optional<int> fallback);
ThrowCompletionOr<Optional<int>> get_number_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, int minimum, int maximum, Optional<int> fallback);
Vector<PatternPartition> partition_pattern(StringView pattern);
+template<size_t Size>
+ThrowCompletionOr<StringOrBoolean> get_string_or_boolean_option(GlobalObject& global_object, Object const& options, PropertyKey const& property, StringView const (&values)[Size], StringOrBoolean true_value, StringOrBoolean falsy_value, StringOrBoolean fallback)
+{
+ return get_string_or_boolean_option(global_object, options, property, Span<StringView const> { values }, move(true_value), move(falsy_value), move(fallback));
+}
+
// NOTE: ECMA-402's GetOption is being removed in favor of a shared ECMA-262 GetOption in the Temporal proposal.
// Until Temporal is merged into ECMA-262, our implementation lives in the Temporal-specific AO file & namespace.
using Temporal::get_option;
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp
index 6863ce6b34..bbf38d3ea3 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp
@@ -161,11 +161,122 @@ StringView NumberFormatBase::rounding_type_string() const
return "fractionDigits"sv;
case RoundingType::CompactRounding:
return "compactRounding"sv;
+ case RoundingType::MorePrecision:
+ return "morePrecision"sv;
+ case RoundingType::LessPrecision:
+ return "lessPrecision"sv;
default:
VERIFY_NOT_REACHED();
}
}
+StringView NumberFormatBase::rounding_mode_string() const
+{
+ switch (m_rounding_mode) {
+ case RoundingMode::Ceil:
+ return "ceil"sv;
+ case RoundingMode::Expand:
+ return "expand"sv;
+ case RoundingMode::Floor:
+ return "floor"sv;
+ case RoundingMode::HalfCeil:
+ return "halfCeil"sv;
+ case RoundingMode::HalfEven:
+ return "halfEven"sv;
+ case RoundingMode::HalfExpand:
+ return "halfExpand"sv;
+ case RoundingMode::HalfFloor:
+ return "halfFloor"sv;
+ case RoundingMode::HalfTrunc:
+ return "halfTrunc"sv;
+ case RoundingMode::Trunc:
+ return "trunc"sv;
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+void NumberFormatBase::set_rounding_mode(StringView rounding_mode)
+{
+ if (rounding_mode == "ceil"sv)
+ m_rounding_mode = RoundingMode::Ceil;
+ else if (rounding_mode == "expand"sv)
+ m_rounding_mode = RoundingMode::Expand;
+ else if (rounding_mode == "floor"sv)
+ m_rounding_mode = RoundingMode::Floor;
+ else if (rounding_mode == "halfCeil"sv)
+ m_rounding_mode = RoundingMode::HalfCeil;
+ else if (rounding_mode == "halfEven"sv)
+ m_rounding_mode = RoundingMode::HalfEven;
+ else if (rounding_mode == "halfExpand"sv)
+ m_rounding_mode = RoundingMode::HalfExpand;
+ else if (rounding_mode == "halfFloor"sv)
+ m_rounding_mode = RoundingMode::HalfFloor;
+ else if (rounding_mode == "halfTrunc"sv)
+ m_rounding_mode = RoundingMode::HalfTrunc;
+ else if (rounding_mode == "trunc"sv)
+ m_rounding_mode = RoundingMode::Trunc;
+}
+
+StringView NumberFormatBase::trailing_zero_display_string() const
+{
+ switch (m_trailing_zero_display) {
+ case TrailingZeroDisplay::Auto:
+ return "auto"sv;
+ case TrailingZeroDisplay::StripIfInteger:
+ return "stripIfInteger"sv;
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+void NumberFormatBase::set_trailing_zero_display(StringView trailing_zero_display)
+{
+ if (trailing_zero_display == "auto"sv)
+ m_trailing_zero_display = TrailingZeroDisplay::Auto;
+ else if (trailing_zero_display == "stripIfInteger"sv)
+ m_trailing_zero_display = TrailingZeroDisplay::StripIfInteger;
+ else
+ VERIFY_NOT_REACHED();
+}
+
+Value NumberFormat::use_grouping_to_value(GlobalObject& global_object) const
+{
+ auto& vm = global_object.vm();
+
+ switch (m_use_grouping) {
+ case UseGrouping::Always:
+ return js_string(vm, "always"sv);
+ case UseGrouping::Auto:
+ return js_string(vm, "auto"sv);
+ case UseGrouping::Min2:
+ return js_string(vm, "min2"sv);
+ case UseGrouping::False:
+ return Value(false);
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+void NumberFormat::set_use_grouping(StringOrBoolean const& use_grouping)
+{
+ use_grouping.visit(
+ [this](StringView grouping) {
+ if (grouping == "always"sv)
+ m_use_grouping = UseGrouping::Always;
+ else if (grouping == "auto"sv)
+ m_use_grouping = UseGrouping::Auto;
+ else if (grouping == "min2"sv)
+ m_use_grouping = UseGrouping::Min2;
+ else
+ VERIFY_NOT_REACHED();
+ },
+ [this](bool grouping) {
+ VERIFY(!grouping);
+ m_use_grouping = UseGrouping::False;
+ });
+}
+
void NumberFormat::set_notation(StringView notation)
{
if (notation == "standard"sv)
@@ -230,6 +341,8 @@ void NumberFormat::set_sign_display(StringView sign_display)
m_sign_display = SignDisplay::Always;
else if (sign_display == "exceptZero"sv)
m_sign_display = SignDisplay::ExceptZero;
+ else if (sign_display == "negative"sv)
+ m_sign_display = SignDisplay::Negative;
else
VERIFY_NOT_REACHED();
}
@@ -245,6 +358,8 @@ StringView NumberFormat::sign_display_string() const
return "always"sv;
case SignDisplay::ExceptZero:
return "exceptZero"sv;
+ case SignDisplay::Negative:
+ return "negative"sv;
default:
VERIFY_NOT_REACHED();
}
@@ -372,6 +487,8 @@ FormatResult format_numeric_to_string(GlobalObject& global_object, NumberFormatB
break;
// 5. Else,
+ case NumberFormatBase::RoundingType::MorePrecision: // FIXME: Handle this case for NumberFormat V3.
+ case NumberFormatBase::RoundingType::LessPrecision: // FIXME: Handle this case for NumberFormat V3.
case NumberFormatBase::RoundingType::CompactRounding:
// a. Assert: intlObject.[[RoundingType]] is compactRounding.
// b. Let result be ToRawPrecision(x, 1, 2).
@@ -662,7 +779,8 @@ Vector<PatternPartition> partition_notation_sub_pattern(GlobalObject& global_obj
// b. Let fraction be undefined.
}
- bool use_grouping = number_format.use_grouping();
+ // FIXME: Handle all NumberFormat V3 [[UseGrouping]] options.
+ bool use_grouping = number_format.use_grouping() != NumberFormat::UseGrouping::False;
// FIXME: The spec doesn't indicate this, but grouping should be disabled for numbers less than 10,000 when the notation is compact.
// This is addressed in Intl.NumberFormat V3 with the "min2" [[UseGrouping]] option. However, test262 explicitly expects this
@@ -1174,7 +1292,8 @@ Optional<Variant<StringView, String>> get_number_format_pattern(GlobalObject& gl
break;
default:
- VERIFY_NOT_REACHED();
+ // FIXME: Handle all NumberFormat V3 [[SignDisplay]] options.
+ return {};
}
found_pattern = patterns.release_value();
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h
index 1f08fe8ae5..56caf70875 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h
@@ -24,7 +24,28 @@ public:
Invalid,
SignificantDigits,
FractionDigits,
- CompactRounding,
+ CompactRounding, // FIXME: Remove this when corresponding AOs are updated for NumberFormat V3.
+ MorePrecision,
+ LessPrecision,
+ };
+
+ enum class RoundingMode {
+ Invalid,
+ Ceil,
+ Expand,
+ Floor,
+ HalfCeil,
+ HalfEven,
+ HalfExpand,
+ HalfFloor,
+ HalfTrunc,
+ Trunc,
+ };
+
+ enum class TrailingZeroDisplay {
+ Invalid,
+ Auto,
+ StripIfInteger,
};
NumberFormatBase(Object& prototype);
@@ -59,15 +80,29 @@ public:
StringView rounding_type_string() const;
void set_rounding_type(RoundingType rounding_type) { m_rounding_type = rounding_type; }
+ RoundingMode rounding_mode() const { return m_rounding_mode; }
+ StringView rounding_mode_string() const;
+ void set_rounding_mode(StringView rounding_mode);
+
+ int rounding_increment() const { return m_rounding_increment; }
+ void set_rounding_increment(int rounding_increment) { m_rounding_increment = rounding_increment; }
+
+ TrailingZeroDisplay trailing_zero_display() const { return m_trailing_zero_display; }
+ StringView trailing_zero_display_string() const;
+ void set_trailing_zero_display(StringView trailing_zero_display);
+
private:
- String m_locale; // [[Locale]]
- String m_data_locale; // [[DataLocale]]
- int m_min_integer_digits { 0 }; // [[MinimumIntegerDigits]]
- Optional<int> m_min_fraction_digits {}; // [[MinimumFractionDigits]]
- Optional<int> m_max_fraction_digits {}; // [[MaximumFractionDigits]]
- Optional<int> m_min_significant_digits {}; // [[MinimumSignificantDigits]]
- Optional<int> m_max_significant_digits {}; // [[MaximumSignificantDigits]]
- RoundingType m_rounding_type { RoundingType::Invalid }; // [[RoundingType]]
+ String m_locale; // [[Locale]]
+ String m_data_locale; // [[DataLocale]]
+ int m_min_integer_digits { 0 }; // [[MinimumIntegerDigits]]
+ Optional<int> m_min_fraction_digits {}; // [[MinimumFractionDigits]]
+ Optional<int> m_max_fraction_digits {}; // [[MaximumFractionDigits]]
+ Optional<int> m_min_significant_digits {}; // [[MinimumSignificantDigits]]
+ Optional<int> m_max_significant_digits {}; // [[MaximumSignificantDigits]]
+ RoundingType m_rounding_type { RoundingType::Invalid }; // [[RoundingType]]
+ RoundingMode m_rounding_mode { RoundingMode::Invalid }; // [[RoundingMode]]
+ int m_rounding_increment { 1 }; // [[RoundingIncrement]]
+ TrailingZeroDisplay m_trailing_zero_display { TrailingZeroDisplay::Invalid }; // [[TrailingZeroDisplay]]
};
class NumberFormat final : public NumberFormatBase {
@@ -120,6 +155,15 @@ public:
Never,
Always,
ExceptZero,
+ Negative,
+ };
+
+ enum class UseGrouping {
+ Invalid,
+ Always,
+ Auto,
+ Min2,
+ False,
};
static constexpr auto relevant_extension_keys()
@@ -163,8 +207,9 @@ public:
StringView unit_display_string() const { return Unicode::style_to_string(*m_unit_display); }
void set_unit_display(StringView unit_display) { m_unit_display = Unicode::style_from_string(unit_display); }
- bool use_grouping() const { return m_use_grouping; }
- void set_use_grouping(bool use_grouping) { m_use_grouping = use_grouping; }
+ UseGrouping use_grouping() const { return m_use_grouping; }
+ Value use_grouping_to_value(GlobalObject&) const;
+ void set_use_grouping(StringOrBoolean const& use_grouping);
Notation notation() const { return m_notation; }
StringView notation_string() const;
@@ -198,7 +243,7 @@ private:
Optional<CurrencySign> m_currency_sign {}; // [[CurrencySign]]
Optional<String> m_unit {}; // [[Unit]]
Optional<Unicode::Style> m_unit_display {}; // [[UnitDisplay]]
- bool m_use_grouping { false }; // [[UseGrouping]]
+ UseGrouping m_use_grouping { false }; // [[UseGrouping]]
Notation m_notation { Notation::Invalid }; // [[Notation]]
Optional<CompactDisplay> 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
index 58a9a3cfa2..bc7fef857f 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp
@@ -80,6 +80,7 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatConstructor::supported_locales_of)
}
// 15.1.2 InitializeNumberFormat ( numberFormat, locales, options ), https://tc39.es/ecma402/#sec-initializenumberformat
+// 1.1.2 InitializeNumberFormat ( numberFormat, locales, options ), https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/proposed.html#sec-initializenumberformat
ThrowCompletionOr<NumberFormat*> initialize_number_format(GlobalObject& global_object, NumberFormat& number_format, Value locales_value, Value options_value)
{
auto& vm = global_object.vm();
@@ -170,32 +171,71 @@ ThrowCompletionOr<NumberFormat*> initialize_number_format(GlobalObject& global_o
// 20. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
TRY(set_number_format_digit_options(global_object, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format.notation()));
- // 21. Let compactDisplay be ? GetOption(options, "compactDisplay", "string", « "short", "long" », "short").
+ // 21. Let roundingIncrement be ? GetNumberOption(options, "roundingIncrement", 1, 5000, 1).
+ auto rounding_increment = TRY(get_number_option(global_object, *options, vm.names.roundingIncrement, 1, 5000, 1));
+
+ // 22. If roundingIncrement is not in « 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 », throw a RangeError exception.
+ static constexpr auto sanctioned_rounding_increments = AK::Array { 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 };
+
+ if (!sanctioned_rounding_increments.span().contains_slow(*rounding_increment))
+ return vm.throw_completion<RangeError>(global_object, ErrorType::IntlInvalidRoundingIncrement, *rounding_increment);
+
+ // 23. If roundingIncrement is not 1 and numberFormat.[[RoundingType]] is not fractionDigits, throw a TypeError exception.
+ if ((rounding_increment != 1) && (number_format.rounding_type() != NumberFormatBase::RoundingType::FractionDigits))
+ return vm.throw_completion<TypeError>(global_object, ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, number_format.rounding_type_string());
+
+ // 24. If roundingIncrement is not 1 and numberFormat.[[MaximumFractionDigits]] is not equal to numberFormat.[[MinimumFractionDigits]], throw a RangeError exception.
+ if ((rounding_increment != 1) && (number_format.max_fraction_digits() != number_format.min_fraction_digits()))
+ return vm.throw_completion<RangeError>(global_object, ErrorType::IntlInvalidRoundingIncrementForFractionDigits, *rounding_increment);
+
+ // 25. Set numberFormat.[[RoundingIncrement]] to roundingIncrement.
+ number_format.set_rounding_increment(*rounding_increment);
+
+ // 26. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", "string", « "auto", "stripIfInteger" », "auto").
+ auto trailing_zero_display = TRY(get_option(global_object, *options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv));
+
+ // 27. Set numberFormat.[[TrailingZeroDisplay]] to trailingZeroDisplay.
+ number_format.set_trailing_zero_display(trailing_zero_display.as_string().string());
+
+ // 28. Let compactDisplay be ? GetOption(options, "compactDisplay", "string", « "short", "long" », "short").
auto compact_display = TRY(get_option(global_object, *options, vm.names.compactDisplay, OptionType::String, { "short"sv, "long"sv }, "short"sv));
- // 22. If notation is "compact", then
+ // 29. Let defaultUseGrouping be "auto".
+ auto default_use_grouping = "auto"sv;
+
+ // 30. If notation is "compact", then
if (number_format.notation() == NumberFormat::Notation::Compact) {
// a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
number_format.set_compact_display(compact_display.as_string().string());
+
+ // b. Set defaultUseGrouping to "min2".
+ default_use_grouping = "min2"sv;
}
- // 23. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
- auto use_grouping = TRY(get_option(global_object, *options, vm.names.useGrouping, OptionType::Boolean, {}, true));
+ // 31. Let useGrouping be ? GetStringOrBooleanOption(options, "useGrouping", « "min2", "auto", "always" », "always", false, defaultUseGrouping).
+ auto use_grouping = TRY(get_string_or_boolean_option(global_object, *options, vm.names.useGrouping, { "min2"sv, "auto"sv, "always"sv }, "always"sv, false, default_use_grouping));
- // 24. Set numberFormat.[[UseGrouping]] to useGrouping.
- number_format.set_use_grouping(use_grouping.as_bool());
+ // 32. Set numberFormat.[[UseGrouping]] to useGrouping.
+ number_format.set_use_grouping(use_grouping);
- // 25. Let signDisplay be ? GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
- auto sign_display = TRY(get_option(global_object, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv }, "auto"sv));
+ // 33. Let signDisplay be ? GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero, "negative" », "auto").
+ auto sign_display = TRY(get_option(global_object, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv, "negative"sv }, "auto"sv));
- // 26. Set numberFormat.[[SignDisplay]] to signDisplay.
+ // 34. Set numberFormat.[[SignDisplay]] to signDisplay.
number_format.set_sign_display(sign_display.as_string().string());
- // 27. Return numberFormat.
+ // 35. Let roundingMode be ? GetOption(options, "roundingMode", "string", « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
+ auto rounding_mode = TRY(get_option(global_object, *options, vm.names.roundingMode, OptionType::String, { "ceil"sv, "floor"sv, "expand"sv, "trunc"sv, "halfCeil"sv, "halfFloor"sv, "halfExpand"sv, "halfTrunc"sv, "halfEven"sv }, "halfExpand"sv));
+
+ // 36. Set numberFormat.[[RoundingMode]] to roundingMode.
+ number_format.set_rounding_mode(rounding_mode.as_string().string());
+
+ // 37. Return numberFormat.
return &number_format;
}
// 15.1.3 SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault, notation ), https://tc39.es/ecma402/#sec-setnfdigitoptions
+// 1.1.1 SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault, notation ), https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/proposed.html#sec-setnfdigitoptions
ThrowCompletionOr<void> set_number_format_digit_options(GlobalObject& global_object, NumberFormatBase& intl_object, Object const& options, int default_min_fraction_digits, int default_max_fraction_digits, NumberFormat::Notation notation)
{
auto& vm = global_object.vm();
@@ -218,46 +258,66 @@ ThrowCompletionOr<void> set_number_format_digit_options(GlobalObject& global_obj
// 6. Set intlObj.[[MinimumIntegerDigits]] to mnid.
intl_object.set_min_integer_digits(*min_integer_digits);
- // 7. If mnsd is not undefined or mxsd is not undefined, then
+ // 7. Let roundingPriority be ? GetOption(options, "roundingPriority", "string", « "auto", "morePrecision", "lessPrecision" », "auto").
+ auto rounding_priority = TRY(get_option(global_object, options, vm.names.roundingPriority, OptionType::String, { "auto"sv, "morePrecision"sv, "lessPrecision"sv }, "auto"sv));
+
+ // 8. If mnsd is not undefined or mxsd is not undefined, then
// a. Let hasSd be true.
- // 8. Else,
+ // 9. Else,
// a. Let hasSd be false.
bool has_significant_digits = !min_significant_digits.is_undefined() || !max_significant_digits.is_undefined();
- // 9. If mnfd is not undefined or mxfd is not undefined, then
+ // 10. If mnfd is not undefined or mxfd is not undefined, then
// a. Let hasFd be true.
- // 10. Else,
+ // 11. Else,
// a. Let hasFd be false.
bool has_fraction_digits = !min_fraction_digits.is_undefined() || !max_fraction_digits.is_undefined();
- // 11. Let needSd be hasSd.
- bool need_significant_digits = has_significant_digits;
+ // 12. Let needSd be true.
+ bool need_significant_digits = true;
+
+ // 13. Let needFd be true.
+ bool need_fraction_digits = true;
- // 12. If hasSd is true, or hasFd is false and notation is "compact", then
- // a. Let needFd be false.
- // 13. Else,
- // a. Let needFd be true.
- bool need_fraction_digits = !has_significant_digits && (has_fraction_digits || (notation != NumberFormat::Notation::Compact));
+ // 14. If roundingPriority is "auto", then
+ if (rounding_priority.as_string().string() == "auto"sv) {
+ // a. Set needSd to hasSd.
+ need_significant_digits = has_significant_digits;
+
+ // b. If hasSd is true, or hasFd is false and notation is "compact", then
+ if (has_significant_digits || (!has_fraction_digits && notation == NumberFormat::Notation::Compact)) {
+ // i. Set needFd to false.
+ need_fraction_digits = false;
+ }
+ }
- // 14. If needSd is true, then
+ // 15. If needSd is true, then
if (need_significant_digits) {
- // a. Assert: hasSd is true.
- VERIFY(has_significant_digits);
+ // a. If hasSd is true, then
+ if (has_significant_digits) {
+ // i. Set mnsd to ? DefaultNumberOption(mnsd, 1, 21, 1).
+ auto min_digits = TRY(default_number_option(global_object, min_significant_digits, 1, 21, 1));
- // b. Set mnsd to ? DefaultNumberOption(mnsd, 1, 21, 1).
- auto min_digits = TRY(default_number_option(global_object, min_significant_digits, 1, 21, 1));
+ // ii. Set mxsd to ? DefaultNumberOption(mxsd, mnsd, 21, 21).
+ auto max_digits = TRY(default_number_option(global_object, max_significant_digits, *min_digits, 21, 21));
- // c. Set mxsd to ? DefaultNumberOption(mxsd, mnsd, 21, 21).
- auto max_digits = TRY(default_number_option(global_object, max_significant_digits, *min_digits, 21, 21));
+ // iii. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
+ intl_object.set_min_significant_digits(*min_digits);
- // d. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
- intl_object.set_min_significant_digits(*min_digits);
+ // iv. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
+ intl_object.set_max_significant_digits(*max_digits);
+ }
+ // b. Else,
+ else {
+ // i. Set intlObj.[[MinimumSignificantDigits]] to 1.
+ intl_object.set_min_significant_digits(1);
- // e. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
- intl_object.set_max_significant_digits(*max_digits);
+ // ii. Set intlObj.[[MaximumSignificantDigits]] to 21.
+ intl_object.set_max_significant_digits(21);
+ }
}
- // 15. If needFd is true, then
+ // 16. If needFd is true, then
if (need_fraction_digits) {
// a. If hasFd is true, then
if (has_fraction_digits) {
@@ -293,20 +353,46 @@ ThrowCompletionOr<void> set_number_format_digit_options(GlobalObject& global_obj
}
}
- // 16. If needSd is false and needFd is false, then
- if (!need_significant_digits && !need_fraction_digits) {
- // a. Set intlObj.[[RoundingType]] to compactRounding.
- intl_object.set_rounding_type(NumberFormatBase::RoundingType::CompactRounding);
- }
- // 17. Else if hasSd is true, then
- else if (has_significant_digits) {
- // a. Set intlObj.[[RoundingType]] to significantDigits.
- intl_object.set_rounding_type(NumberFormatBase::RoundingType::SignificantDigits);
+ // 17. If needSd is true or needFd is true, then
+ if (need_significant_digits || need_fraction_digits) {
+ // a. If roundingPriority is "morePrecision", then
+ if (rounding_priority.as_string().string() == "morePrecision"sv) {
+ // i. Set intlObj.[[RoundingType]] to morePrecision.
+ intl_object.set_rounding_type(NumberFormatBase::RoundingType::MorePrecision);
+ }
+ // b. Else if roundingPriority is "lessPrecision", then
+ else if (rounding_priority.as_string().string() == "lessPrecision"sv) {
+ // i. Set intlObj.[[RoundingType]] to lessPrecision.
+ intl_object.set_rounding_type(NumberFormatBase::RoundingType::LessPrecision);
+ }
+ // c. Else if hasSd is true, then
+ else if (has_significant_digits) {
+ // i. Set intlObj.[[RoundingType]] to significantDigits.
+ intl_object.set_rounding_type(NumberFormatBase::RoundingType::SignificantDigits);
+ }
+ // d. Else,
+ else {
+ // i. Set intlObj.[[RoundingType]] to fractionDigits.
+ intl_object.set_rounding_type(NumberFormatBase::RoundingType::FractionDigits);
+ }
}
+
// 18. Else,
else {
- // a. Set intlObj.[[RoundingType]] to fractionDigits.
- intl_object.set_rounding_type(NumberFormatBase::RoundingType::FractionDigits);
+ // a. Set intlObj.[[RoundingType]] to morePrecision.
+ intl_object.set_rounding_type(NumberFormatBase::RoundingType::MorePrecision);
+
+ // b. Set intlObj.[[MinimumFractionDigits]] to 0.
+ intl_object.set_min_fraction_digits(0);
+
+ // c. Set intlObj.[[MaximumFractionDigits]] to 0.
+ intl_object.set_max_fraction_digits(0);
+
+ // d. Set intlObj.[[MinimumSignificantDigits]] to 1.
+ intl_object.set_min_significant_digits(1);
+
+ // e. Set intlObj.[[MaximumSignificantDigits]] to 2.
+ intl_object.set_max_significant_digits(2);
}
return {};
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp
index 9f9d4771aa..907b384fdd 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp
@@ -114,13 +114,34 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatPrototype::resolved_options)
MUST(options->create_data_property_or_throw(vm.names.minimumSignificantDigits, Value(number_format->min_significant_digits())));
if (number_format->has_max_significant_digits())
MUST(options->create_data_property_or_throw(vm.names.maximumSignificantDigits, Value(number_format->max_significant_digits())));
- MUST(options->create_data_property_or_throw(vm.names.useGrouping, Value(number_format->use_grouping())));
+ MUST(options->create_data_property_or_throw(vm.names.useGrouping, number_format->use_grouping_to_value(global_object)));
MUST(options->create_data_property_or_throw(vm.names.notation, js_string(vm, number_format->notation_string())));
if (number_format->has_compact_display())
MUST(options->create_data_property_or_throw(vm.names.compactDisplay, js_string(vm, number_format->compact_display_string())));
MUST(options->create_data_property_or_throw(vm.names.signDisplay, js_string(vm, number_format->sign_display_string())));
+ MUST(options->create_data_property_or_throw(vm.names.roundingMode, js_string(vm, number_format->rounding_mode_string())));
+ MUST(options->create_data_property_or_throw(vm.names.roundingIncrement, Value(number_format->rounding_increment())));
+ MUST(options->create_data_property_or_throw(vm.names.trailingZeroDisplay, js_string(vm, number_format->trailing_zero_display_string())));
+
+ switch (number_format->rounding_type()) {
+ // 6. If nf.[[RoundingType]] is morePrecision, then
+ case NumberFormatBase::RoundingType::MorePrecision:
+ // a. Perform ! CreateDataPropertyOrThrow(options, "roundingPriority", "morePrecision").
+ MUST(options->create_data_property_or_throw(vm.names.roundingPriority, js_string(vm, "morePrecision"sv)));
+ break;
+ // 7. Else if nf.[[RoundingType]] is lessPrecision, then
+ case NumberFormatBase::RoundingType::LessPrecision:
+ // a. Perform ! CreateDataPropertyOrThrow(options, "roundingPriority", "lessPrecision").
+ MUST(options->create_data_property_or_throw(vm.names.roundingPriority, js_string(vm, "lessPrecision"sv)));
+ break;
+ // 8. Else,
+ default:
+ // a. Perform ! CreateDataPropertyOrThrow(options, "roundingPriority", "auto").
+ MUST(options->create_data_property_or_throw(vm.names.roundingPriority, js_string(vm, "auto"sv)));
+ break;
+ }
- // 5. Return options.
+ // 9. Return options.
return options;
}
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js
index d56577db7e..46a6509175 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.js
@@ -208,6 +208,70 @@ describe("errors", () => {
new Intl.NumberFormat("en", { signDisplay: "hello!" });
}).toThrowWithMessage(RangeError, "hello! is not a valid value for option signDisplay");
});
+
+ test("roundingPriority option is invalid", () => {
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingPriority: "hello!" });
+ }).toThrowWithMessage(
+ RangeError,
+ "hello! is not a valid value for option roundingPriority"
+ );
+ });
+
+ test("roundingMode option is invalid", () => {
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingMode: "hello!" });
+ }).toThrowWithMessage(RangeError, "hello! is not a valid value for option roundingMode");
+ });
+
+ test("roundingIncrement option is invalid", () => {
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingIncrement: "hello!" });
+ }).toThrowWithMessage(RangeError, "Value NaN is NaN or is not between 1 and 5000");
+
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingIncrement: 0 });
+ }).toThrowWithMessage(RangeError, "Value 0 is NaN or is not between 1 and 5000");
+
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingIncrement: 5001 });
+ }).toThrowWithMessage(RangeError, "Value 5001 is NaN or is not between 1 and 5000");
+
+ expect(() => {
+ new Intl.NumberFormat("en", {
+ roundingIncrement: 3,
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+ }).toThrowWithMessage(RangeError, "3 is not a valid rounding increment");
+
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingIncrement: 5, minimumSignificantDigits: 1 });
+ }).toThrowWithMessage(
+ TypeError,
+ "5 is not a valid rounding increment for rounding type significantDigits"
+ );
+
+ expect(() => {
+ new Intl.NumberFormat("en", {
+ roundingIncrement: 5,
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 3,
+ });
+ }).toThrowWithMessage(
+ RangeError,
+ "5 is not a valid rounding increment for inequal min/max fraction digits"
+ );
+ });
+
+ test("trailingZeroDisplay option is invalid", () => {
+ expect(() => {
+ new Intl.NumberFormat("en", { trailingZeroDisplay: "hello!" });
+ }).toThrowWithMessage(
+ RangeError,
+ "hello! is not a valid value for option trailingZeroDisplay"
+ );
+ });
});
describe("normal behavior", () => {
@@ -344,10 +408,66 @@ describe("normal behavior", () => {
});
test("all valid signDisplay options", () => {
- ["auto", "never", "always", "exceptZero"].forEach(signDisplay => {
+ ["auto", "never", "always", "exceptZero", "negative"].forEach(signDisplay => {
expect(() => {
new Intl.NumberFormat("en", { signDisplay: signDisplay });
}).not.toThrow();
});
});
+
+ test("valid useGrouping options", () => {
+ ["min2", "auto", "always", false, true, ""].forEach(useGrouping => {
+ expect(() => {
+ new Intl.NumberFormat("en", { useGrouping: useGrouping });
+ }).not.toThrow();
+ });
+ });
+
+ test("all valid roundingPriority options", () => {
+ ["auto", "morePrecision", "lessPrecision"].forEach(roundingPriority => {
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingPriority: roundingPriority });
+ }).not.toThrow();
+ });
+ });
+
+ test("all valid roundingMode options", () => {
+ [
+ "ceil",
+ "floor",
+ "expand",
+ "trunc",
+ "halfCeil",
+ "halfFloor",
+ "halfExpand",
+ "halfTrunc",
+ "halfEven",
+ ].forEach(roundingMode => {
+ expect(() => {
+ new Intl.NumberFormat("en", { roundingMode: roundingMode });
+ }).not.toThrow();
+ });
+ });
+
+ test("all valid roundingIncrement options", () => {
+ [1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000].forEach(
+ roundingIncrement => {
+ expect(() => {
+ new Intl.NumberFormat("en", {
+ roundingIncrement: roundingIncrement,
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+ }).not.toThrow();
+ }
+ );
+ });
+
+ test("all valid trailingZeroDisplay options", () => {
+ ["auto", "stripIfInteger"].forEach(trailingZeroDisplay => {
+ expect(() => {
+ new Intl.NumberFormat("en", { trailingZeroDisplay: trailingZeroDisplay });
+ }).not.toThrow();
+ });
+ });
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js
index 70ebdde2df..8dafec921c 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.resolvedOptions.js
@@ -176,12 +176,12 @@ describe("correct behavior", () => {
});
});
- test("compact notation causes all min/max digits to be undefined by default", () => {
+ test("compact notation causes all min/max digits to be set to default values", () => {
const en = new Intl.NumberFormat("en", { notation: "compact" });
- expect(en.resolvedOptions().minimumFractionDigits).toBeUndefined();
- expect(en.resolvedOptions().maximumFractionDigits).toBeUndefined();
- expect(en.resolvedOptions().minimumSignificantDigits).toBeUndefined();
- expect(en.resolvedOptions().maximumSignificantDigits).toBeUndefined();
+ expect(en.resolvedOptions().minimumFractionDigits).toBe(0);
+ expect(en.resolvedOptions().maximumFractionDigits).toBe(0);
+ expect(en.resolvedOptions().minimumSignificantDigits).toBe(1);
+ expect(en.resolvedOptions().maximumSignificantDigits).toBe(2);
});
test("currency display and sign only defined when style is currency", () => {
@@ -276,19 +276,89 @@ describe("correct behavior", () => {
test("use grouping", () => {
const en1 = new Intl.NumberFormat("en");
- expect(en1.resolvedOptions().useGrouping).toBeTrue();
+ expect(en1.resolvedOptions().useGrouping).toBe("auto");
- const en2 = new Intl.NumberFormat("en", { useGrouping: false });
- expect(en2.resolvedOptions().useGrouping).toBeFalse();
+ const en2 = new Intl.NumberFormat("en", { notation: "compact" });
+ expect(en2.resolvedOptions().useGrouping).toBe("min2");
+
+ const en3 = new Intl.NumberFormat("en", { useGrouping: false });
+ expect(en3.resolvedOptions().useGrouping).toBeFalse();
+
+ const en4 = new Intl.NumberFormat("en", { useGrouping: true });
+ expect(en4.resolvedOptions().useGrouping).toBe("always");
+
+ ["auto", "always", "min2"].forEach(useGrouping => {
+ const en5 = new Intl.NumberFormat("en", { useGrouping: useGrouping });
+ expect(en5.resolvedOptions().useGrouping).toBe(useGrouping);
+ });
});
test("sign display", () => {
const en1 = new Intl.NumberFormat("en");
expect(en1.resolvedOptions().signDisplay).toBe("auto");
- ["auto", "never", "always", "exceptZero"].forEach(signDisplay => {
+ ["auto", "never", "always", "exceptZero", "negative"].forEach(signDisplay => {
const en2 = new Intl.NumberFormat("en", { signDisplay: signDisplay });
expect(en2.resolvedOptions().signDisplay).toBe(signDisplay);
});
});
+
+ test("rounding priority", () => {
+ const en1 = new Intl.NumberFormat("en");
+ expect(en1.resolvedOptions().roundingPriority).toBe("auto");
+
+ const en2 = new Intl.NumberFormat("en", { notation: "compact" });
+ expect(en2.resolvedOptions().roundingPriority).toBe("morePrecision");
+
+ ["auto", "morePrecision", "lessPrecision"].forEach(roundingPriority => {
+ const en3 = new Intl.NumberFormat("en", { roundingPriority: roundingPriority });
+ expect(en3.resolvedOptions().roundingPriority).toBe(roundingPriority);
+ });
+ });
+
+ test("rounding mode", () => {
+ const en1 = new Intl.NumberFormat("en");
+ expect(en1.resolvedOptions().roundingMode).toBe("halfExpand");
+
+ [
+ "ceil",
+ "floor",
+ "expand",
+ "trunc",
+ "halfCeil",
+ "halfFloor",
+ "halfExpand",
+ "halfTrunc",
+ "halfEven",
+ ].forEach(roundingMode => {
+ const en2 = new Intl.NumberFormat("en", { roundingMode: roundingMode });
+ expect(en2.resolvedOptions().roundingMode).toBe(roundingMode);
+ });
+ });
+
+ test("rounding increment", () => {
+ const en1 = new Intl.NumberFormat("en");
+ expect(en1.resolvedOptions().roundingIncrement).toBe(1);
+
+ [1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000].forEach(
+ roundingIncrement => {
+ const en2 = new Intl.NumberFormat("en", {
+ roundingIncrement: roundingIncrement,
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+ expect(en2.resolvedOptions().roundingIncrement).toBe(roundingIncrement);
+ }
+ );
+ });
+
+ test("trailing zero display", () => {
+ const en1 = new Intl.NumberFormat("en");
+ expect(en1.resolvedOptions().trailingZeroDisplay).toBe("auto");
+
+ ["auto", "stripIfInteger"].forEach(trailingZeroDisplay => {
+ const en2 = new Intl.NumberFormat("en", { trailingZeroDisplay: trailingZeroDisplay });
+ expect(en2.resolvedOptions().trailingZeroDisplay).toBe(trailingZeroDisplay);
+ });
+ });
});