From 5b3b14be0a0f83db7c4942a7d932176f7135b8c8 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 30 Jan 2023 09:44:00 -0500 Subject: [PATCH] LibJS: Move resolution of some Intl.NumberFormat options to a common AO This is a normative change in the Intl.NumberFormat V3 spec. See: https://github.com/tc39/proposal-intl-numberformat-v3/commit/29acfc6 This is to allow Intl.PluralRules to use these options, as they were in- effect required by later AOs anyways. --- .../Runtime/Intl/NumberFormatConstructor.cpp | 126 +++++++++--------- .../Runtime/Intl/PluralRulesConstructor.cpp | 2 +- .../Runtime/Intl/PluralRulesPrototype.cpp | 3 + .../builtins/Intl/PluralRules/PluralRules.js | 87 ++++++++++++ .../PluralRules.prototype.resolvedOptions.js | 42 ++++++ 5 files changed, 197 insertions(+), 63 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp index dabc866e61a..2e5943afd00 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatConstructor.cpp @@ -51,7 +51,7 @@ ThrowCompletionOr> NumberFormatConstructor::construct(Funct auto locales = vm.argument(0); auto options = vm.argument(1); - // 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]] »). + // 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]], [[RoundingMode]], [[RoundingIncrement]], [[TrailingZeroDisplay]], [[BoundFormat]] »). auto number_format = TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::intl_number_format_prototype)); // 3. Perform ? InitializeNumberFormat(numberFormat, locales, options). @@ -161,55 +161,22 @@ ThrowCompletionOr initialize_number_format(VM& vm, NumberFormat& default_max_fraction_digits = style == NumberFormat::Style::Percent ? 0 : 3; } - // 18. Let roundingIncrement be ? GetNumberOption(options, "roundingIncrement", 1, 5000, 1). - auto rounding_increment = TRY(get_number_option(vm, *options, vm.names.roundingIncrement, 1, 5000, 1)); - - // 19. 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(ErrorType::IntlInvalidRoundingIncrement, *rounding_increment); - - // 20. If roundingIncrement is not 1, set mxfdDefault to mnfdDefault. - if (rounding_increment != 1) - default_max_fraction_digits = default_min_fraction_digits; - - // 21. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard"). + // 18. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard"). auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv)); - // 22. Set numberFormat.[[Notation]] to notation. + // 19. Set numberFormat.[[Notation]] to notation. number_format.set_notation(TRY(notation.as_string().utf8_string_view())); - // 23. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation). + // 20. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation). TRY(set_number_format_digit_options(vm, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format.notation())); - // 24. If roundingIncrement is not 1, then - if (rounding_increment != 1) { - // a. If numberFormat.[[RoundingType]] is not fractionDigits, throw a TypeError exception. - if (number_format.rounding_type() != NumberFormatBase::RoundingType::FractionDigits) - return vm.throw_completion(ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, number_format.rounding_type_string()); - - // b. If numberFormat.[[MaximumFractionDigits]] is not equal to numberFormat.[[MinimumFractionDigits]], throw a RangeError exception. - if (number_format.max_fraction_digits() != number_format.min_fraction_digits()) - return vm.throw_completion(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(vm, *options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv)); - - // 27. Set numberFormat.[[TrailingZeroDisplay]] to trailingZeroDisplay. - number_format.set_trailing_zero_display(TRY(trailing_zero_display.as_string().utf8_string_view())); - - // 28. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short"). + // 21. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short"). auto compact_display = TRY(get_option(vm, *options, vm.names.compactDisplay, OptionType::String, { "short"sv, "long"sv }, "short"sv)); - // 29. Let defaultUseGrouping be "auto". + // 22. Let defaultUseGrouping be "auto". auto default_use_grouping = "auto"sv; - // 30. If notation is "compact", then + // 23. If notation is "compact", then if (number_format.notation() == NumberFormat::Notation::Compact) { // a. Set numberFormat.[[CompactDisplay]] to compactDisplay. number_format.set_compact_display(TRY(compact_display.as_string().utf8_string_view())); @@ -218,25 +185,19 @@ ThrowCompletionOr initialize_number_format(VM& vm, NumberFormat& default_use_grouping = "min2"sv; } - // 31. Let useGrouping be ? GetStringOrBooleanOption(options, "useGrouping", « "min2", "auto", "always" », "always", false, defaultUseGrouping). + // 24. Let useGrouping be ? GetStringOrBooleanOption(options, "useGrouping", « "min2", "auto", "always" », "always", false, defaultUseGrouping). auto use_grouping = TRY(get_string_or_boolean_option(vm, *options, vm.names.useGrouping, { "min2"sv, "auto"sv, "always"sv }, "always"sv, false, default_use_grouping)); - // 32. Set numberFormat.[[UseGrouping]] to useGrouping. + // 25. Set numberFormat.[[UseGrouping]] to useGrouping. number_format.set_use_grouping(use_grouping); - // 33. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero, "negative" », "auto"). + // 26. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero, "negative" », "auto"). auto sign_display = TRY(get_option(vm, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv, "negative"sv }, "auto"sv)); - // 34. Set numberFormat.[[SignDisplay]] to signDisplay. + // 27. Set numberFormat.[[SignDisplay]] to signDisplay. number_format.set_sign_display(TRY(sign_display.as_string().utf8_string_view())); - // 35. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand"). - auto rounding_mode = TRY(get_option(vm, *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(TRY(rounding_mode.as_string().utf8_string_view())); - - // 37. Return numberFormat. + // 28. Return numberFormat. return &number_format; } @@ -265,25 +226,55 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase // 7. Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto"). auto rounding_priority = TRY(get_option(vm, 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 + // 8. Let roundingIncrement be ? GetNumberOption(options, "roundingIncrement", 1, 5000, 1). + auto rounding_increment = TRY(get_number_option(vm, options, vm.names.roundingIncrement, 1, 5000, 1)); + + // 9. 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(ErrorType::IntlInvalidRoundingIncrement, *rounding_increment); + + // 10. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand"). + auto rounding_mode = TRY(get_option(vm, 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)); + + // 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto"). + auto trailing_zero_display = TRY(get_option(vm, options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv)); + + // 12. NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions. + + // 13. If roundingIncrement is not 1, set mxfdDefault to mnfdDefault. + if (rounding_increment != 1) + default_max_fraction_digits = default_min_fraction_digits; + + // 14. Set intlObj.[[RoundingIncrement]] to roundingIncrement. + intl_object.set_rounding_increment(*rounding_increment); + + // 15. Set intlObj.[[RoundingMode]] to roundingMode. + intl_object.set_rounding_mode(TRY(rounding_mode.as_string().utf8_string_view())); + + // 16. Set intlObj.[[TrailingZeroDisplay]] to trailingZeroDisplay. + intl_object.set_trailing_zero_display(TRY(trailing_zero_display.as_string().utf8_string_view())); + + // 17. If mnsd is not undefined or mxsd is not undefined, then // a. Let hasSd be true. - // 9. Else, + // 18. Else, // a. Let hasSd be false. bool has_significant_digits = !min_significant_digits.is_undefined() || !max_significant_digits.is_undefined(); - // 10. If mnfd is not undefined or mxfd is not undefined, then + // 19. If mnfd is not undefined or mxfd is not undefined, then // a. Let hasFd be true. - // 11. Else, + // 20. Else, // a. Let hasFd be false. bool has_fraction_digits = !min_fraction_digits.is_undefined() || !max_fraction_digits.is_undefined(); - // 12. Let needSd be true. + // 21. Let needSd be true. bool need_significant_digits = true; - // 13. Let needFd be true. + // 22. Let needFd be true. bool need_fraction_digits = true; - // 14. If roundingPriority is "auto", then + // 23. If roundingPriority is "auto", then if (TRY(rounding_priority.as_string().utf8_string_view()) == "auto"sv) { // a. Set needSd to hasSd. need_significant_digits = has_significant_digits; @@ -295,7 +286,7 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase } } - // 15. If needSd is true, then + // 24. If needSd is true, then if (need_significant_digits) { // a. If hasSd is true, then if (has_significant_digits) { @@ -321,7 +312,7 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase } } - // 16. If needFd is true, then + // 25. If needFd is true, then if (need_fraction_digits) { // a. If hasFd is true, then if (has_fraction_digits) { @@ -357,7 +348,7 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase } } - // 17. If needSd is true or needFd is true, then + // 26. If needSd is true or needFd is true, then if (need_significant_digits || need_fraction_digits) { auto rounding_priority_string = TRY(rounding_priority.as_string().utf8_string_view()); @@ -383,7 +374,7 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase } } - // 18. Else, + // 27. Else, else { // a. Set intlObj.[[RoundingType]] to morePrecision. intl_object.set_rounding_type(NumberFormatBase::RoundingType::MorePrecision); @@ -401,6 +392,17 @@ ThrowCompletionOr set_number_format_digit_options(VM& vm, NumberFormatBase intl_object.set_max_significant_digits(2); } + // 28. If roundingIncrement is not 1, then + if (rounding_increment != 1) { + // a. If intlObj.[[RoundingType]] is not fractionDigits, throw a TypeError exception. + if (intl_object.rounding_type() != NumberFormatBase::RoundingType::FractionDigits) + return vm.throw_completion(ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, intl_object.rounding_type_string()); + + // b. If intlObj.[[MaximumFractionDigits]] is not equal to intlObj.[[MinimumFractionDigits]], throw a RangeError exception. + if (intl_object.max_fraction_digits() != intl_object.min_fraction_digits()) + return vm.throw_completion(ErrorType::IntlInvalidRoundingIncrementForFractionDigits, *rounding_increment); + } + return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp index 7836a45ffad..fd99f4bdfe2 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesConstructor.cpp @@ -52,7 +52,7 @@ ThrowCompletionOr> PluralRulesConstructor::construct(Functi auto locales = vm.argument(0); auto options = vm.argument(1); - // 2. Let pluralRules be ? OrdinaryCreateFromConstructor(NewTarget, "%PluralRules.prototype%", « [[InitializedPluralRules]], [[Locale]], [[Type]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]] »). + // 2. Let pluralRules be ? OrdinaryCreateFromConstructor(NewTarget, "%PluralRules.prototype%", « [[InitializedPluralRules]], [[Locale]], [[Type]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[RoundingMode]], [[RoundingIncrement]], [[TrailingZeroDisplay]] »). auto plural_rules = TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::intl_plural_rules_prototype)); // 3. Return ? InitializePluralRules(pluralRules, locales, options). diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp index 4fcd02e2701..317f0ecdb53 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp @@ -106,6 +106,9 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options) MUST(options->create_data_property_or_throw(vm.names.minimumSignificantDigits, Value(plural_rules->min_significant_digits()))); if (plural_rules->has_max_significant_digits()) MUST(options->create_data_property_or_throw(vm.names.maximumSignificantDigits, Value(plural_rules->max_significant_digits()))); + MUST(options->create_data_property_or_throw(vm.names.roundingMode, PrimitiveString::create(vm, plural_rules->rounding_mode_string()))); + MUST(options->create_data_property_or_throw(vm.names.roundingIncrement, Value(plural_rules->rounding_increment()))); + MUST(options->create_data_property_or_throw(vm.names.trailingZeroDisplay, PrimitiveString::create(vm, plural_rules->trailing_zero_display_string()))); // 5. Let pluralCategories be a List of Strings containing all possible results of PluralRuleSelect for the selected locale pr.[[Locale]]. auto available_categories = ::Locale::available_plural_categories(plural_rules->locale(), plural_rules->type()); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js index 8c983d514af..a74537dec0d 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.js @@ -125,6 +125,57 @@ describe("errors", () => { "hello! is not a valid value for option roundingPriority" ); }); + + test("roundingMode option is invalid", () => { + expect(() => { + new Intl.PluralRules("en", { roundingMode: "hello!" }); + }).toThrowWithMessage(RangeError, "hello! is not a valid value for option roundingMode"); + }); + + test("roundingIncrement option is invalid", () => { + expect(() => { + new Intl.PluralRules("en", { roundingIncrement: "hello!" }); + }).toThrowWithMessage(RangeError, "Value NaN is NaN or is not between 1 and 5000"); + + expect(() => { + new Intl.PluralRules("en", { roundingIncrement: 0 }); + }).toThrowWithMessage(RangeError, "Value 0 is NaN or is not between 1 and 5000"); + + expect(() => { + new Intl.PluralRules("en", { roundingIncrement: 5001 }); + }).toThrowWithMessage(RangeError, "Value 5001 is NaN or is not between 1 and 5000"); + + expect(() => { + new Intl.PluralRules("en", { roundingIncrement: 3 }); + }).toThrowWithMessage(RangeError, "3 is not a valid rounding increment"); + + expect(() => { + new Intl.PluralRules("en", { roundingIncrement: 5, minimumSignificantDigits: 1 }); + }).toThrowWithMessage( + TypeError, + "5 is not a valid rounding increment for rounding type significantDigits" + ); + + expect(() => { + new Intl.PluralRules("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.PluralRules("en", { trailingZeroDisplay: "hello!" }); + }).toThrowWithMessage( + RangeError, + "hello! is not a valid value for option trailingZeroDisplay" + ); + }); }); describe("normal behavior", () => { @@ -195,4 +246,40 @@ describe("normal behavior", () => { }).not.toThrow(); }); }); + + test("all valid roundingMode options", () => { + [ + "ceil", + "floor", + "expand", + "trunc", + "halfCeil", + "halfFloor", + "halfExpand", + "halfTrunc", + "halfEven", + ].forEach(roundingMode => { + expect(() => { + new Intl.PluralRules("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.PluralRules("en", { roundingIncrement: roundingIncrement }); + }).not.toThrow(); + } + ); + }); + + test("all valid trailingZeroDisplay options", () => { + ["auto", "stripIfInteger"].forEach(trailingZeroDisplay => { + expect(() => { + new Intl.PluralRules("en", { trailingZeroDisplay: trailingZeroDisplay }); + }).not.toThrow(); + }); + }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js index 27e65fb39f5..76fc86c7705 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.resolvedOptions.js @@ -114,4 +114,46 @@ describe("correct behavior", () => { expect(en2.resolvedOptions().roundingPriority).toBe(roundingPriority); }); }); + + test("rounding mode", () => { + const en1 = new Intl.PluralRules("en"); + expect(en1.resolvedOptions().roundingMode).toBe("halfExpand"); + + [ + "ceil", + "floor", + "expand", + "trunc", + "halfCeil", + "halfFloor", + "halfExpand", + "halfTrunc", + "halfEven", + ].forEach(roundingMode => { + const en2 = new Intl.PluralRules("en", { roundingMode: roundingMode }); + expect(en2.resolvedOptions().roundingMode).toBe(roundingMode); + }); + }); + + test("rounding increment", () => { + const en1 = new Intl.PluralRules("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.PluralRules("en", { roundingIncrement: roundingIncrement }); + expect(en2.resolvedOptions().roundingIncrement).toBe(roundingIncrement); + } + ); + }); + + test("trailing zero display", () => { + const en1 = new Intl.PluralRules("en"); + expect(en1.resolvedOptions().trailingZeroDisplay).toBe("auto"); + + ["auto", "stripIfInteger"].forEach(trailingZeroDisplay => { + const en2 = new Intl.PluralRules("en", { trailingZeroDisplay: trailingZeroDisplay }); + expect(en2.resolvedOptions().trailingZeroDisplay).toBe(trailingZeroDisplay); + }); + }); });