diff options
Diffstat (limited to 'js/src/tests/non262/Intl/NumberFormat')
49 files changed, 5115 insertions, 0 deletions
diff --git a/js/src/tests/non262/Intl/NumberFormat/StringBuffer.js b/js/src/tests/non262/Intl/NumberFormat/StringBuffer.js new file mode 100644 index 0000000000..70aaa7bd75 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/StringBuffer.js @@ -0,0 +1,37 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The implementation of the format function uses the C++ StringBuffer class, +// which changes its storage model at 32 characters, and uses it in a way which +// also means that there's no room for null-termination at this limit. +// This test makes sure that none of this affects the output. + +var format = new Intl.NumberFormat("it-IT", {minimumFractionDigits: 1}); + +assertEq(format.format(1123123123123123123123.1), "1.123.123.123.123.123.100.000,0"); +assertEq(format.format(12123123123123123123123.1), "12.123.123.123.123.122.000.000,0"); +assertEq(format.format(123123123123123123123123.1), "123.123.123.123.123.120.000.000,0"); + +// Ensure the ICU output matches Number.prototype.toFixed. +function formatToFixed(x) { + var mfd = format.resolvedOptions().maximumFractionDigits; + var s = x.toFixed(mfd); + + // To keep it simple we assume |s| is always in exponential form. + var m = s.match(/^(\d)\.(\d+)e\+(\d+)$/); + assertEq(m !== null, true); + s = m[1] + m[2].padEnd(m[3], "0"); + + // Group digits and append fractional part. + m = s.match(/\d{1,3}(?=(?:\d{3})*$)/g); + assertEq(m !== null, true); + return m.join(".") + ",0"; +} + +assertEq(formatToFixed(1123123123123123123123.1), "1.123.123.123.123.123.100.000,0"); +assertEq(formatToFixed(12123123123123123123123.1), "12.123.123.123.123.122.000.000,0"); +assertEq(formatToFixed(123123123123123123123123.1), "123.123.123.123.123.120.000.000,0"); + +reportCompare(0, 0, "ok"); diff --git a/js/src/tests/non262/Intl/NumberFormat/bigint-int64.js b/js/src/tests/non262/Intl/NumberFormat/bigint-int64.js new file mode 100644 index 0000000000..7b670d0873 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/bigint-int64.js @@ -0,0 +1,40 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Ensure the int64_t optimization when formatting a BigInt value works correctly by testing with +// various integers around the (u)int[32,64] limits. + +const limits = { + int32: { + min: -0x80000000n, + max: 0x7FFFFFFFn, + }, + uint32: { + min: 0n, + max: 0xFFFFFFFFn + }, + int64: { + min: -0x8000000000000000n, + max: 0x7FFFFFFFFFFFFFFFn, + }, + uint64: { + min: 0n, + max: 0xFFFFFFFFFFFFFFFFn + }, +}; + +const nf = new Intl.NumberFormat("en", {useGrouping: false}); + +const diff = 10n; + +for (const int of Object.values(limits)) { + for (let i = -diff; i <= diff; ++i) { + let n = int.min + i; + assertEq(nf.format(n), n.toString()); + + let m = int.max + i; + assertEq(nf.format(m), m.toString()); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/browser.js b/js/src/tests/non262/Intl/NumberFormat/browser.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/browser.js diff --git a/js/src/tests/non262/Intl/NumberFormat/call.js b/js/src/tests/non262/Intl/NumberFormat/call.js new file mode 100644 index 0000000000..14ecf99eb5 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/call.js @@ -0,0 +1,184 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +function IsIntlService(c) { + return typeof c === "function" && + c.hasOwnProperty("prototype") && + c.prototype.hasOwnProperty("resolvedOptions"); +} + +function IsObject(o) { + return Object(o) === o; +} + +function IsPrimitive(o) { + return Object(o) !== o; +} + +function thisValues() { + const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService); + + return [ + // Primitive values. + ...[undefined, null, true, "abc", Symbol(), 123], + + // Object values. + ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})], + + // Intl objects. + ...[].concat(...intlConstructors.map(ctor => { + let args = []; + if (ctor === Intl.DisplayNames) { + // Intl.DisplayNames can't be constructed without any arguments. + args = [undefined, {type: "language"}]; + } + + return [ + // Instance of an Intl constructor. + new ctor(...args), + + // Instance of a subclassed Intl constructor. + new class extends ctor {}(...args), + + // Object inheriting from an Intl constructor prototype. + Object.create(ctor.prototype), + + // Intl object not inheriting from its default prototype. + Object.setPrototypeOf(new ctor(...args), Object.prototype), + ]; + })), + ]; +} + +const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0]; + +// Invoking [[Call]] for Intl.NumberFormat returns a new instance unless called +// with an instance inheriting from Intl.NumberFormat.prototype. +for (let thisValue of thisValues()) { + let obj = Intl.NumberFormat.call(thisValue); + + if (!Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) { + assertEq(Object.is(obj, thisValue), false); + assertEq(obj instanceof Intl.NumberFormat, true); + if (IsObject(thisValue)) + assertEqArray(Object.getOwnPropertySymbols(thisValue), []); + } else { + assertEq(Object.is(obj, thisValue), true); + assertEq(obj instanceof Intl.NumberFormat, true); + assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]); + } +} + +// Intl.NumberFormat uses the legacy Intl constructor compromise semantics. +// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns true. +for (let thisValue of thisValues().filter(IsObject)) { + let isPrototypeOf = Intl.NumberFormat.prototype.isPrototypeOf(thisValue); + let hasInstanceCalled = false; + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { + assertEq(hasInstanceCalled, false); + hasInstanceCalled = true; + return true; + }, configurable: true + }); + let obj = Intl.NumberFormat.call(thisValue); + delete Intl.NumberFormat[Symbol.hasInstance]; + + assertEq(Object.is(obj, thisValue), isPrototypeOf); + assertEq(hasInstanceCalled, false); + assertEqArray(Object.getOwnPropertySymbols(thisValue), isPrototypeOf ? [intlFallbackSymbol] : []); +} +// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false. +for (let thisValue of thisValues().filter(IsObject)) { + let isPrototypeOf = Intl.NumberFormat.prototype.isPrototypeOf(thisValue); + let hasInstanceCalled = false; + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { + assertEq(hasInstanceCalled, false); + hasInstanceCalled = true; + return false; + }, configurable: true + }); + let obj = Intl.NumberFormat.call(thisValue); + delete Intl.NumberFormat[Symbol.hasInstance]; + + assertEq(Object.is(obj, thisValue), isPrototypeOf); + assertEq(obj instanceof Intl.NumberFormat, true); + assertEq(hasInstanceCalled, false); + assertEqArray(Object.getOwnPropertySymbols(thisValue), isPrototypeOf ? [intlFallbackSymbol] : []); +} +// - Test with primitive values. +for (let thisValue of thisValues().filter(IsPrimitive)) { + // Ensure @@hasInstance is not called. + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { assertEq(true, false); }, configurable: true + }); + let obj = Intl.NumberFormat.call(thisValue); + delete Intl.NumberFormat[Symbol.hasInstance]; + + assertEq(Object.is(obj, thisValue), false); + assertEq(obj instanceof Intl.NumberFormat, true); +} + +// Throws an error when attempting to install [[FallbackSymbol]] twice. +{ + let thisValue = Object.create(Intl.NumberFormat.prototype); + assertEqArray(Object.getOwnPropertySymbols(thisValue), []); + + assertEq(Intl.NumberFormat.call(thisValue), thisValue); + assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]); + + assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError); + assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]); +} + +// Throws an error when the thisValue is non-extensible. +{ + let thisValue = Object.create(Intl.NumberFormat.prototype); + Object.preventExtensions(thisValue); + + assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError); + assertEqArray(Object.getOwnPropertySymbols(thisValue), []); +} + +// [[FallbackSymbol]] is installed as a frozen property holding an Intl.NumberFormat instance. +{ + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + + let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol); + assertEq(desc !== undefined, true); + assertEq(desc.writable, false); + assertEq(desc.enumerable, false); + assertEq(desc.configurable, false); + assertEq(desc.value instanceof Intl.NumberFormat, true); +} + +// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]] +// during initialization. +{ + let thisValue = {}; + let options = { + get useGrouping() { + Object.setPrototypeOf(thisValue, Intl.NumberFormat.prototype); + return false; + } + }; + let obj = Intl.NumberFormat.call(thisValue, undefined, options); + assertEq(Object.is(obj, thisValue), true); + assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]); +} +{ + let thisValue = Object.create(Intl.NumberFormat.prototype); + let options = { + get useGrouping() { + Object.setPrototypeOf(thisValue, Object.prototype); + return false; + } + }; + let obj = Intl.NumberFormat.call(thisValue, undefined, options); + assertEq(Object.is(obj, thisValue), false); + assertEqArray(Object.getOwnPropertySymbols(thisValue), []); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js b/js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js new file mode 100644 index 0000000000..0053b2737e --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +// Test subclassing %Intl.NumberFormat% works correctly. +class MyNumberFormat extends Intl.NumberFormat {} + +var obj = new MyNumberFormat(); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, []); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], MyNumberFormat); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], Intl.NumberFormat); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyNumberFormat, [], Array); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.NumberFormat, [], Array); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %NumberFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.NumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.NumberFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.NumberFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/non262/Intl/NumberFormat/cross-compartment.js b/js/src/tests/non262/Intl/NumberFormat/cross-compartment.js new file mode 100644 index 0000000000..806f889d6f --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/cross-compartment.js @@ -0,0 +1,70 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +var otherGlobal = newGlobal(); + +var numberFormat = new Intl.NumberFormat(); +var ccwNumberFormat = new otherGlobal.Intl.NumberFormat(); + +// Test Intl.NumberFormat.prototype.format with a CCW object. +var Intl_NumberFormat_format_get = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get; + +assertEq(Intl_NumberFormat_format_get.call(ccwNumberFormat)(0), + Intl_NumberFormat_format_get.call(numberFormat)(0)); + +// Test Intl.NumberFormat.prototype.formatToParts with a CCW object. +var Intl_NumberFormat_formatToParts = Intl.NumberFormat.prototype.formatToParts; + +assertEq(deepEqual(Intl_NumberFormat_formatToParts.call(ccwNumberFormat, 0), + Intl_NumberFormat_formatToParts.call(numberFormat, 0)), + true); + +// Test Intl.NumberFormat.prototype.resolvedOptions with a CCW object. +var Intl_NumberFormat_resolvedOptions = Intl.NumberFormat.prototype.resolvedOptions; + +assertEq(deepEqual(Intl_NumberFormat_resolvedOptions.call(ccwNumberFormat), + Intl_NumberFormat_resolvedOptions.call(numberFormat)), + true); + +// Special case for Intl.NumberFormat: The Intl fallback symbol. + +function fallbackSymbol(global) { + var NF = global.Intl.NumberFormat; + return Object.getOwnPropertySymbols(NF.call(Object.create(NF.prototype)))[0]; +} + +const intlFallbackSymbol = fallbackSymbol(this); +const otherIntlFallbackSymbol = fallbackSymbol(otherGlobal); +assertEq(intlFallbackSymbol === otherIntlFallbackSymbol, false); + +// Test when the fallback symbol points to a CCW NumberFormat object. +var objWithFallbackCCWNumberFormat = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: ccwNumberFormat, +}; + +assertEq(Intl_NumberFormat_format_get.call(objWithFallbackCCWNumberFormat)(0), + Intl_NumberFormat_format_get.call(numberFormat)(0)); + +assertEq(deepEqual(Intl_NumberFormat_resolvedOptions.call(objWithFallbackCCWNumberFormat), + Intl_NumberFormat_resolvedOptions.call(numberFormat)), + true); + +// Ensure the fallback symbol(s) are not accessed for CCW NumberFormat objects. +var ccwNumberFormatWithPoisonedFallback = new otherGlobal.Intl.NumberFormat(); +Object.setPrototypeOf(ccwNumberFormatWithPoisonedFallback, Intl.NumberFormat.prototype); +Object.defineProperty(ccwNumberFormatWithPoisonedFallback, intlFallbackSymbol, { + get() { throw new Error(); } +}); +Object.defineProperty(ccwNumberFormatWithPoisonedFallback, otherIntlFallbackSymbol, { + get() { throw new Error(); } +}); + +assertEq(Intl_NumberFormat_format_get.call(ccwNumberFormatWithPoisonedFallback)(0), + Intl_NumberFormat_format_get.call(numberFormat)(0)); + +assertEq(deepEqual(Intl_NumberFormat_resolvedOptions.call(ccwNumberFormatWithPoisonedFallback), + Intl_NumberFormat_resolvedOptions.call(numberFormat)), + true); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js b/js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js new file mode 100644 index 0000000000..bf2b9adcd8 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/currency-narrow-symbol.js @@ -0,0 +1,40 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Integer, Decimal, Fraction, Currency, Literal, +} = NumberFormatParts; + +const testcases = [ + { + locale: "en-CA", + options: { + style: "currency", + currency: "USD", + currencyDisplay: "narrowSymbol", + }, + values: [ + {value: 123, string: "US$123.00", + parts: [Currency("US$"), Integer("123"), Decimal("."), Fraction("00")]}, + ], + }, + + // And for comparison "symbol" currency-display. + + { + locale: "en-CA", + options: { + style: "currency", + currency: "USD", + currencyDisplay: "symbol", + }, + values: [ + {value: 123, string: "US$123.00", + parts: [Currency("US$"), Integer("123"), Decimal("."), Fraction("00")]}, + ], + }, +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js b/js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js new file mode 100644 index 0000000000..6f91d6edd4 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/currency-sign-accounting.js @@ -0,0 +1,192 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, + Currency, Literal, +} = NumberFormatParts; + +const testcases = [ + // "auto": Show the sign on negative numbers only. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "auto", + }, + values: [ + {value: +0, string: "$0.00", + parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]}, + {value: -0, string: "($0.00)", + parts: [Literal("("), Currency("$"), Integer("0"), Decimal("."), Fraction("00"), Literal(")")]}, + + {value: 1, string: "$1.00", + parts: [Currency("$"), Integer("1"), Decimal("."), Fraction("00")]}, + {value: -1, string: "($1.00)", + parts: [Literal("("), Currency("$"), Integer("1"), Decimal("."), Fraction("00"), Literal(")")]}, + + {value: Infinity, string: "$∞", parts: [Currency("$"), Inf("∞")]}, + {value: -Infinity, string: "($∞)", parts: [Literal("("), Currency("$"), Inf("∞"), Literal(")")]}, + + {value: NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]}, + {value: -NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]}, + ], + }, + + // "never": Show the sign on neither positive nor negative numbers. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "never", + }, + values: [ + {value: +0, string: "$0.00", parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]}, + {value: -0, string: "$0.00", parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]}, + + {value: 1, string: "$1.00", parts: [Currency("$"), Integer("1"), Decimal("."), Fraction("00")]}, + {value: -1, string: "$1.00", parts: [Currency("$"), Integer("1"), Decimal("."), Fraction("00")]}, + + {value: Infinity, string: "$∞", parts: [Currency("$"), Inf("∞")]}, + {value: -Infinity, string: "$∞", parts: [Currency("$"), Inf("∞")]}, + + {value: NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]}, + {value: -NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]}, + ], + }, + + // "always": Show the sign on positive and negative numbers including zero. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "always", + }, + values: [ + {value: +0, string: "+$0.00", + parts: [PlusSign("+"), Currency("$"), Integer("0"), Decimal("."), Fraction("00")]}, + {value: -0, string: "($0.00)", + parts: [Literal("("), Currency("$"), Integer("0"), Decimal("."), Fraction("00"), Literal(")")]}, + + {value: 1, string: "+$1.00", + parts: [PlusSign("+"), Currency("$"), Integer("1"), Decimal("."), Fraction("00")]}, + {value: -1, string: "($1.00)", + parts: [Literal("("), Currency("$"), Integer("1"), Decimal("."), Fraction("00"), Literal(")")]}, + + {value: Infinity, string: "+$∞", parts: [PlusSign("+"), Currency("$"), Inf("∞")]}, + {value: -Infinity, string: "($∞)", parts: [Literal("("), Currency("$"), Inf("∞"), Literal(")")]}, + + {value: NaN, string: "+$NaN", parts: [PlusSign("+"), Currency("$"), Nan("NaN")]}, + {value: -NaN, string: "+$NaN", parts: [PlusSign("+"), Currency("$"), Nan("NaN")]}, + ], + }, + + // "exceptZero": Show the sign on positive and negative numbers but not zero. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "exceptZero", + }, + values: [ + {value: +0, string: "$0.00", + parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]}, + {value: -0, string: "$0.00", + parts: [Currency("$"), Integer("0"), Decimal("."), Fraction("00")]}, + + {value: 1, string: "+$1.00", + parts: [PlusSign("+"), Currency("$"), Integer("1"), Decimal("."), Fraction("00")]}, + {value: -1, string: "($1.00)", + parts: [Literal("("), Currency("$"), Integer("1"), Decimal("."), Fraction("00"), Literal(")")]}, + + {value: Infinity, string: "+$∞", parts: [PlusSign("+"), Currency("$"), Inf("∞")]}, + {value: -Infinity, string: "($∞)", parts: [Literal("("), Currency("$"), Inf("∞"), Literal(")")]}, + + {value: NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]}, + {value: -NaN, string: "$NaN", parts: [Currency("$"), Nan("NaN")]}, + ], + }, + + // Tests with suppressed fractional digits. + + // "auto": Show the sign on negative numbers only. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "auto", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }, + values: [ + {value: +0.1, string: "$0", parts: [Currency("$"), Integer("0")]}, + {value: -0.1, string: "($0)", parts: [Literal("("), Currency("$"), Integer("0"), Literal(")")]}, + ], + }, + + // "never": Show the sign on neither positive nor negative numbers. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "never", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }, + values: [ + {value: +0.1, string: "$0", parts: [Currency("$"), Integer("0")]}, + {value: -0.1, string: "$0", parts: [Currency("$"), Integer("0")]}, + ], + }, + + // "always": Show the sign on positive and negative numbers including zero. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "always", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }, + values: [ + {value: +0.1, string: "+$0", parts: [PlusSign("+"), Currency("$"), Integer("0")]}, + {value: -0.1, string: "($0)", parts: [Literal("("), Currency("$"), Integer("0"), Literal(")")]}, + ], + }, + + // "exceptZero": Show the sign on positive and negative numbers but not zero. + { + locale: "en", + options: { + style: "currency", + currency: "USD", + currencySign: "accounting", + signDisplay: "exceptZero", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }, + + values: [ + {value: +0.1, string: "$0", parts: [Currency("$"), Integer("0")]}, + {value: -0.1, string: "$0", parts: [Currency("$"), Integer("0")]}, + ], + } +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js b/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js new file mode 100644 index 0000000000..42bf78c3fe --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Check for duplicate variants and singletons case-insensitively, but don't +// check in privateuse components. + +function checkInvalidLocale(locale) +{ + try + { + new Intl.NumberFormat(locale); + throw new Error("didn't throw"); + } + catch (e) + { + assertEq(e instanceof RangeError, true, + "expected RangeError for locale '" + locale + "', got " + e); + } +} + +var badLocales = + [ + "en-u-foo-U-foo", + "en-tester-Tester", + "en-tesTER-TESter", + "de-DE-u-kn-true-U-kn-true", + "ar-u-foo-q-bar-u-baz", + "ar-z-moo-u-foo-q-bar-z-eit-u-baz", + ]; + +for (var locale of badLocales) + checkInvalidLocale(locale); + +// Fully-privateuse locales are rejected. +for (var locale of badLocales) + assertThrowsInstanceOf(() => new Intl.NumberFormat("x-" + locale), RangeError); + +// Locales with trailing privateuse also okay. +for (var locale of badLocales) +{ + new Intl.NumberFormat("en-x-" + locale).format(5); + new Intl.NumberFormat("en-u-foo-x-u-" + locale).format(5); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js b/js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js new file mode 100644 index 0000000000..ba257ecd71 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/format-as-code-or-name.js @@ -0,0 +1,75 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1093421; +var summary = + "new Intl.NumberFormat(..., { style: 'currency', currency: '...', " + + "currencyDisplay: 'name' or 'code' }) should have behavior other than " + + "throwing"; + +print(BUGNUMBER + ": " + summary); + +//----------------------------------------------------------------------------- + +// Test that currencyDisplay: "code" behaves correctly and doesn't throw. + +var usdCodeOptions = + { + style: "currency", + currency: "USD", + currencyDisplay: "code", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var usDollarsCode = new Intl.NumberFormat("en-US", usdCodeOptions); +assertEq(/USD/.test(usDollarsCode.format(25)), true); + +// ISO 4217 currency codes are formed from an ISO 3166-1 alpha-2 country code +// followed by a third letter. ISO 3166 guarantees that no country code +// starting with "X" will ever be assigned. Stepping carefully around a few +// 4217-designated special "currencies", XQQ will never have a representation. +// Thus, yes: this really is specified to work, as unrecognized or unsupported +// codes pass into the string unmodified. +var xqqCodeOptions = + { + style: "currency", + currency: "XQQ", + currencyDisplay: "code", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var xqqMoneyCode = new Intl.NumberFormat("en-US", xqqCodeOptions); +assertEq(/XQQ/.test(xqqMoneyCode.format(25)), true); + +// Test that currencyDisplay: "name" behaves without throwing. (Unlike the two +// above tests, the results here aren't guaranteed as the name is +// implementation-defined.) +var usdNameOptions = + { + style: "currency", + currency: "USD", + currencyDisplay: "name", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var usDollarsName = new Intl.NumberFormat("en-US", usdNameOptions); +assertEq(usDollarsName.format(25), "25 US dollars"); + +// But if the implementation doesn't recognize the currency, the provided code +// is used in place of a proper name, unmolested. +var xqqNameOptions = + { + style: "currency", + currency: "XQQ", + currencyDisplay: "name", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var xqqMoneyName = new Intl.NumberFormat("en-US", xqqNameOptions); +assertEq(/XQQ/.test(xqqMoneyName.format(25)), true); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/format-string.js b/js/src/tests/non262/Intl/NumberFormat/format-string.js new file mode 100644 index 0000000000..ec83b5147a --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/format-string.js @@ -0,0 +1,149 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +function groupByThree(s) { + return String(s).split("").reduceRight((acc, x) => x + (acc.match(/^\d{3}/) ? "," : "") + acc, ""); +} + +const tests = [ + {value: "", expected: "0"}, + {value: "0", expected: "0"}, + {value: "+0", expected: "0"}, + {value: "-0", expected: "-0"}, + + {value: "Infinity", expected: "∞"}, + {value: "+Infinity", expected: "∞"}, + {value: "-Infinity", expected: "-∞"}, + + {value: "NaN", expected: "NaN"}, + {value: "invalid", expected: "NaN"}, + + // Integer 1 with and without fractional/exponent part. + {value: "1", expected: "1"}, + {value: "1.", expected: "1"}, + {value: "1.0", expected: "1"}, + {value: "1.00", expected: "1"}, + {value: "1e0", expected: "1"}, + {value: "1e+0", expected: "1"}, + {value: "1e-0", expected: "1"}, + + // Leading zeros. + {value: "01", expected: "1"}, + {value: "01.", expected: "1"}, + {value: "01.0", expected: "1"}, + {value: "01.00", expected: "1"}, + {value: "01e0", expected: "1"}, + {value: "01e+0", expected: "1"}, + {value: "01e-0", expected: "1"}, + + // Large values. + {value: "1e300", expected: "1" + ",000".repeat(100)}, + {value: "1e308", expected: "100" + ",000".repeat(102)}, + {value: "1e309", expected: "∞"}, + {value: "1e3000", expected: "∞"}, + {value: "1e+9999999", expected: "∞"}, + {value: "1e+99999999", expected: "∞"}, + {value: ".1e+9999999", expected: "∞"}, + {value: ".1e+99999999", expected: "∞"}, + {value: ".1e+999999999", expected: "∞"}, + {value: ".1e+9999999999", expected: "∞"}, + {value: "9007199254740991", expected: "9,007,199,254,740,991"}, + {value: "9007199254740992", expected: "9,007,199,254,740,992"}, + {value: "9007199254740993", expected: "9,007,199,254,740,993"}, + + {value: "-1e300", expected: "-1" + ",000".repeat(100)}, + {value: "-1e308", expected: "-100" + ",000".repeat(102)}, + {value: "-1e309", expected: "-∞"}, + {value: "-1e3000", expected: "-∞"}, + {value: "-1e+9999999", expected: "-∞"}, + {value: "-1e+99999999", expected: "-∞"}, + {value: "-.1e+9999999", expected: "-∞"}, + {value: "-.1e+99999999", expected: "-∞"}, + {value: "-.1e+999999999", expected: "-∞"}, + {value: "-.1e+9999999999", expected: "-∞"}, + {value: "-9007199254740991", expected: "-9,007,199,254,740,991"}, + {value: "-9007199254740992", expected: "-9,007,199,254,740,992"}, + {value: "-9007199254740993", expected: "-9,007,199,254,740,993"}, + + // Small values. + {value: "0.10000000000000000001", expected: "0.10000000000000000001"}, + {value: "0.00000000000000000001", expected: "0.00000000000000000001"}, + {value: "1e-20", expected: "0.00000000000000000001"}, + {value: "1e-30", expected: "0"}, + {value: ".1e-9999999", expected: "0"}, + {value: ".1e-99999999", expected: "0"}, + {value: ".1e-999999999", expected: "0"}, + {value: ".1e-9999999999", expected: "0"}, + + {value: "-0.10000000000000000001", expected: "-0.10000000000000000001"}, + {value: "-0.00000000000000000001", expected: "-0.00000000000000000001"}, + {value: "-1e-20", expected: "-0.00000000000000000001"}, + {value: "-1e-30", expected: "-0"}, + {value: "-.1e-9999999", expected: "-0"}, + {value: "-.1e-99999999", expected: "-0"}, + {value: "-.1e-999999999", expected: "-0"}, + {value: "-.1e-9999999999", expected: "-0"}, + + // Non-standard exponent notation. + {value: ".001e-2", expected: "0.00001"}, + {value: "123.001e-2", expected: "1.23001"}, + {value: "1000e-2", expected: "10"}, + {value: "1000e+2", expected: "100,000"}, + {value: "1000e-0", expected: "1,000"}, + + // Non-decimal strings. + {value: "0b101", expected: "5"}, + {value: "0o377", expected: "255"}, + {value: "0xdeadBEEF", expected: "3,735,928,559"}, + {value: "0B0011", expected: "3"}, + {value: "0O0777", expected: "511"}, + {value: "0X0ABC", expected: "2,748"}, + {value: "0b" + "1".repeat(1000), expected: groupByThree((2n ** 1000n) - 1n)}, + {value: "0o1" + "7".repeat(333), expected: groupByThree((2n ** 1000n) - 1n)}, + {value: "0x" + "f".repeat(250), expected: groupByThree((2n ** 1000n) - 1n)}, + + {value: "0b" + "1".repeat(1023), expected: groupByThree(0b10n ** 1023n - 1n)}, + {value: "0b" + "1".repeat(1024), expected: "∞"}, + {value: "0o" + "7".repeat(341), expected: groupByThree(0o10n ** 341n - 1n)}, + {value: "0o" + "7".repeat(342), expected: "∞"}, + {value: "0x" + "f".repeat(255), expected: groupByThree(0x10n ** 255n - 1n)}, + {value: "0x" + "f".repeat(256), expected: "∞"}, + + // Non-decimal strings don't accept a sign. + {value: "+0xbad", expected: "NaN"}, + {value: "-0xbad", expected: "NaN"}, +]; + +// https://tc39.es/ecma262/#prod-StrWhiteSpaceChar +const strWhiteSpaceChar = [ + "", + + // https://tc39.es/ecma262/#sec-white-space + "\t", "\v", "\f", " ", "\u00A0", "\uFEFF", + "\u1680", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", + "\u2007", "\u2008", "\u2009", "\u200A", "\u202F", "\u205F", "\u3000", + + // https://tc39.es/ecma262/#sec-line-terminators + "\n", "\r", "\u2028", "\u2029", +]; + +let nf = new Intl.NumberFormat("en", {maximumFractionDigits: 20}); +for (let {value, expected} of tests) { + for (let ws of strWhiteSpaceChar) { + assertEq(nf.format(ws + value), expected); + assertEq(nf.format(value + ws), expected); + assertEq(nf.format(ws + value + ws), expected); + } +} + +// Combine extreme values with other rounding modes. +{ + let nf = new Intl.NumberFormat("en", { + minimumFractionDigits: 20, + roundingMode: "ceil", + roundingIncrement: 5000, + }); + assertEq(nf.format(".1e-999999998"), "0.00000000000000000000"); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/format.js b/js/src/tests/non262/Intl/NumberFormat/format.js new file mode 100644 index 0000000000..db35251b20 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/format.js @@ -0,0 +1,55 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Tests the format function with a diverse set of locales and options. + +var format; + +// Locale en-US; default options. +format = new Intl.NumberFormat("en-us"); +assertEq(format.format(0), "0"); +assertEq(format.format(-1), "-1"); +assertEq(format.format(123456789.123456789), "123,456,789.123"); + +// Locale en-US; currency USD. +// The US dollar uses two fractional digits, and negative values are commonly +// parenthesized. +format = new Intl.NumberFormat("en-us", {style: "currency", currency: "USD"}); +assertEq(format.format(0), "$0.00"); +assertEq(format.format(-1), "-$1.00"); +assertEq(format.format(123456789.123456789), "$123,456,789.12"); + +// Locale ja-JP; currency JPY. +// The Japanese yen has no subunit in real life. +format = new Intl.NumberFormat("ja-jp", {style: "currency", currency: "JPY"}); +assertEq(format.format(0), "¥0"); +assertEq(format.format(-1), "-¥1"); +assertEq(format.format(123456789.123456789), "¥123,456,789"); + +// Locale ar-JO; currency JOD. +// The Jordanian Dinar divides into 1000 fils. Jordan uses (real) Arabic digits. +format = new Intl.NumberFormat("ar-jo", {style: "currency", currency: "JOD"}); +assertEq(format.format(0), "\u{200F}٠٫٠٠٠ د.أ.\u{200F}"); +assertEq(format.format(-1), "\u{061C}-\u{200F}١٫٠٠٠ د.أ.\u{200F}"); +assertEq(format.format(123456789.123456789), "\u{200F}١٢٣٬٤٥٦٬٧٨٩٫١٢٣ د.أ.\u{200F}"); + +// Locale th-TH; Thai digits, percent, two significant digits. +format = new Intl.NumberFormat("th-th-u-nu-thai", + {style: "percent", + minimumSignificantDigits: 2, + maximumSignificantDigits: 2}); +assertEq(format.format(0), "๐.๐%"); +assertEq(format.format(-0.01), "-๑.๐%"); +assertEq(format.format(1.10), "๑๑๐%"); + + +// Test the .name property of the "format" getter. +var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format"); +assertEq(desc !== undefined, true); +assertEq(typeof desc.get, "function"); +assertEq(desc.get.name, "get format"); + + +reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRange-BigInt.js b/js/src/tests/non262/Intl/NumberFormat/formatRange-BigInt.js new file mode 100644 index 0000000000..f856073b71 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRange-BigInt.js @@ -0,0 +1,150 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Int64-BigInts which can be exactly represented as doubles receive a fast-path. + +const tests = { + "en": { + options: {}, + ranges: [ + // BigInt around Number.MIN_SAFE_INTEGER. + { + start: -0x20000000000001n, + end: -0x20000000000000n, + result: "-9,007,199,254,740,993 – -9,007,199,254,740,992", + }, + { + start: -0x20000000000000n, + end: -0x20000000000000n, + result: "~-9,007,199,254,740,992", + }, + { + start: -0x20000000000000n, + end: -0x1fffffffffffffn, + result: "-9,007,199,254,740,992 – -9,007,199,254,740,991", + }, + { + start: -0x1fffffffffffffn, + end: -0x1fffffffffffffn, + result: "~-9,007,199,254,740,991", + }, + { + start: -0x1fffffffffffffn, + end: -0x1ffffffffffffen, + result: "-9,007,199,254,740,991 – -9,007,199,254,740,990", + }, + { + start: -0x1ffffffffffffen, + end: -0x1ffffffffffffen, + result: "~-9,007,199,254,740,990", + }, + + // BigInt around Number.MAX_SAFE_INTEGER. + { + start: 0x1ffffffffffffen, + end: 0x1ffffffffffffen, + result: "~9,007,199,254,740,990", + }, + { + start: 0x1ffffffffffffen, + end: 0x1fffffffffffffn, + result: "9,007,199,254,740,990–9,007,199,254,740,991", + }, + { + start: 0x1fffffffffffffn, + end: 0x1fffffffffffffn, + result: "~9,007,199,254,740,991", + }, + { + start: 0x1fffffffffffffn, + end: 0x20000000000000n, + result: "9,007,199,254,740,991–9,007,199,254,740,992", + }, + { + start: 0x20000000000000n, + end: 0x20000000000000n, + result: "~9,007,199,254,740,992", + }, + { + start: 0x20000000000000n, + end: 0x20000000000001n, + result: "9,007,199,254,740,992–9,007,199,254,740,993", + }, + + // BigInt around INT64_MIN. + { + start: -0x8000000000000002n, + end: -0x8000000000000001n, + result: "-9,223,372,036,854,775,810 – -9,223,372,036,854,775,809", + }, + { + start: -0x8000000000000001n, + end: -0x8000000000000001n, + result: "~-9,223,372,036,854,775,809", + }, + { + start: -0x8000000000000001n, + end: -0x8000000000000000n, + result: "-9,223,372,036,854,775,809 – -9,223,372,036,854,775,808", + }, + { + start: -0x8000000000000000n, + end: -0x8000000000000000n, + result: "~-9,223,372,036,854,775,808", + }, + { + start: -0x8000000000000000n, + end: -0x7fffffffffffffffn, + result: "-9,223,372,036,854,775,808 – -9,223,372,036,854,775,807", + }, + { + start: -0x7fffffffffffffffn, + end: -0x7fffffffffffffffn, + result: "~-9,223,372,036,854,775,807", + }, + + // BigInt around INT64_MAX. + { + start: 0x7ffffffffffffffen, + end: 0x7ffffffffffffffen, + result: "~9,223,372,036,854,775,806", + }, + { + start: 0x7ffffffffffffffen, + end: 0x7fffffffffffffffn, + result: "9,223,372,036,854,775,806–9,223,372,036,854,775,807", + }, + { + start: 0x7fffffffffffffffn, + end: 0x7fffffffffffffffn, + result: "~9,223,372,036,854,775,807", + }, + { + start: 0x7fffffffffffffffn, + end: 0x8000000000000000n, + result: "9,223,372,036,854,775,807–9,223,372,036,854,775,808", + }, + { + start: 0x8000000000000000n, + end: 0x8000000000000000n, + result: "~9,223,372,036,854,775,808", + }, + { + start: 0x8000000000000000n, + end: 0x8000000000000001n, + result: "9,223,372,036,854,775,808–9,223,372,036,854,775,809", + }, + ], + }, +}; + +for (let [locale, {options, ranges}] of Object.entries(tests)) { + let nf = new Intl.NumberFormat(locale, options); + for (let {start, end, result} of ranges) { + assertEq(nf.formatRange(start, end), result, `${start}-${end}`); + assertEq(nf.formatRangeToParts(start, end).reduce((acc, part) => acc + part.value, ""), + result, `${start}-${end}`); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRange.js b/js/src/tests/non262/Intl/NumberFormat/formatRange.js new file mode 100644 index 0000000000..733d7e71a5 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRange.js @@ -0,0 +1,296 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// String representation for Number.MAX_VALUE. +const en_Number_MAX_VALUE = "179,769,313,486,231,570" + ",000".repeat(97); +const de_Number_MAX_VALUE = en_Number_MAX_VALUE.replaceAll(",", "."); +const fr_Number_MAX_VALUE = en_Number_MAX_VALUE.replaceAll(",", " "); + +const tests = { + "en": { + options: {}, + ranges: [ + // Values around zero. + {start: 0, end: 0, result: "~0"}, + {start: 0, end: -0, result: "0–-0"}, + {start: -0, end: 0, result: "-0 – 0"}, + {start: -0, end: 0.1e-3, result: "-0 – 0"}, + {start: -0, end: "0.1e-3", result: "-0 – 0"}, + {start: "-0", end: 0.1e-3, result: "-0 – 0"}, + {start: -0, end: -0, result: "~-0"}, + {start: -0, end: -0.1, result: "-0 – -0.1"}, + + // Values starting at negative infinity. + {start: -Infinity, end: -Infinity, result: "~-∞"}, + {start: -Infinity, end: -0, result: "-∞ – -0"}, + {start: -Infinity, end: +0, result: "-∞ – 0"}, + {start: -Infinity, end: +Infinity, result: "-∞ – ∞"}, + + // Values ending at negative infinity. + {start: -Number.MAX_VALUE, end: -Infinity, result: "-" + en_Number_MAX_VALUE + " – -∞"}, + {start: -0, end: -Infinity, result: "-0 – -∞"}, + {start: 0, end: -Infinity, result: "0–-∞"}, + {start: Number.MAX_VALUE, end: -Infinity, result: en_Number_MAX_VALUE + "–-∞"}, + + // Values starting at positive infinity. + {start: Infinity, end: Number.MAX_VALUE, result: "∞–" + en_Number_MAX_VALUE}, + {start: Infinity, end: 0, result: "∞–0"}, + {start: Infinity, end: -0, result: "∞–-0"}, + {start: Infinity, end: -Number.MAX_VALUE, result: "∞–-" + en_Number_MAX_VALUE}, + {start: Infinity, end: -Infinity, result: "∞–-∞"}, + + // Values ending at positive infinity. + {start: Infinity, end: Infinity, result: "~∞"}, + + // Non-special cases. + {start: 1, end: 100, result: "1–100"}, + {start: -100, end: 100, result: "-100 – 100"}, + {start: -1000, end: -100, result: "-1,000 – -100"}, + {start: Math.PI, end: 123_456.789, result: "3.142–123,456.789"}, + {start: -Math.PI, end: Math.E, result: "-3.142 – 2.718"}, + { + start: Number.MAX_SAFE_INTEGER, + end: 9007199254740993, + result: "9,007,199,254,740,991–9,007,199,254,740,992", + }, + { + start: Number.MAX_SAFE_INTEGER, + end: "9007199254740993", + result: "9,007,199,254,740,991–9,007,199,254,740,993", + }, + + // Start value is larger than end value. + {start: -0, end: -0.1, result: "-0 – -0.1"}, + {start: -0, end: -Number.MAX_VALUE, result: "-0 – -" + en_Number_MAX_VALUE}, + {start: 1, end: 0, result: "1–0"}, + {start: 0, end: -1, result: "0–-1"}, + {start: 1, end: -1, result: "1–-1"}, + {start: -1, end: -2, result: "-1 – -2"}, + {start: "10e2", end: "1e-3", result: "1,000–0.001"}, + {start: "0x100", end: "1e1", result: "256–10"}, + {start: ".1e-999999", end: ".01e-999999", result: "~0"}, + {start: ".1e99999", end: "0", result: "∞–0"}, + // Number.MAX_VALUE is 1.7976931348623157e+308. + { + start: "1.7976931348623158e+308", + end: Number.MAX_VALUE, + result: "179,769,313,486,231,580" + ",000".repeat(97) + "–" + en_Number_MAX_VALUE, + }, + // Number.MIN_VALUE is 5e-324. + {start: "6e-324", end: Number.MIN_VALUE, result: "~0"}, + ], + }, + "de": { + options: {style: "currency", currency: "EUR"}, + ranges: [ + // Values around zero. + {start: 0, end: 0, result: "≈0,00 €"}, + {start: 0, end: -0, result: "0,00 € – -0,00 €"}, + {start: -0, end: 0, result: "-0,00 € – 0,00 €"}, + {start: -0, end: 0.1e-3, result: "-0,00 € – 0,00 €"}, + {start: -0, end: "0.1e-3", result: "-0,00 € – 0,00 €"}, + {start: "-0", end: 0.1e-3, result: "-0,00 € – 0,00 €"}, + {start: -0, end: -0, result: "≈-0,00 €"}, + {start: -0, end: -0.1, result: "-0,00–0,10 €"}, + + // Values starting at negative infinity. + {start: -Infinity, end: -Infinity, result: "≈-∞ €"}, + {start: -Infinity, end: -0, result: "-∞–0,00 €"}, + {start: -Infinity, end: +0, result: "-∞ € – 0,00 €"}, + {start: -Infinity, end: +Infinity, result: "-∞ € – ∞ €"}, + + // Values ending at negative infinity. + {start: -Number.MAX_VALUE, end: -Infinity, result: "-" + de_Number_MAX_VALUE + ",00–∞ €"}, + {start: -0, end: -Infinity, result: "-0,00–∞ €"}, + {start: 0, end: -Infinity, result: "0,00 € – -∞ €"}, + {start: Number.MAX_VALUE, end: -Infinity, result: de_Number_MAX_VALUE + ",00 € – -∞ €"}, + + // Values starting at positive infinity. + {start: Infinity, end: Number.MAX_VALUE, result: "∞–" + de_Number_MAX_VALUE + ",00 €"}, + {start: Infinity, end: 0, result: "∞–0,00 €"}, + {start: Infinity, end: -0, result: "∞ € – -0,00 €"}, + {start: Infinity, end: -Number.MAX_VALUE, result: "∞ € – -" + de_Number_MAX_VALUE + ",00 €"}, + {start: Infinity, end: -Infinity, result: "∞ € – -∞ €"}, + + // Values ending at positive infinity. + {start: Infinity, end: Infinity, result: "≈∞ €"}, + + // Non-special cases. + {start: 1, end: 100, result: "1,00–100,00 €"}, + {start: -100, end: 100, result: "-100,00 € – 100,00 €"}, + {start: -1000, end: -100, result: "-1.000,00–100,00 €"}, + {start: Math.PI, end: 123_456.789, result: "3,14–123.456,79 €"}, + {start: -Math.PI, end: Math.E, result: "-3,14 € – 2,72 €"}, + { + start: Number.MAX_SAFE_INTEGER, + end: 9007199254740993, + result: "9.007.199.254.740.991,00–9.007.199.254.740.992,00 €", + }, + { + start: Number.MAX_SAFE_INTEGER, + end: "9007199254740993", + result: "9.007.199.254.740.991,00–9.007.199.254.740.993,00 €", + }, + + // Start value is larger than end value. + {start: -0, end: -0.1, result: "-0,00–0,10 €"}, + {start: -0, end: -Number.MAX_VALUE, result: "-0,00–" + de_Number_MAX_VALUE + ",00 €"}, + {start: 1, end: 0, result: "1,00–0,00 €"}, + {start: 0, end: -1, result: "0,00 € – -1,00 €"}, + {start: 1, end: -1, result: "1,00 € – -1,00 €"}, + {start: -1, end: -2, result: "-1,00–2,00 €"}, + {start: "10e2", end: "1e-3", result: "1.000,00–0,00 €"}, + {start: "0x100", end: "1e1", result: "256,00–10,00 €"}, + {start: ".1e-999999", end: ".01e-999999", result: "≈0,00 €"}, + {start: ".1e99999", end: "0", result: "∞–0,00 €"}, + // Number.MAX_VALUE is 1.7976931348623157e+308. + { + start: "1.7976931348623158e+308", + end: Number.MAX_VALUE, + result: "179.769.313.486.231.580" + ".000".repeat(97) + ",00–" + de_Number_MAX_VALUE + ",00 €", + }, + // Number.MIN_VALUE is 5e-324. + {start: "6e-324", end: Number.MIN_VALUE, result: "≈0,00 €"}, + ], + }, + "fr": { + options: {style: "unit", unit: "meter"}, + ranges: [ + // Values around zero. + {start: 0, end: 0, result: "≃0 m"}, + {start: -0, end: 0, result: "-0 – 0 m"}, + {start: -0, end: 0, result: "-0 – 0 m"}, + {start: -0, end: 0.1e-3, result: "-0 – 0 m"}, + {start: -0, end: "0.1e-3", result: "-0 – 0 m"}, + {start: "-0", end: 0.1e-3, result: "-0 – 0 m"}, + {start: -0, end: -0, result: "≃-0 m"}, + {start: -0, end: -0.1, result: "-0 – -0,1 m"}, + + // Values starting at negative infinity. + {start: -Infinity, end: -Infinity, result: "≃-∞ m"}, + {start: -Infinity, end: -0, result: "-∞ – -0 m"}, + {start: -Infinity, end: +0, result: "-∞ – 0 m"}, + {start: -Infinity, end: +Infinity, result: "-∞ – ∞ m"}, + + // Values ending at negative infinity. + {start: -Number.MAX_VALUE, end: -Infinity, result: "-" + fr_Number_MAX_VALUE + " – -∞ m"}, + {start: -0, end: -Infinity, result: "-0 – -∞ m"}, + {start: 0, end: -Infinity, result: "0–-∞ m"}, + {start: Number.MAX_VALUE, end: -Infinity, result: fr_Number_MAX_VALUE + "–-∞ m"}, + + // Values starting at positive infinity. + {start: Infinity, end: Number.MAX_VALUE, result: "∞–" + fr_Number_MAX_VALUE + " m"}, + {start: Infinity, end: 0, result: "∞–0 m"}, + {start: Infinity, end: -0, result: "∞–-0 m"}, + {start: Infinity, end: -Number.MAX_VALUE, result: "∞–-" + fr_Number_MAX_VALUE + " m"}, + {start: Infinity, end: -Infinity, result: "∞–-∞ m"}, + + // Values ending at positive infinity. + {start: Infinity, end: Infinity, result: "≃∞ m"}, + + // Non-special cases. + {start: 1, end: 100, result: "1–100 m"}, + {start: -100, end: 100, result: "-100 – 100 m"}, + {start: -1000, end: -100, result: "-1 000 – -100 m"}, + {start: Math.PI, end: 123_456.789, result: "3,142–123 456,789 m"}, + {start: -Math.PI, end: Math.E, result: "-3,142 – 2,718 m"}, + { + start: Number.MAX_SAFE_INTEGER, + end: 9007199254740993, + result: "9 007 199 254 740 991–9 007 199 254 740 992 m", + }, + { + start: Number.MAX_SAFE_INTEGER, + end: "9007199254740993", + result: "9 007 199 254 740 991–9 007 199 254 740 993 m", + }, + + // Start value is larger than end value. + {start: -0, end: -0.1, result: "-0 – -0,1 m"}, + {start: -0, end: -Number.MAX_VALUE, result: "-0 – -" + fr_Number_MAX_VALUE + " m"}, + {start: 1, end: 0, result: "1–0 m"}, + {start: 0, end: -1, result: "0–-1 m"}, + {start: 1, end: -1, result: "1–-1 m"}, + {start: -1, end: -2, result: "-1 – -2 m"}, + {start: "10e2", end: "1e-3", result: "1 000–0,001 m"}, + {start: "0x100", end: "1e1", result: "256–10 m"}, + {start: ".1e-999999", end: ".01e-999999", result: "≃0 m"}, + {start: ".1e99999", end: "0", result: "∞–0 m"}, + // Number.MAX_VALUE is 1.7976931348623157e+308. + { + start: "1.7976931348623158e+308", + end: Number.MAX_VALUE, + result: "179 769 313 486 231 580" + " 000".repeat(97) + "–" + fr_Number_MAX_VALUE + " m", + }, + // Number.MIN_VALUE is 5e-324. + {start: "6e-324", end: Number.MIN_VALUE, result: "≃0 m"}, + ], + }, + // Non-ASCII digits. + "ar": { + options: {}, + ranges: [ + {start: -2, end: -1, result: "-٢–١"}, + {start: -1, end: -1, result: "~-١"}, + {start: -1, end: 0, result: "-١ – ٠"}, + {start: 0, end: 0, result: "~٠"}, + {start: 0, end: 1, result: "٠–١"}, + {start: 1, end: 1, result: "~١"}, + {start: 1, end: 2, result: "١–٢"}, + ], + }, + "th-u-nu-thai": { + options: {}, + ranges: [ + {start: -2, end: -1, result: "-๒ - -๑"}, + {start: -1, end: -1, result: "~-๑"}, + {start: -1, end: 0, result: "-๑ - ๐"}, + {start: 0, end: 0, result: "~๐"}, + {start: 0, end: 1, result: "๐-๑"}, + {start: 1, end: 1, result: "~๑"}, + {start: 1, end: 2, result: "๑-๒"}, + ], + }, + // Approximation sign may consist of multiple characters. + "no": { + options: {}, + ranges: [ + {start: 1, end: 1, result: "ca.1"}, + ], + }, + // Approximation sign can be a word. + "ja": { + options: {}, + ranges: [ + {start: 1, end: 1, result: "約1"}, + ], + }, +}; + +for (let [locale, {options, ranges}] of Object.entries(tests)) { + let nf = new Intl.NumberFormat(locale, options); + for (let {start, end, result} of ranges) { + assertEq(nf.formatRange(start, end), result, `${start}-${end}`); + assertEq(nf.formatRangeToParts(start, end).reduce((acc, part) => acc + part.value, ""), + result, `${start}-${end}`); + } +} + +// Throws an error if either value is NaN. +{ + const errorTests = [ + {start: NaN, end: NaN}, + {start: 0, end: NaN}, + {start: NaN, end: 0}, + {start: Infinity, end: NaN}, + {start: NaN, end: Infinity}, + ]; + + let nf = new Intl.NumberFormat("en"); + for (let {start, end} of errorTests) { + assertThrowsInstanceOf(() => nf.formatRange(start, end), RangeError); + assertThrowsInstanceOf(() => nf.formatRangeToParts(start, end), RangeError); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-compact.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-compact.js new file mode 100644 index 0000000000..dea7c40642 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-compact.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +const numbers = [ + 0, 1, 2, 5, 10, 100, 1000, 10_000, 100_000, 1_000_000, + 0.1, 0.2, 0.5, 1.5, + -0, -1, -2, -5, + Infinity, -Infinity, +]; + +const options = {notation: "compact"}; + +// List of known approximately sign in CLDR 40. +const approximatelySigns = [ + "~", "∼", "≈", "≃", "ca.", "約", "dáàṣì", "dáàshì", +]; + +// Iterate over all locales and ensure we find exactly one approximately sign. +for (let locale of getAvailableLocalesOf("NumberFormat").sort()) { + let nf = new Intl.NumberFormat(locale, options); + for (let number of numbers) { + let parts = nf.formatRangeToParts(number, number); + let approx = parts.filter(part => part.type === "approximatelySign"); + + assertEq(approx.length, 1); + assertEq(approximatelySigns.some(approxSign => approx[0].value.includes(approxSign)), true); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-currency.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-currency.js new file mode 100644 index 0000000000..4855b9ca00 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-currency.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +const numbers = [ + 0, 1, 2, 5, 10, 100, 1000, 10_000, 100_000, 1_000_000, + 0.1, 0.2, 0.5, 1.5, + -0, -1, -2, -5, + Infinity, -Infinity, +]; + +const options = {style: "currency", currency: "EUR"}; + +// List of known approximately sign in CLDR 40. +const approximatelySigns = [ + "~", "∼", "≈", "≃", "ca.", "約", "dáàṣì", "dáàshì", +]; + +// Iterate over all locales and ensure we find exactly one approximately sign. +for (let locale of getAvailableLocalesOf("NumberFormat").sort()) { + let nf = new Intl.NumberFormat(locale, options); + for (let number of numbers) { + let parts = nf.formatRangeToParts(number, number); + let approx = parts.filter(part => part.type === "approximatelySign"); + + assertEq(approx.length, 1); + assertEq(approximatelySigns.some(approxSign => approx[0].value.includes(approxSign)), true); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-percent.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-percent.js new file mode 100644 index 0000000000..abdd3de083 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-percent.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +const numbers = [ + 0, 1, 2, 5, 10, 100, 1000, 10_000, 100_000, 1_000_000, + 0.1, 0.2, 0.5, 1.5, + -0, -1, -2, -5, + Infinity, -Infinity, +]; + +const options = {style: "percent"}; + +// List of known approximately sign in CLDR 40. +const approximatelySigns = [ + "~", "∼", "≈", "≃", "ca.", "約", "dáàṣì", "dáàshì", +]; + +// Iterate over all locales and ensure we find exactly one approximately sign. +for (let locale of getAvailableLocalesOf("NumberFormat").sort()) { + let nf = new Intl.NumberFormat(locale, options); + for (let number of numbers) { + let parts = nf.formatRangeToParts(number, number); + let approx = parts.filter(part => part.type === "approximatelySign"); + + assertEq(approx.length, 1); + assertEq(approximatelySigns.some(approxSign => approx[0].value.includes(approxSign)), true); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-signDisplay.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-signDisplay.js new file mode 100644 index 0000000000..92d8251f6a --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-signDisplay.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +const numbers = [ + 0, 1, 2, 5, 10, 100, 1000, 10_000, 100_000, 1_000_000, + 0.1, 0.2, 0.5, 1.5, + -0, -1, -2, -5, + Infinity, -Infinity, +]; + +const options = {signDisplay: "always"}; + +// List of known approximately sign in CLDR 40. +const approximatelySigns = [ + "~", "∼", "≈", "≃", "ca.", "約", "dáàṣì", "dáàshì", +]; + +// Iterate over all locales and ensure we find exactly one approximately sign. +for (let locale of getAvailableLocalesOf("NumberFormat").sort()) { + let nf = new Intl.NumberFormat(locale, options); + for (let number of numbers) { + let parts = nf.formatRangeToParts(number, number); + let approx = parts.filter(part => part.type === "approximatelySign"); + + assertEq(approx.length, 1); + assertEq(approximatelySigns.some(approxSign => approx[0].value.includes(approxSign)), true); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-unit.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-unit.js new file mode 100644 index 0000000000..c66dbfb462 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign-unit.js @@ -0,0 +1,41 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +const numbers = [ + 0, 1, 2, 5, 10, 100, 1000, 10_000, 100_000, 1_000_000, + 0.1, 0.2, 0.5, 1.5, + -0, -1, -2, -5, + Infinity, -Infinity, +]; + +const options = {style: "unit", unit: "meter"}; + +// List of known approximately sign in CLDR 40. +const approximatelySigns = [ + "~", "∼", "≈", "≃", "ca.", "約", "dáàṣì", "dáàshì", +]; + +// Iterate over all locales and ensure we find exactly one approximately sign. +for (let locale of getAvailableLocalesOf("NumberFormat").sort()) { + let nf = new Intl.NumberFormat(locale, options); + for (let number of numbers) { + let parts = nf.formatRangeToParts(number, number); + let approx = parts.filter(part => part.type === "approximatelySign"); + + // Known failure case. + // - https://github.com/tc39/proposal-intl-numberformat-v3/issues/64 + // - https://unicode-org.atlassian.net/browse/CLDR-14918 + if (approx.length === 0 && new Intl.Locale(locale).language === "ar") { + continue; + } + + assertEq(approx.length, 1); + assertEq(approximatelySigns.some(approxSign => approx[0].value.includes(approxSign)), true); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign.js new file mode 100644 index 0000000000..cab6e5c3d5 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts-approximately-sign.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +const numbers = [ + 0, 1, 2, 5, 10, 100, 1000, 10_000, 100_000, 1_000_000, + 0.1, 0.2, 0.5, 1.5, + -0, -1, -2, -5, + Infinity, -Infinity, +]; + +const options = {}; + +// List of known approximately sign in CLDR 40. +const approximatelySigns = [ + "~", "∼", "≈", "≃", "ca.", "約", "dáàṣì", "dáàshì", +]; + +// Iterate over all locales and ensure we find exactly one approximately sign. +for (let locale of getAvailableLocalesOf("NumberFormat").sort()) { + let nf = new Intl.NumberFormat(locale, options); + for (let number of numbers) { + let parts = nf.formatRangeToParts(number, number); + let approx = parts.filter(part => part.type === "approximatelySign"); + + assertEq(approx.length, 1); + assertEq(approximatelySigns.some(approxSign => approx[0].value.includes(approxSign)), true); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts.js b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts.js new file mode 100644 index 0000000000..fba5388973 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatRangeToParts.js @@ -0,0 +1,174 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const Start = NumberRangeFormatParts("startRange"); +const End = NumberRangeFormatParts("endRange"); +const Shared = NumberRangeFormatParts("shared"); + +const tests = { + "en": { + options: {}, + ranges: [ + // Approximation sign present. + { + start: 0, + end: 0, + result: [Shared.Approx("~"), Shared.Integer("0")], + }, + { + start: -0, + end: -0, + result: [Shared.Approx("~"), Shared.MinusSign("-"), Shared.Integer("0")], + }, + { + start: -1, + end: -1, + result: [Shared.Approx("~"), Shared.MinusSign("-"), Shared.Integer("1")], + }, + { + start: 0.5, + end: 0.5, + result: [Shared.Approx("~"), Shared.Integer("0"), Shared.Decimal("."), Shared.Fraction("5")], + }, + { + start: Infinity, + end: Infinity, + result: [Shared.Approx("~"), Shared.Inf("∞")], + }, + { + start: -Infinity, + end: -Infinity, + result: [Shared.Approx("~"), Shared.MinusSign("-"), Shared.Inf("∞")], + }, + + // Proper ranges. + { + start: -2, + end: -1, + result: [Start.MinusSign("-"), Start.Integer("2"), Shared.Literal(" – "), End.MinusSign("-"), End.Integer("1")], + }, + { + start: -1, + end: 1, + result: [Start.MinusSign("-"), Start.Integer("1"), Shared.Literal(" – "), End.Integer("1")], + }, + { + start: 1, + end: 2, + result: [Start.Integer("1"), Shared.Literal("–"), End.Integer("2")], + }, + ], + }, + // Non-ASCII digits. + "ar": { + options: {}, + ranges: [ + { + start: -2, + end: -1, + result: [Shared.Literal("\u061C"), Shared.MinusSign("-"), Start.Integer("٢"), Shared.Literal("–"), End.Integer("١")], + }, + { + start: -1, + end: -1, + result: [Shared.Approx("~"), Shared.Literal("\u061C"), Shared.MinusSign("-"), Shared.Integer("١")], + }, + { + start: -1, + end: 0, + result: [Start.Literal("\u061C"), Start.MinusSign("-"), Start.Integer("١"), Shared.Literal(" – "), End.Integer("٠")], + }, + { + start: 0, + end: 0, + result: [Shared.Approx("~"), Shared.Integer("٠")], + }, + { + start: 0, + end: 1, + result: [Start.Integer("٠"), Shared.Literal("–"), End.Integer("١")], + }, + { + start: 1, + end: 1, + result: [Shared.Approx("~"), Shared.Integer("١")], + }, + { + start: 1, + end: 2, + result: [Start.Integer("١"), Shared.Literal("–"), End.Integer("٢")], + }, + ], + }, + "th-u-nu-thai": { + options: {}, + ranges: [ + { + start: -2, + end: -1, + result: [Start.MinusSign("-"), Start.Integer("๒"), Shared.Literal(" - "), End.MinusSign("-"), End.Integer("๑")], + }, + { + start: -1, + end: -1, + result: [Shared.Approx("~"), Shared.MinusSign("-"), Shared.Integer("๑")], + }, + { + start: -1, + end: 0, + result: [Start.MinusSign("-"), Start.Integer("๑"), Shared.Literal(" - "), End.Integer("๐")], + }, + { + start: 0, + end: 0, + result: [Shared.Approx("~"), Shared.Integer("๐")], + }, + { + start: 0, + end: 1, + result: [Start.Integer("๐"), Shared.Literal("-"), End.Integer("๑")], + }, + { + start: 1, + end: 1, + result: [Shared.Approx("~"), Shared.Integer("๑")], + }, + { + start: 1, + end: 2, + result: [Start.Integer("๑"), Shared.Literal("-"), End.Integer("๒")], + }, + ], + }, + // Approximation sign may consist of multiple characters. + "no": { + options: {}, + ranges: [ + { + start: 1, + end: 1, + result: [Shared.Approx("ca."), Shared.Integer("1")], + }, + ], + }, + // Approximation sign can be a word. + "ja": { + options: {}, + ranges: [ + { + start: 1, + end: 1, + result: [Shared.Approx("約"), Shared.Integer("1")], + }, + ], + }, +}; + +for (let [locale, {options, ranges}] of Object.entries(tests)) { + let nf = new Intl.NumberFormat(locale, options); + for (let {start, end, result} of ranges) { + assertRangeParts(nf, start, end, result); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatToParts.js b/js/src/tests/non262/Intl/NumberFormat/formatToParts.js new file mode 100644 index 0000000000..3ac7c3f4d3 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatToParts.js @@ -0,0 +1,357 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1289882; +var summary = "Implement Intl.NumberFormat.prototype.formatToParts"; + +print(BUGNUMBER + ": " + summary); + +//----------------------------------------------------------------------------- + +assertEq("formatToParts" in Intl.NumberFormat(), true); + +// NOTE: Some of these tests exercise standard behavior (e.g. that format and +// formatToParts expose the same formatted string). But much of this, +// like the exact-formatted-string expectations, is technically +// implementation-dependent. This is necessary as a practical matter to +// properly test the conversion from ICU's nested-field exposure to +// ECMA-402's sequential-parts exposure. + +var { + Nan, Inf, Integer, Group, Decimal, Fraction, + MinusSign, PlusSign, PercentSign, Currency, Literal, +} = NumberFormatParts; + +//----------------------------------------------------------------------------- + +// Test -0's partitioning now that it's not treated like +0. +// https://github.com/tc39/ecma402/pull/232 + +var deadSimpleFormatter = new Intl.NumberFormat("en-US"); + +assertParts(deadSimpleFormatter, -0, + [MinusSign("-"), Integer("0")]); + +// Test behavior of a currency with code formatting. +var usdCodeOptions = + { + style: "currency", + currency: "USD", + currencyDisplay: "code", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var usDollarsCode = new Intl.NumberFormat("en-US", usdCodeOptions); + +assertParts(usDollarsCode, 25, + [Currency("USD"), Literal("\xA0"), Integer("25")]); + +// ISO 4217 currency codes are formed from an ISO 3166-1 alpha-2 country code +// followed by a third letter. ISO 3166 guarantees that no country code +// starting with "X" will ever be assigned. Stepping carefully around a few +// 4217-designated special "currencies", XQQ will never have a representation. +// Thus, yes: this really is specified to work, as unrecognized or unsupported +// codes pass into the string unmodified. +var xqqCodeOptions = + { + style: "currency", + currency: "XQQ", + currencyDisplay: "code", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var xqqMoneyCode = new Intl.NumberFormat("en-US", xqqCodeOptions); + +assertParts(xqqMoneyCode, 25, + [Currency("XQQ"), Literal("\xA0"), Integer("25")]); + +// Test currencyDisplay: "name". +var usdNameOptions = + { + style: "currency", + currency: "USD", + currencyDisplay: "name", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var usDollarsName = new Intl.NumberFormat("en-US", usdNameOptions); + +assertParts(usDollarsName, 25, + [Integer("25"), Literal(" "), Currency("US dollars")]); + +var usdNameGroupingOptions = + { + style: "currency", + currency: "USD", + currencyDisplay: "name", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var usDollarsNameGrouping = + new Intl.NumberFormat("en-US", usdNameGroupingOptions); + +assertParts(usDollarsNameGrouping, 12345678, + [Integer("12"), + Group(","), + Integer("345"), + Group(","), + Integer("678"), + Literal(" "), + Currency("US dollars")]); + +// But if the implementation doesn't recognize the currency, the provided code +// is used in place of a proper name, unmolested. +var xqqNameOptions = + { + style: "currency", + currency: "XQQ", + currencyDisplay: "name", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; +var xqqMoneyName = new Intl.NumberFormat("en-US", xqqNameOptions); + +assertParts(xqqMoneyName, 25, + [Integer("25"), Literal(" "), Currency("XQQ")]); + +// Test some currencies with fractional components. + +var usdNameFractionOptions = + { + style: "currency", + currency: "USD", + currencyDisplay: "name", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }; +var usdNameFractionFormatter = + new Intl.NumberFormat("en-US", usdNameFractionOptions); + +// The US national surplus (i.e. debt) as of October 18, 2016. (Replicating +// data from a comment in builtin/Intl/NumberFormat.cpp.) +var usNationalSurplus = -19766580028249.41; + +assertParts(usdNameFractionFormatter, usNationalSurplus, + [MinusSign("-"), + Integer("19"), + Group(","), + Integer("766"), + Group(","), + Integer("580"), + Group(","), + Integer("028"), + Group(","), + Integer("249"), + Decimal("."), + Fraction("41"), + Literal(" "), + Currency("US dollars")]); + +// Percents in various forms. + +var usPercentOptions = + { + style: "percent", + minimumFractionDigits: 1, + maximumFractionDigits: 1, + }; +var usPercentFormatter = + new Intl.NumberFormat("en-US", usPercentOptions); + +assertParts(usPercentFormatter, 0.375, + [Integer("37"), Decimal("."), Fraction("5"), PercentSign("%")]); + +assertParts(usPercentFormatter, -1284.375, + [MinusSign("-"), + Integer("128"), + Group(","), + Integer("437"), + Decimal("."), + Fraction("5"), + PercentSign("%")]); + +assertParts(usPercentFormatter, NaN, + [Nan("NaN"), PercentSign("%")]); + +assertParts(usPercentFormatter, Infinity, + [Inf("∞"), PercentSign("%")]); + +assertParts(usPercentFormatter, -Infinity, + [MinusSign("-"), Inf("∞"), PercentSign("%")]); + +var dePercentOptions = + { + style: "percent", + minimumFractionDigits: 1, + maximumFractionDigits: 1, + }; +var dePercentFormatter = + new Intl.NumberFormat("de", dePercentOptions); + +assertParts(dePercentFormatter, 0.375, + [Integer("37"), Decimal(","), Fraction("5"), Literal("\xA0"), PercentSign("%")]); + +assertParts(dePercentFormatter, -1284.375, + [MinusSign("-"), + Integer("128"), + Group("."), + Integer("437"), + Decimal(","), + Fraction("5"), + Literal("\xA0"), + PercentSign("%")]); + +assertParts(dePercentFormatter, NaN, + [Nan("NaN"), Literal("\xA0"), PercentSign("%")]); + +assertParts(dePercentFormatter, Infinity, + [Inf("∞"), Literal("\xA0"), PercentSign("%")]); + +assertParts(dePercentFormatter, -Infinity, + [MinusSign("-"), Inf("∞"), Literal("\xA0"), PercentSign("%")]); + +var arPercentOptions = + { + style: "percent", + minimumFractionDigits: 2, + }; +var arPercentFormatter = + new Intl.NumberFormat("ar-IQ", arPercentOptions); + +assertParts(arPercentFormatter, -135.32, + [Literal("\u{061C}"), + MinusSign("-"), + Integer("١٣"), + Group("٬"), + Integer("٥٣٢"), + Decimal("٫"), + Fraction("٠٠"), + PercentSign("٪"), + Literal("\u{061C}")]); + +// Decimals. + +var usDecimalOptions = + { + style: "decimal", + maximumFractionDigits: 7 // minimum defaults to 0 + }; +var usDecimalFormatter = + new Intl.NumberFormat("en-US", usDecimalOptions); + +assertParts(usDecimalFormatter, 42, + [Integer("42")]); + +assertParts(usDecimalFormatter, 1337, + [Integer("1"), Group(","), Integer("337")]); + +assertParts(usDecimalFormatter, -6.25, + [MinusSign("-"), Integer("6"), Decimal("."), Fraction("25")]); + +assertParts(usDecimalFormatter, -1376.25, + [MinusSign("-"), + Integer("1"), + Group(","), + Integer("376"), + Decimal("."), + Fraction("25")]); + +assertParts(usDecimalFormatter, 124816.8359375, + [Integer("124"), + Group(","), + Integer("816"), + Decimal("."), + Fraction("8359375")]); + +var usNoGroupingDecimalOptions = + { + style: "decimal", + useGrouping: false, + maximumFractionDigits: 7 // minimum defaults to 0 + }; +var usNoGroupingDecimalFormatter = + new Intl.NumberFormat("en-US", usNoGroupingDecimalOptions); + +assertParts(usNoGroupingDecimalFormatter, 1337, + [Integer("1337")]); + +assertParts(usNoGroupingDecimalFormatter, -6.25, + [MinusSign("-"), Integer("6"), Decimal("."), Fraction("25")]); + +assertParts(usNoGroupingDecimalFormatter, -1376.25, + [MinusSign("-"), + Integer("1376"), + Decimal("."), + Fraction("25")]); + +assertParts(usNoGroupingDecimalFormatter, 124816.8359375, + [Integer("124816"), + Decimal("."), + Fraction("8359375")]); + +var deDecimalOptions = + { + style: "decimal", + maximumFractionDigits: 7 // minimum defaults to 0 + }; +var deDecimalFormatter = + new Intl.NumberFormat("de-DE", deDecimalOptions); + +assertParts(deDecimalFormatter, 42, + [Integer("42")]); + +assertParts(deDecimalFormatter, 1337, + [Integer("1"), Group("."), Integer("337")]); + +assertParts(deDecimalFormatter, -6.25, + [MinusSign("-"), Integer("6"), Decimal(","), Fraction("25")]); + +assertParts(deDecimalFormatter, -1376.25, + [MinusSign("-"), + Integer("1"), + Group("."), + Integer("376"), + Decimal(","), + Fraction("25")]); + +assertParts(deDecimalFormatter, 124816.8359375, + [Integer("124"), + Group("."), + Integer("816"), + Decimal(","), + Fraction("8359375")]); + +var deNoGroupingDecimalOptions = + { + style: "decimal", + useGrouping: false, + maximumFractionDigits: 7 // minimum defaults to 0 + }; +var deNoGroupingDecimalFormatter = + new Intl.NumberFormat("de-DE", deNoGroupingDecimalOptions); + +assertParts(deNoGroupingDecimalFormatter, 1337, + [Integer("1337")]); + +assertParts(deNoGroupingDecimalFormatter, -6.25, + [MinusSign("-"), Integer("6"), Decimal(","), Fraction("25")]); + +assertParts(deNoGroupingDecimalFormatter, -1376.25, + [MinusSign("-"), + Integer("1376"), + Decimal(","), + Fraction("25")]); + +assertParts(deNoGroupingDecimalFormatter, 124816.8359375, + [Integer("124816"), + Decimal(","), + Fraction("8359375")]); + +//----------------------------------------------------------------------------- + +if (typeof reportCompare === "function") + reportCompare(0, 0, 'ok'); + +print("Tests complete"); diff --git a/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js b/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js new file mode 100644 index 0000000000..430d74222e --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js @@ -0,0 +1,35 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1484943; +var summary = "Don't crash doing format/formatToParts on -NaN"; + +print(BUGNUMBER + ": " + summary); + +//----------------------------------------------------------------------------- + +assertEq("formatToParts" in Intl.NumberFormat(), true); + +var nf = new Intl.NumberFormat("en-US"); +var parts; + +var values = [NaN, -NaN]; + +for (var v of values) +{ + assertEq(nf.format(v), "NaN"); + + parts = nf.formatToParts(v); + assertEq(parts.length, 1); + assertEq(parts[0].type, "nan"); + assertEq(parts[0].value, "NaN"); +} + +//----------------------------------------------------------------------------- + +if (typeof reportCompare === "function") + reportCompare(0, 0, 'ok'); + +print("Tests complete"); diff --git a/js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js b/js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js new file mode 100644 index 0000000000..0db461f50e --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/negativeZeroFractionDigits.js @@ -0,0 +1,21 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const optionsList = [ + {minimumFractionDigits: -0, maximumFractionDigits: -0}, + {minimumFractionDigits: -0, maximumFractionDigits: +0}, + {minimumFractionDigits: +0, maximumFractionDigits: -0}, + {minimumFractionDigits: +0, maximumFractionDigits: +0}, +]; + +for (let options of optionsList) { + let numberFormat = new Intl.NumberFormat("en-US", options); + + let {minimumFractionDigits, maximumFractionDigits} = numberFormat.resolvedOptions(); + assertEq(minimumFractionDigits, +0); + assertEq(maximumFractionDigits, +0); + + assertEq(numberFormat.format(123), "123"); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js b/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js new file mode 100644 index 0000000000..80c3416afb --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js @@ -0,0 +1,139 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group, Literal, + Compact, +} = NumberFormatParts; + +const testcases = [ + { + locale: "en", + options: { + notation: "compact", + compactDisplay: "long", + }, + values: [ + {value: +0, string: "0", parts: [Integer("0")]}, + {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]}, + {value: 0n, string: "0", parts: [Integer("0")]}, + + {value: 1, string: "1", parts: [Integer("1")]}, + {value: 10, string: "10", parts: [Integer("10")]}, + {value: 100, string: "100", parts: [Integer("100")]}, + {value: 1000, string: "1 thousand", parts: [Integer("1"), Literal(" "), Compact("thousand")]}, + {value: 10000, string: "10 thousand", parts: [Integer("10"), Literal(" "), Compact("thousand")]}, + {value: 100000, string: "100 thousand", parts: [Integer("100"), Literal(" "), Compact("thousand")]}, + {value: 1000000, string: "1 million", parts: [Integer("1"), Literal(" "), Compact("million")]}, + {value: 10000000, string: "10 million", parts: [Integer("10"), Literal(" "), Compact("million")]}, + {value: 100000000, string: "100 million", parts: [Integer("100"), Literal(" "), Compact("million")]}, + {value: 1000000000, string: "1 billion", parts: [Integer("1"), Literal(" "), Compact("billion")]}, + {value: 10000000000, string: "10 billion", parts: [Integer("10"), Literal(" "), Compact("billion")]}, + {value: 100000000000, string: "100 billion", parts: [Integer("100"), Literal(" "), Compact("billion")]}, + {value: 1000000000000, string: "1 trillion", parts: [Integer("1"), Literal(" "), Compact("trillion")]}, + {value: 10000000000000, string: "10 trillion", parts: [Integer("10"), Literal(" "), Compact("trillion")]}, + {value: 100000000000000, string: "100 trillion", parts: [Integer("100"), Literal(" "), Compact("trillion")]}, + {value: 1000000000000000, string: "1000 trillion", parts: [Integer("1000"), Literal(" "), Compact("trillion")]}, + {value: 10000000000000000, string: "10,000 trillion", parts: [Integer("10"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]}, + {value: 100000000000000000, string: "100,000 trillion", parts: [Integer("100"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]}, + + {value: 1n, string: "1", parts: [Integer("1")]}, + {value: 10n, string: "10", parts: [Integer("10")]}, + {value: 100n, string: "100", parts: [Integer("100")]}, + {value: 1000n, string: "1 thousand", parts: [Integer("1"), Literal(" "), Compact("thousand")]}, + {value: 10000n, string: "10 thousand", parts: [Integer("10"), Literal(" "), Compact("thousand")]}, + {value: 100000n, string: "100 thousand", parts: [Integer("100"), Literal(" "), Compact("thousand")]}, + {value: 1000000n, string: "1 million", parts: [Integer("1"), Literal(" "), Compact("million")]}, + {value: 10000000n, string: "10 million", parts: [Integer("10"), Literal(" "), Compact("million")]}, + {value: 100000000n, string: "100 million", parts: [Integer("100"), Literal(" "), Compact("million")]}, + {value: 1000000000n, string: "1 billion", parts: [Integer("1"), Literal(" "), Compact("billion")]}, + {value: 10000000000n, string: "10 billion", parts: [Integer("10"), Literal(" "), Compact("billion")]}, + {value: 100000000000n, string: "100 billion", parts: [Integer("100"), Literal(" "), Compact("billion")]}, + {value: 1000000000000n, string: "1 trillion", parts: [Integer("1"), Literal(" "), Compact("trillion")]}, + {value: 10000000000000n, string: "10 trillion", parts: [Integer("10"), Literal(" "), Compact("trillion")]}, + {value: 100000000000000n, string: "100 trillion", parts: [Integer("100"), Literal(" "), Compact("trillion")]}, + {value: 1000000000000000n, string: "1000 trillion", parts: [Integer("1000"), Literal(" "), Compact("trillion")]}, + {value: 10000000000000000n, string: "10,000 trillion", parts: [Integer("10"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]}, + {value: 100000000000000000n, string: "100,000 trillion", parts: [Integer("100"), Group(","), Integer("000"), Literal(" "), Compact("trillion")]}, + + {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]}, + {value: 0.01, string: "0.01", parts: [Integer("0"), Decimal("."), Fraction("01")]}, + {value: 0.001, string: "0.001", parts: [Integer("0"), Decimal("."), Fraction("001")]}, + {value: 0.0001, string: "0.0001", parts: [Integer("0"), Decimal("."), Fraction("0001")]}, + {value: 0.00001, string: "0.00001", parts: [Integer("0"), Decimal("."), Fraction("00001")]}, + {value: 0.000001, string: "0.000001", parts: [Integer("0"), Decimal("."), Fraction("000001")]}, + {value: 0.0000001, string: "0.0000001", parts: [Integer("0"), Decimal("."), Fraction("0000001")]}, + + {value: 12, string: "12", parts: [Integer("12")]}, + {value: 123, string: "123", parts: [Integer("123")]}, + {value: 1234, string: "1.2 thousand", parts: [Integer("1"), Decimal("."), Fraction("2"), Literal(" "), Compact("thousand")]}, + {value: 12345, string: "12 thousand", parts: [Integer("12"), Literal(" "), Compact("thousand")]}, + {value: 123456, string: "123 thousand", parts: [Integer("123"), Literal(" "), Compact("thousand")]}, + {value: 1234567, string: "1.2 million", parts: [Integer("1"), Decimal("."), Fraction("2"), Literal(" "), Compact("million")]}, + {value: 12345678, string: "12 million", parts: [Integer("12"), Literal(" "), Compact("million")]}, + {value: 123456789, string: "123 million", parts: [Integer("123"), Literal(" "), Compact("million")]}, + + {value: Infinity, string: "∞", parts: [Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // The "{compactName}" placeholder may have different plural forms. + { + locale: "de", + options: { + notation: "compact", + compactDisplay: "long", + }, + values: [ + {value: 1e6, string: "1 Million", parts: [Integer("1"), Literal(" "), Compact("Million")]}, + {value: 2e6, string: "2 Millionen", parts: [Integer("2"), Literal(" "), Compact("Millionen")]}, + {value: 1e9, string: "1 Milliarde", parts: [Integer("1"), Literal(" "), Compact("Milliarde")]}, + {value: 2e9, string: "2 Milliarden", parts: [Integer("2"), Literal(" "), Compact("Milliarden")]}, + ], + }, + + // Digits are grouped in myriads (every 10,000) in Japanese. + { + locale: "ja", + options: { + notation: "compact", + compactDisplay: "long", + }, + values: [ + {value: 1, string: "1", parts: [Integer("1")]}, + {value: 10, string: "10", parts: [Integer("10")]}, + {value: 100, string: "100", parts: [Integer("100")]}, + {value: 1000, string: "1000", parts: [Integer("1000")]}, + + {value: 10e3, string: "1\u4E07", parts: [Integer("1"), Compact("\u4E07")]}, + {value: 100e3, string: "10\u4E07", parts: [Integer("10"), Compact("\u4E07")]}, + {value: 1000e3, string: "100\u4E07", parts: [Integer("100"), Compact("\u4E07")]}, + {value: 10000e3, string: "1000\u4E07", parts: [Integer("1000"), Compact("\u4E07")]}, + + {value: 10e7, string: "1\u5104", parts: [Integer("1"), Compact("\u5104")]}, + {value: 100e7, string: "10\u5104", parts: [Integer("10"), Compact("\u5104")]}, + {value: 1000e7, string: "100\u5104", parts: [Integer("100"), Compact("\u5104")]}, + {value: 10000e7, string: "1000\u5104", parts: [Integer("1000"), Compact("\u5104")]}, + + {value: 10e11, string: "1\u5146", parts: [Integer("1"), Compact("\u5146")]}, + {value: 100e11, string: "10\u5146", parts: [Integer("10"), Compact("\u5146")]}, + {value: 1000e11, string: "100\u5146", parts: [Integer("100"), Compact("\u5146")]}, + {value: 10000e11, string: "1000\u5146", parts: [Integer("1000"), Compact("\u5146")]}, + + {value: 10e15, string: "1\u4EAC", parts: [Integer("1"), Compact("\u4EAC")]}, + {value: 100e15, string: "10\u4EAC", parts: [Integer("10"), Compact("\u4EAC")]}, + {value: 1000e15, string: "100\u4EAC", parts: [Integer("100"), Compact("\u4EAC")]}, + {value: 10000e15, string: "1000\u4EAC", parts: [Integer("1000"), Compact("\u4EAC")]}, + + {value: 100000e15, string: "10,000\u4EAC", parts: [Integer("10"), Group(","), Integer("000"), Compact("\u4EAC")]}, + ], + }, +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js b/js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js new file mode 100644 index 0000000000..f3a546294d --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/notation-compact-short.js @@ -0,0 +1,136 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group, Literal, + Compact, +} = NumberFormatParts; + +const testcases = [ + { + locale: "en", + options: { + notation: "compact", + compactDisplay: "short", + }, + values: [ + {value: +0, string: "0", parts: [Integer("0")]}, + {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]}, + {value: 0n, string: "0", parts: [Integer("0")]}, + + {value: 1, string: "1", parts: [Integer("1")]}, + {value: 10, string: "10", parts: [Integer("10")]}, + {value: 100, string: "100", parts: [Integer("100")]}, + {value: 1000, string: "1K", parts: [Integer("1"), Compact("K")]}, + {value: 10000, string: "10K", parts: [Integer("10"), Compact("K")]}, + {value: 100000, string: "100K", parts: [Integer("100"), Compact("K")]}, + {value: 1000000, string: "1M", parts: [Integer("1"), Compact("M")]}, + {value: 10000000, string: "10M", parts: [Integer("10"), Compact("M")]}, + {value: 100000000, string: "100M", parts: [Integer("100"), Compact("M")]}, + {value: 1000000000, string: "1B", parts: [Integer("1"), Compact("B")]}, + {value: 10000000000, string: "10B", parts: [Integer("10"), Compact("B")]}, + {value: 100000000000, string: "100B", parts: [Integer("100"), Compact("B")]}, + {value: 1000000000000, string: "1T", parts: [Integer("1"), Compact("T")]}, + {value: 10000000000000, string: "10T", parts: [Integer("10"), Compact("T")]}, + {value: 100000000000000, string: "100T", parts: [Integer("100"), Compact("T")]}, + {value: 1000000000000000, string: "1000T", parts: [Integer("1000"), Compact("T")]}, + {value: 10000000000000000, string: "10,000T", parts: [Integer("10"), Group(","), Integer("000"), Compact("T")]}, + {value: 100000000000000000, string: "100,000T", parts: [Integer("100"), Group(","), Integer("000"), Compact("T")]}, + + {value: 1n, string: "1", parts: [Integer("1")]}, + {value: 10n, string: "10", parts: [Integer("10")]}, + {value: 100n, string: "100", parts: [Integer("100")]}, + {value: 1000n, string: "1K", parts: [Integer("1"), Compact("K")]}, + {value: 10000n, string: "10K", parts: [Integer("10"), Compact("K")]}, + {value: 100000n, string: "100K", parts: [Integer("100"), Compact("K")]}, + {value: 1000000n, string: "1M", parts: [Integer("1"), Compact("M")]}, + {value: 10000000n, string: "10M", parts: [Integer("10"), Compact("M")]}, + {value: 100000000n, string: "100M", parts: [Integer("100"), Compact("M")]}, + {value: 1000000000n, string: "1B", parts: [Integer("1"), Compact("B")]}, + {value: 10000000000n, string: "10B", parts: [Integer("10"), Compact("B")]}, + {value: 100000000000n, string: "100B", parts: [Integer("100"), Compact("B")]}, + {value: 1000000000000n, string: "1T", parts: [Integer("1"), Compact("T")]}, + {value: 10000000000000n, string: "10T", parts: [Integer("10"), Compact("T")]}, + {value: 100000000000000n, string: "100T", parts: [Integer("100"), Compact("T")]}, + {value: 1000000000000000n, string: "1000T", parts: [Integer("1000"), Compact("T")]}, + {value: 10000000000000000n, string: "10,000T", parts: [Integer("10"), Group(","), Integer("000"), Compact("T")]}, + {value: 100000000000000000n, string: "100,000T", parts: [Integer("100"), Group(","), Integer("000"), Compact("T")]}, + + {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]}, + {value: 0.01, string: "0.01", parts: [Integer("0"), Decimal("."), Fraction("01")]}, + {value: 0.001, string: "0.001", parts: [Integer("0"), Decimal("."), Fraction("001")]}, + {value: 0.0001, string: "0.0001", parts: [Integer("0"), Decimal("."), Fraction("0001")]}, + {value: 0.00001, string: "0.00001", parts: [Integer("0"), Decimal("."), Fraction("00001")]}, + {value: 0.000001, string: "0.000001", parts: [Integer("0"), Decimal("."), Fraction("000001")]}, + {value: 0.0000001, string: "0.0000001", parts: [Integer("0"), Decimal("."), Fraction("0000001")]}, + + {value: 12, string: "12", parts: [Integer("12")]}, + {value: 123, string: "123", parts: [Integer("123")]}, + {value: 1234, string: "1.2K", parts: [Integer("1"), Decimal("."), Fraction("2"), Compact("K")]}, + {value: 12345, string: "12K", parts: [Integer("12"), Compact("K")]}, + {value: 123456, string: "123K", parts: [Integer("123"), Compact("K")]}, + {value: 1234567, string: "1.2M", parts: [Integer("1"), Decimal("."), Fraction("2"), Compact("M")]}, + {value: 12345678, string: "12M", parts: [Integer("12"), Compact("M")]}, + {value: 123456789, string: "123M", parts: [Integer("123"), Compact("M")]}, + + {value: Infinity, string: "∞", parts: [Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // Compact symbol can consist of multiple characters, e.g. when it's an abbreviation. + { + locale: "de", + options: { + notation: "compact", + compactDisplay: "short", + }, + values: [ + {value: 1e6, string: "1\u00A0Mio.", parts: [Integer("1"), Literal("\u00A0"), Compact("Mio.")]}, + {value: 1e9, string: "1\u00A0Mrd.", parts: [Integer("1"), Literal("\u00A0"), Compact("Mrd.")]}, + {value: 1e12, string: "1\u00A0Bio.", parts: [Integer("1"), Literal("\u00A0"), Compact("Bio.")]}, + + // CLDR doesn't support "Billiarde" (Brd.), Trillion (Trill.), etc. + {value: 1e15, string: "1000\u00A0Bio.", parts: [Integer("1000"), Literal("\u00A0"), Compact("Bio.")]}, + ], + }, + + // Digits are grouped in myriads (every 10,000) in Chinese. + { + locale: "zh", + options: { + notation: "compact", + compactDisplay: "short", + }, + values: [ + {value: 1, string: "1", parts: [Integer("1")]}, + {value: 10, string: "10", parts: [Integer("10")]}, + {value: 100, string: "100", parts: [Integer("100")]}, + {value: 1000, string: "1000", parts: [Integer("1000")]}, + + {value: 10e3, string: "1\u4E07", parts: [Integer("1"), Compact("\u4E07")]}, + {value: 100e3, string: "10\u4E07", parts: [Integer("10"), Compact("\u4E07")]}, + {value: 1000e3, string: "100\u4E07", parts: [Integer("100"), Compact("\u4E07")]}, + {value: 10000e3, string: "1000\u4E07", parts: [Integer("1000"), Compact("\u4E07")]}, + + {value: 10e7, string: "1\u4EBF", parts: [Integer("1"), Compact("\u4EBF")]}, + {value: 100e7, string: "10\u4EBF", parts: [Integer("10"), Compact("\u4EBF")]}, + {value: 1000e7, string: "100\u4EBF", parts: [Integer("100"), Compact("\u4EBF")]}, + {value: 10000e7, string: "1000\u4EBF", parts: [Integer("1000"), Compact("\u4EBF")]}, + + {value: 10e11, string: "1\u4E07\u4EBF", parts: [Integer("1"), Compact("\u4E07\u4EBF")]}, + {value: 100e11, string: "10\u4E07\u4EBF", parts: [Integer("10"), Compact("\u4E07\u4EBF")]}, + {value: 1000e11, string: "100\u4E07\u4EBF", parts: [Integer("100"), Compact("\u4E07\u4EBF")]}, + {value: 10000e11, string: "1000\u4E07\u4EBF", parts: [Integer("1000"), Compact("\u4E07\u4EBF")]}, + + {value: 100000e11, string: "10,000\u4E07\u4EBF", parts: [Integer("10"), Group(","), Integer("000"), Compact("\u4E07\u4EBF")]}, + ], + }, +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-compact-with-fraction-digits.js b/js/src/tests/non262/Intl/NumberFormat/notation-compact-with-fraction-digits.js new file mode 100644 index 0000000000..4bdf26b9ad --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/notation-compact-with-fraction-digits.js @@ -0,0 +1,17 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +let nf = new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + notation: "compact", +}); + +assertEq(nf.format(1500), "1.5K"); +assertEq(nf.format(1520), "1.52K"); +assertEq(nf.format(1540), "1.54K"); +assertEq(nf.format(1550), "1.55K"); +assertEq(nf.format(1560), "1.56K"); +assertEq(nf.format(1580), "1.58K"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-engineering.js b/js/src/tests/non262/Intl/NumberFormat/notation-engineering.js new file mode 100644 index 0000000000..b4c0e19e53 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/notation-engineering.js @@ -0,0 +1,136 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group, + ExponentSeparator, ExponentInteger, ExponentMinusSign, +} = NumberFormatParts; + +const testcases = [ + { + locale: "en", + options: { + notation: "engineering", + }, + values: [ + {value: +0, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: -0, string: "-0E0", parts: [MinusSign("-"), Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 0n, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]}, + + {value: 1, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 10, string: "10E0", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 100, string: "100E0", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 1000, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 10000, string: "10E3", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 100000, string: "100E3", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 1000000, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]}, + + {value: 1n, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 10n, string: "10E0", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 100n, string: "100E0", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 1000n, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 10000n, string: "10E3", parts: [Integer("10"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 100000n, string: "100E3", parts: [Integer("100"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 1000000n, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]}, + + {value: 0.1, string: "100E-3", parts: [Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]}, + {value: 0.01, string: "10E-3", parts: [Integer("10"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]}, + {value: 0.001, string: "1E-3", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]}, + {value: 0.0001, string: "100E-6", parts: [Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]}, + {value: 0.00001, string: "10E-6", parts: [Integer("10"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]}, + {value: 0.000001, string: "1E-6", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]}, + {value: 0.0000001, string: "100E-9", parts: [Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("9")]}, + + {value: Infinity, string: "∞", parts: [Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // Exponent modifications take place early, so while in the "standard" notation + // `Intl.NumberFormat("en", {maximumFractionDigits: 0}).format(0.1)` returns "0", for + // "engineering" notation the result string is not "0", but instead "100E-3". + + { + locale: "en", + options: { + notation: "engineering", + maximumFractionDigits: 0, + }, + values: [ + {value: 0.1, string: "100E-3", parts: [ + Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "engineering", + minimumFractionDigits: 4, + }, + values: [ + {value: 10, string: "10.0000E0", parts: [ + Integer("10"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentInteger("0") + ]}, + {value: 0.1, string: "100.0000E-3", parts: [ + Integer("100"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "engineering", + minimumIntegerDigits: 4, + }, + values: [ + {value: 10, string: "0,010E0", parts: [ + Integer("0"), Group(","), Integer("010"), ExponentSeparator("E"), ExponentInteger("0") + ]}, + {value: 0.1, string: "0,100E-3", parts: [ + Integer("0"), Group(","), Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "engineering", + minimumSignificantDigits: 4, + }, + values: [ + {value: 10, string: "10.00E0", parts: [ + Integer("10"), Decimal("."), Fraction("00"), ExponentSeparator("E"), ExponentInteger("0") + ]}, + {value: 0.1, string: "100.0E-3", parts: [ + Integer("100"), Decimal("."), Fraction("0"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "engineering", + maximumSignificantDigits: 1, + }, + values: [ + {value: 12, string: "10E0", parts: [ + Integer("10"), ExponentSeparator("E"), ExponentInteger("0") + ]}, + {value: 0.12, string: "100E-3", parts: [ + Integer("100"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3") + ]}, + ], + }, +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/notation-scientific.js b/js/src/tests/non262/Intl/NumberFormat/notation-scientific.js new file mode 100644 index 0000000000..f14a660129 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/notation-scientific.js @@ -0,0 +1,136 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction, Group, + ExponentSeparator, ExponentInteger, ExponentMinusSign, +} = NumberFormatParts; + +const testcases = [ + { + locale: "en", + options: { + notation: "scientific", + }, + values: [ + {value: +0, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: -0, string: "-0E0", parts: [MinusSign("-"), Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 0n, string: "0E0", parts: [Integer("0"), ExponentSeparator("E"), ExponentInteger("0")]}, + + {value: 1, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 10, string: "1E1", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("1")]}, + {value: 100, string: "1E2", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("2")]}, + {value: 1000, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 10000, string: "1E4", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("4")]}, + {value: 100000, string: "1E5", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("5")]}, + {value: 1000000, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]}, + + {value: 1n, string: "1E0", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("0")]}, + {value: 10n, string: "1E1", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("1")]}, + {value: 100n, string: "1E2", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("2")]}, + {value: 1000n, string: "1E3", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("3")]}, + {value: 10000n, string: "1E4", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("4")]}, + {value: 100000n, string: "1E5", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("5")]}, + {value: 1000000n, string: "1E6", parts: [Integer("1"), ExponentSeparator("E"), ExponentInteger("6")]}, + + {value: 0.1, string: "1E-1", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1")]}, + {value: 0.01, string: "1E-2", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("2")]}, + {value: 0.001, string: "1E-3", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("3")]}, + {value: 0.0001, string: "1E-4", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("4")]}, + {value: 0.00001, string: "1E-5", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("5")]}, + {value: 0.000001, string: "1E-6", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("6")]}, + {value: 0.0000001, string: "1E-7", parts: [Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("7")]}, + + {value: Infinity, string: "∞", parts: [Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // Exponent modifications take place early, so while in the "standard" notation + // `Intl.NumberFormat("en", {maximumFractionDigits: 0}).format(0.1)` returns "0", for + // "scientific" notation the result string is not "0", but instead "1E-1". + + { + locale: "en", + options: { + notation: "scientific", + maximumFractionDigits: 0, + }, + values: [ + {value: 0.1, string: "1E-1", parts: [ + Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "scientific", + minimumFractionDigits: 4, + }, + values: [ + {value: 10, string: "1.0000E1", parts: [ + Integer("1"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentInteger("1") + ]}, + {value: 0.1, string: "1.0000E-1", parts: [ + Integer("1"), Decimal("."), Fraction("0000"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "scientific", + minimumIntegerDigits: 4, + }, + values: [ + {value: 10, string: "0,001E1", parts: [ + Integer("0"), Group(","), Integer("001"), ExponentSeparator("E"), ExponentInteger("1") + ]}, + {value: 0.1, string: "0,001E-1", parts: [ + Integer("0"), Group(","), Integer("001"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "scientific", + minimumSignificantDigits: 4, + }, + values: [ + {value: 10, string: "1.000E1", parts: [ + Integer("1"), Decimal("."), Fraction("000"), ExponentSeparator("E"), ExponentInteger("1") + ]}, + {value: 0.1, string: "1.000E-1", parts: [ + Integer("1"), Decimal("."), Fraction("000"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1") + ]}, + ], + }, + + { + locale: "en", + options: { + notation: "scientific", + maximumSignificantDigits: 1, + }, + values: [ + {value: 12, string: "1E1", parts: [ + Integer("1"), ExponentSeparator("E"), ExponentInteger("1") + ]}, + {value: 0.12, string: "1E-1", parts: [ + Integer("1"), ExponentSeparator("E"), ExponentMinusSign("-"), ExponentInteger("1") + ]}, + ], + }, +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js new file mode 100644 index 0000000000..c0b9ba78ed --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-format.js @@ -0,0 +1,18 @@ +for (let [numberingSystem, {digits, algorithmic}] of Object.entries(numberingSystems)) { + if (algorithmic) { + // We don't yet support algorithmic numbering systems. + continue; + } + + let nf = new Intl.NumberFormat("en", {numberingSystem}); + + assertEq([...digits].length, 10, "expect exactly ten digits for each numbering system"); + + let i = 0; + for (let digit of digits) { + assertEq(nf.format(i++), digit); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js new file mode 100644 index 0000000000..4624ee5192 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/numberingSystem-option.js @@ -0,0 +1,60 @@ +const defaultLocale = "en"; +const defaultNumberingSystem = new Intl.NumberFormat(defaultLocale).resolvedOptions().numberingSystem; + +function createWithLocale(locale, numberingSystem) { + return new Intl.NumberFormat(locale, {numberingSystem}); +} + +function create(numberingSystem) { + return createWithLocale(defaultLocale, numberingSystem); +} + +// Empty string should throw. +assertThrowsInstanceOf(() => create(""), RangeError); + +// Trailing \0 should throw. +assertThrowsInstanceOf(() => create("latn\0"), RangeError); + +// Too short or too long strings should throw. +assertThrowsInstanceOf(() => create("a"), RangeError); +assertThrowsInstanceOf(() => create("toolongstring"), RangeError); + +// Throw even when prefix is valid. +assertThrowsInstanceOf(() => create("latn-toolongstring"), RangeError); + +// |numberingSystem| can be set to |undefined|. +let nf = create(undefined); +assertEq(nf.resolvedOptions().numberingSystem, defaultNumberingSystem); + +// Unsupported numbering systems are ignored. +nf = create("xxxxxxxx"); +assertEq(nf.resolvedOptions().numberingSystem, defaultNumberingSystem); + +// Numbering system in options overwrite Unicode extension keyword. +nf = createWithLocale(`${defaultLocale}-u-nu-thai`, "arab"); +assertEq(nf.resolvedOptions().locale, defaultLocale); +assertEq(nf.resolvedOptions().numberingSystem, "arab"); + +// |numberingSystem| option ignores case. +nf = create("ARAB"); +assertEq(nf.resolvedOptions().locale, defaultLocale); +assertEq(nf.resolvedOptions().numberingSystem, "arab"); + +for (let [numberingSystem, {algorithmic}] of Object.entries(numberingSystems)) { + let nf1 = new Intl.NumberFormat(`${defaultLocale}-u-nu-${numberingSystem}`); + let nf2 = new Intl.NumberFormat(defaultLocale, {numberingSystem}); + + if (!algorithmic) { + assertEq(nf1.resolvedOptions().numberingSystem, numberingSystem); + assertEq(nf2.resolvedOptions().numberingSystem, numberingSystem); + } else { + // We don't yet support algorithmic numbering systems. + assertEq(nf1.resolvedOptions().numberingSystem, defaultNumberingSystem); + assertEq(nf2.resolvedOptions().numberingSystem, defaultNumberingSystem); + } + + assertEq(nf2.format(0), nf1.format(0)); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js b/js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js new file mode 100644 index 0000000000..ddb2d61350 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/options-emulate-undefined.js @@ -0,0 +1,13 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// createIsHTMLDDA is only available when running tests in the shell, +// not the browser +if (typeof createIsHTMLDDA === "function") { + let nf = new Intl.NumberFormat('en-US', createIsHTMLDDA()); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js b/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js new file mode 100644 index 0000000000..87cfc317cb --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js @@ -0,0 +1,25 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Locale processing is supposed to internally remove any Unicode extension +// sequences in the locale. Test that various weird testcases invoking +// algorithmic edge cases don't assert or throw exceptions. + +var weirdCases = + [ + "en-x-u-foo", + "en-a-bar-x-u-foo", + "en-x-u-foo-a-bar", + "en-a-bar-u-baz-x-u-foo", + ]; + +for (var locale of weirdCases) + Intl.NumberFormat(locale).format(5); + +assertThrowsInstanceOf(() => Intl.NumberFormat("x-u-foo"), RangeError); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/rounding-increment.js b/js/src/tests/non262/Intl/NumberFormat/rounding-increment.js new file mode 100644 index 0000000000..69c457a04a --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/rounding-increment.js @@ -0,0 +1,115 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Nickel rounding. +{ + let nf = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + roundingIncrement: 5, + }); + + assertEq(nf.format(1.22), "1.20"); + assertEq(nf.format(1.224), "1.20"); + assertEq(nf.format(1.225), "1.25"); + assertEq(nf.format(1.23), "1.25"); +} + +// Dime rounding. +{ + let nf = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + roundingIncrement: 10, + }); + + assertEq(nf.format(1.24), "1.20"); + assertEq(nf.format(1.249), "1.20"); + assertEq(nf.format(1.250), "1.30"); + assertEq(nf.format(1.25), "1.30"); +} + +// Rounding increment option is rounded down. +{ + let nf1 = new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + roundingIncrement: 10, + }); + + let nf2 = new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + roundingIncrement: 10.1, + }); + + let nf3 = new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + roundingIncrement: 10.9, + }); + + assertEq(nf1.resolvedOptions().roundingIncrement, 10); + assertEq(nf2.resolvedOptions().roundingIncrement, 10); + assertEq(nf3.resolvedOptions().roundingIncrement, 10); + + assertEq(nf1.format(123), "120"); + assertEq(nf2.format(123), "120"); + assertEq(nf3.format(123), "120"); +} + +// |minimumFractionDigits| must be equal to |maximumFractionDigits| when +// |roundingIncrement| is used. +// +// |minimumFractionDigits| defaults to zero. +{ + let nf = new Intl.NumberFormat("en", { + roundingIncrement: 10, + // minimumFractionDigits: 0, (default) + maximumFractionDigits: 0, + }); + + let resolved = nf.resolvedOptions(); + assertEq(resolved.minimumFractionDigits, 0); + assertEq(resolved.maximumFractionDigits, 0); + assertEq(resolved.roundingIncrement, 10); + + assertEq(nf.format(123), "120"); + assertEq(nf.format(123.456), "120"); +} + +// |maximumFractionDigitsDefault| is set to |minimumFractionDigitsDefault| when +// roundingIncrement isn't equal to 1. +{ + let options = { + roundingIncrement: 10, + // minimumFractionDigits: 0, (default) + // maximumFractionDigits: 0, (default) + }; + let nf = new Intl.NumberFormat("en", options); + assertEq(nf.resolvedOptions().minimumFractionDigits, 0); + assertEq(nf.resolvedOptions().maximumFractionDigits, 0); +} + +// |maximumFractionDigits| must be equal to |minimumFractionDigits| when +// roundingIncrement isn't equal to 1. +{ + let options = { + roundingIncrement: 10, + // minimumFractionDigits: 0, (default) + maximumFractionDigits: 1, + }; + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", options), RangeError); +} + +// Invalid values. +for (let roundingIncrement of [-1, 0, Infinity, NaN]){ + let options = { + roundingIncrement, + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }; + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", options), RangeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/rounding-mode.js b/js/src/tests/non262/Intl/NumberFormat/rounding-mode.js new file mode 100644 index 0000000000..b58a7ad3da --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/rounding-mode.js @@ -0,0 +1,287 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const tests = [ + // Special values: Zeros and non-finite values. + { + value: 0, + options: {}, + roundingModes: { + ceil: "0", + floor: "0", + expand: "0", + trunc: "0", + halfCeil: "0", + halfFloor: "0", + halfExpand: "0", + halfTrunc: "0", + halfEven: "0", + }, + }, + { + value: -0, + options: {}, + roundingModes: { + ceil: "-0", + floor: "-0", + expand: "-0", + trunc: "-0", + halfCeil: "-0", + halfFloor: "-0", + halfExpand: "-0", + halfTrunc: "-0", + halfEven: "-0", + }, + }, + { + value: -Infinity, + options: {}, + roundingModes: { + ceil: "-∞", + floor: "-∞", + expand: "-∞", + trunc: "-∞", + halfCeil: "-∞", + halfFloor: "-∞", + halfExpand: "-∞", + halfTrunc: "-∞", + halfEven: "-∞", + }, + }, + { + value: Infinity, + options: {}, + roundingModes: { + ceil: "∞", + floor: "∞", + expand: "∞", + trunc: "∞", + halfCeil: "∞", + halfFloor: "∞", + halfExpand: "∞", + halfTrunc: "∞", + halfEven: "∞", + }, + }, + { + value: NaN, + options: {}, + roundingModes: { + ceil: "NaN", + floor: "NaN", + expand: "NaN", + trunc: "NaN", + halfCeil: "NaN", + halfFloor: "NaN", + halfExpand: "NaN", + halfTrunc: "NaN", + halfEven: "NaN", + }, + }, + + // Integer rounding with positive values. + { + value: 0.4, + options: {maximumFractionDigits: 0}, + roundingModes: { + ceil: "1", + floor: "0", + expand: "1", + trunc: "0", + halfCeil: "0", + halfFloor: "0", + halfExpand: "0", + halfTrunc: "0", + halfEven: "0", + }, + }, + { + value: 0.5, + options: {maximumFractionDigits: 0}, + roundingModes: { + ceil: "1", + floor: "0", + expand: "1", + trunc: "0", + halfCeil: "1", + halfFloor: "0", + halfExpand: "1", + halfTrunc: "0", + halfEven: "0", + }, + }, + { + value: 0.6, + options: {maximumFractionDigits: 0}, + roundingModes: { + ceil: "1", + floor: "0", + expand: "1", + trunc: "0", + halfCeil: "1", + halfFloor: "1", + halfExpand: "1", + halfTrunc: "1", + halfEven: "1", + }, + }, + + // Integer rounding with negative values. + { + value: -0.4, + options: {maximumFractionDigits: 0}, + roundingModes: { + ceil: "-0", + floor: "-1", + expand: "-1", + trunc: "-0", + halfCeil: "-0", + halfFloor: "-0", + halfExpand: "-0", + halfTrunc: "-0", + halfEven: "-0", + }, + }, + { + value: -0.5, + options: {maximumFractionDigits: 0}, + roundingModes: { + ceil: "-0", + floor: "-1", + expand: "-1", + trunc: "-0", + halfCeil: "-0", + halfFloor: "-1", + halfExpand: "-1", + halfTrunc: "-0", + halfEven: "-0", + }, + }, + { + value: -0.6, + options: {maximumFractionDigits: 0}, + roundingModes: { + ceil: "-0", + floor: "-1", + expand: "-1", + trunc: "-0", + halfCeil: "-1", + halfFloor: "-1", + halfExpand: "-1", + halfTrunc: "-1", + halfEven: "-1", + }, + }, + + // Fractional digits rounding with positive values. + { + value: 0.04, + options: {maximumFractionDigits: 1}, + roundingModes: { + ceil: "0.1", + floor: "0", + expand: "0.1", + trunc: "0", + halfCeil: "0", + halfFloor: "0", + halfExpand: "0", + halfTrunc: "0", + halfEven: "0", + }, + }, + { + value: 0.05, + options: {maximumFractionDigits: 1}, + roundingModes: { + ceil: "0.1", + floor: "0", + expand: "0.1", + trunc: "0", + halfCeil: "0.1", + halfFloor: "0", + halfExpand: "0.1", + halfTrunc: "0", + halfEven: "0", + }, + }, + { + value: 0.06, + options: {maximumFractionDigits: 1}, + roundingModes: { + ceil: "0.1", + floor: "0", + expand: "0.1", + trunc: "0", + halfCeil: "0.1", + halfFloor: "0.1", + halfExpand: "0.1", + halfTrunc: "0.1", + halfEven: "0.1", + }, + }, + + // Fractional digits rounding with negative values. + { + value: -0.04, + options: {maximumFractionDigits: 1}, + roundingModes: { + ceil: "-0", + floor: "-0.1", + expand: "-0.1", + trunc: "-0", + halfCeil: "-0", + halfFloor: "-0", + halfExpand: "-0", + halfTrunc: "-0", + halfEven: "-0", + }, + }, + { + value: -0.05, + options: {maximumFractionDigits: 1}, + roundingModes: { + ceil: "-0", + floor: "-0.1", + expand: "-0.1", + trunc: "-0", + halfCeil: "-0", + halfFloor: "-0.1", + halfExpand: "-0.1", + halfTrunc: "-0", + halfEven: "-0", + }, + }, + { + value: -0.06, + options: {maximumFractionDigits: 1}, + roundingModes: { + ceil: "-0", + floor: "-0.1", + expand: "-0.1", + trunc: "-0", + halfCeil: "-0.1", + halfFloor: "-0.1", + halfExpand: "-0.1", + halfTrunc: "-0.1", + halfEven: "-0.1", + }, + }, +]; + +for (let {value, options, roundingModes} of tests) { + for (let [roundingMode, expected] of Object.entries(roundingModes)) { + let nf = new Intl.NumberFormat("en", {...options, roundingMode}); + assertEq(nf.format(value), expected, `value=${value}, roundingMode=${roundingMode}`); + assertEq(nf.resolvedOptions().roundingMode, roundingMode); + } +} + +// Default value is "halfExpand". +assertEq(new Intl.NumberFormat().resolvedOptions().roundingMode, "halfExpand"); + +// Invalid values. +for (let roundingMode of ["", null, "halfOdd", "halfUp", "Up", "up"]){ + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {roundingMode}), RangeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/rounding-priority.js b/js/src/tests/non262/Intl/NumberFormat/rounding-priority.js new file mode 100644 index 0000000000..2e672a3022 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/rounding-priority.js @@ -0,0 +1,132 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const tests = [ + // Rounding conflict with maximum fraction/significand digits. + { + value: 4.321, + options: { + maximumFractionDigits: 2, + maximumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "4.3", + lessPrecision: "4.3", + morePrecision: "4.32", + }, + }, + { + value: 4.321, + options: { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + maximumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "4.3", + lessPrecision: "4.3", + morePrecision: "4.32", + }, + }, + { + value: 4.321, + options: { + maximumFractionDigits: 2, + maximumSignificantDigits: 2, + minimumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "4.3", + lessPrecision: "4.3", + morePrecision: "4.32", + }, + }, + { + value: 4.321, + options: { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + maximumSignificantDigits: 2, + minimumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "4.3", + lessPrecision: "4.3", + morePrecision: "4.32", + }, + }, + + // Rounding conflict with minimum fraction/significand digits. + { + value: 1.0, + options: { + minimumFractionDigits: 2, + minimumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "1.0", + // Returning "1.00" for both options seems unexpected. Also filed at + // <https://github.com/tc39/proposal-intl-numberformat-v3/issues/52>. + lessPrecision: "1.00", + morePrecision: "1.0", + }, + }, + { + value: 1.0, + options: { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + minimumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "1.0", + lessPrecision: "1.00", + morePrecision: "1.0", + }, + }, + { + value: 1.0, + options: { + minimumFractionDigits: 2, + minimumSignificantDigits: 2, + maximumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "1.0", + lessPrecision: "1.0", + morePrecision: "1.00", + }, + }, + { + value: 1.0, + options: { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + minimumSignificantDigits: 2, + maximumSignificantDigits: 2, + }, + roundingPriorities: { + auto: "1.0", + lessPrecision: "1.0", + morePrecision: "1.00", + }, + }, +]; + +for (let {value, options, roundingPriorities} of tests) { + for (let [roundingPriority, expected] of Object.entries(roundingPriorities)) { + let nf = new Intl.NumberFormat("en", {...options, roundingPriority}); + assertEq(nf.resolvedOptions().roundingPriority, roundingPriority); + assertEq(nf.format(value), expected, `value=${value}, roundingPriority=${roundingPriority}`); + } +} + +// Default value of "auto". +assertEq(new Intl.NumberFormat().resolvedOptions().roundingPriority, "auto"); + +// Invalid values. +for (let roundingPriority of ["", null, "more", "less", "never"]){ + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {roundingPriority}), RangeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/shell.js b/js/src/tests/non262/Intl/NumberFormat/shell.js new file mode 100644 index 0000000000..471fdad71b --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/shell.js @@ -0,0 +1,77 @@ +function GenericPartCreator(type) { + return str => ({ type, value: str }); +} + +const NumberFormatParts = { + Nan: GenericPartCreator("nan"), + Inf: GenericPartCreator("infinity"), + Integer: GenericPartCreator("integer"), + Group: GenericPartCreator("group"), + Decimal: GenericPartCreator("decimal"), + Fraction: GenericPartCreator("fraction"), + MinusSign: GenericPartCreator("minusSign"), + PlusSign: GenericPartCreator("plusSign"), + PercentSign: GenericPartCreator("percentSign"), + Currency: GenericPartCreator("currency"), + Literal: GenericPartCreator("literal"), + ExponentSeparator: GenericPartCreator("exponentSeparator"), + ExponentMinusSign: GenericPartCreator("exponentMinusSign"), + ExponentInteger: GenericPartCreator("exponentInteger"), + Compact: GenericPartCreator("compact"), + Unit: GenericPartCreator("unit"), +}; + +function NumberRangeFormatParts(source) { + let entries = Object.entries(NumberFormatParts) + + entries.push(["Approx", GenericPartCreator("approximatelySign")]); + + return Object.fromEntries(entries.map(([key, part]) => { + let partWithSource = str => { + return Object.defineProperty(part(str), "source", { + value: source, writable: true, enumerable: true, configurable: true + }); + }; + return [key, partWithSource]; + })); +} + +function assertParts(nf, x, expected) { + var parts = nf.formatToParts(x); + assertEq(parts.map(part => part.value).join(""), nf.format(x), + "formatToParts and format must agree"); + + var len = parts.length; + assertEq(len, expected.length, "parts count mismatch"); + for (var i = 0; i < len; i++) { + assertEq(parts[i].type, expected[i].type, "type mismatch at " + i); + assertEq(parts[i].value, expected[i].value, "value mismatch at " + i); + } +} + +function assertRangeParts(nf, start, end, expected) { + var parts = nf.formatRangeToParts(start, end); + assertEq(parts.map(part => part.value).join(""), nf.formatRange(start, end), + "formatRangeToParts and formatRange must agree"); + + var len = parts.length; + assertEq(len, expected.length, "parts count mismatch"); + for (var i = 0; i < len; i++) { + assertEq(parts[i].type, expected[i].type, "type mismatch at " + i); + assertEq(parts[i].value, expected[i].value, "value mismatch at " + i); + assertEq(parts[i].source, expected[i].source, "source mismatch at " + i); + } +} + +function runNumberFormattingTestcases(testcases) { + for (let {locale, options, values} of testcases) { + let nf = new Intl.NumberFormat(locale, options); + + for (let {value, string, parts} of values) { + assertEq(nf.format(value), string, + `locale=${locale}, options=${JSON.stringify(options)}, value=${value}`); + + assertParts(nf, value, parts); + } + } +} diff --git a/js/src/tests/non262/Intl/NumberFormat/sign-display.js b/js/src/tests/non262/Intl/NumberFormat/sign-display.js new file mode 100644 index 0000000000..b18517d847 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/sign-display.js @@ -0,0 +1,187 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Decimal, Fraction +} = NumberFormatParts; + +const testcases = [ + // "auto": Show the sign on negative numbers only. + { + locale: "en", + options: { + signDisplay: "auto", + }, + values: [ + {value: +0, string: "0", parts: [Integer("0")]}, + {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]}, + {value: 0n, string: "0", parts: [Integer("0")]}, + + {value: 1, string: "1", parts: [Integer("1")]}, + {value: -1, string: "-1", parts: [MinusSign("-"), Integer("1")]}, + {value: 1n, string: "1", parts: [Integer("1")]}, + {value: -1n, string: "-1", parts: [MinusSign("-"), Integer("1")]}, + + {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]}, + {value: -0.1, string: "-0.1", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("1")]}, + + {value: 0.9, string: "0.9", parts: [Integer("0"), Decimal("."), Fraction("9")]}, + {value: -0.9, string: "-0.9", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("9")]}, + + {value: Infinity, string: "∞", parts: [Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // "never": Show the sign on neither positive nor negative numbers. + { + locale: "en", + options: { + signDisplay: "never", + }, + values: [ + {value: +0, string: "0", parts: [Integer("0")]}, + {value: -0, string: "0", parts: [Integer("0")]}, + {value: 0n, string: "0", parts: [Integer("0")]}, + + {value: 1, string: "1", parts: [Integer("1")]}, + {value: -1, string: "1", parts: [Integer("1")]}, + {value: 1n, string: "1", parts: [Integer("1")]}, + {value: -1n, string: "1", parts: [Integer("1")]}, + + {value: 0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]}, + {value: -0.1, string: "0.1", parts: [Integer("0"), Decimal("."), Fraction("1")]}, + + {value: 0.9, string: "0.9", parts: [Integer("0"), Decimal("."), Fraction("9")]}, + {value: -0.9, string: "0.9", parts: [Integer("0"), Decimal("."), Fraction("9")]}, + + {value: Infinity, string: "∞", parts: [Inf("∞")]}, + {value: -Infinity, string: "∞", parts: [Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // "always": Show the sign on positive and negative numbers including zero. + { + locale: "en", + options: { + signDisplay: "always", + }, + values: [ + {value: +0, string: "+0", parts: [PlusSign("+"), Integer("0")]}, + {value: -0, string: "-0", parts: [MinusSign("-"), Integer("0")]}, + {value: 0n, string: "+0", parts: [PlusSign("+"), Integer("0")]}, + + {value: 1, string: "+1", parts: [PlusSign("+"), Integer("1")]}, + {value: -1, string: "-1", parts: [MinusSign("-"), Integer("1")]}, + {value: 1n, string: "+1", parts: [PlusSign("+"), Integer("1")]}, + {value: -1n, string: "-1", parts: [MinusSign("-"), Integer("1")]}, + + {value: 0.1, string: "+0.1", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("1")]}, + {value: -0.1, string: "-0.1", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("1")]}, + + {value: 0.9, string: "+0.9", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("9")]}, + {value: -0.9, string: "-0.9", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("9")]}, + + {value: Infinity, string: "+∞", parts: [PlusSign("+"), Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "+NaN", parts: [PlusSign("+"), Nan("NaN")]}, + {value: -NaN, string: "+NaN", parts: [PlusSign("+"), Nan("NaN")]}, + ], + }, + + // "exceptZero": Show the sign on positive and negative numbers but not zero. + { + locale: "en", + options: { + signDisplay: "exceptZero", + }, + values: [ + {value: +0, string: "0", parts: [Integer("0")]}, + {value: -0, string: "0", parts: [Integer("0")]}, + {value: 0n, string: "0", parts: [Integer("0")]}, + + {value: 1, string: "+1", parts: [PlusSign("+"), Integer("1")]}, + {value: -1, string: "-1", parts: [MinusSign("-"), Integer("1")]}, + {value: 1n, string: "+1", parts: [PlusSign("+"), Integer("1")]}, + {value: -1n, string: "-1", parts: [MinusSign("-"), Integer("1")]}, + + {value: 0.1, string: "+0.1", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("1")]}, + {value: -0.1, string: "-0.1", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("1")]}, + + {value: 0.9, string: "+0.9", parts: [PlusSign("+"), Integer("0"), Decimal("."), Fraction("9")]}, + {value: -0.9, string: "-0.9", parts: [MinusSign("-"), Integer("0"), Decimal("."), Fraction("9")]}, + + {value: Infinity, string: "+∞", parts: [PlusSign("+"), Inf("∞")]}, + {value: -Infinity, string: "-∞", parts: [MinusSign("-"), Inf("∞")]}, + + {value: NaN, string: "NaN", parts: [Nan("NaN")]}, + {value: -NaN, string: "NaN", parts: [Nan("NaN")]}, + ], + }, + + // Tests with suppressed fractional digits. + + // "auto": Show the sign on negative numbers only. + { + locale: "en", + options: { + signDisplay: "auto", + maximumFractionDigits: 0, + }, + values: [ + {value: +0.1, string: "0", parts: [Integer("0")]}, + {value: -0.1, string: "-0", parts: [MinusSign("-"), Integer("0")]}, + ], + }, + + // "never": Show the sign on neither positive nor negative numbers. + { + locale: "en", + options: { + signDisplay: "never", + maximumFractionDigits: 0, + }, + values: [ + {value: +0.1, string: "0", parts: [Integer("0")]}, + {value: -0.1, string: "0", parts: [Integer("0")]}, + ], + }, + + // "always": Show the sign on positive and negative numbers including zero. + { + locale: "en", + options: { + signDisplay: "always", + maximumFractionDigits: 0, + }, + values: [ + {value: +0.1, string: "+0", parts: [PlusSign("+"), Integer("0")]}, + {value: -0.1, string: "-0", parts: [MinusSign("-"), Integer("0")]}, + ], + }, + + // "exceptZero": Show the sign on positive and negative numbers but not zero. + { + locale: "en", + options: { + signDisplay: "exceptZero", + maximumFractionDigits: 0, + }, + + values: [ + {value: +0.1, string: "0", parts: [Integer("0")]}, + {value: -0.1, string: "0", parts: [Integer("0")]}, + ], + } +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js b/js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js new file mode 100644 index 0000000000..c569313266 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/significantDigitsOfZero.js @@ -0,0 +1,38 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) +// -- test that NumberFormat correctly formats 0 with various numbers of significant digits + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var testData = [ + {minimumSignificantDigits: 1, maximumSignificantDigits: 1, expected: "0"}, + {minimumSignificantDigits: 1, maximumSignificantDigits: 2, expected: "0"}, + {minimumSignificantDigits: 1, maximumSignificantDigits: 3, expected: "0"}, + {minimumSignificantDigits: 1, maximumSignificantDigits: 4, expected: "0"}, + {minimumSignificantDigits: 1, maximumSignificantDigits: 5, expected: "0"}, + {minimumSignificantDigits: 2, maximumSignificantDigits: 2, expected: "0.0"}, + {minimumSignificantDigits: 2, maximumSignificantDigits: 3, expected: "0.0"}, + {minimumSignificantDigits: 2, maximumSignificantDigits: 4, expected: "0.0"}, + {minimumSignificantDigits: 2, maximumSignificantDigits: 5, expected: "0.0"}, + {minimumSignificantDigits: 3, maximumSignificantDigits: 3, expected: "0.00"}, + {minimumSignificantDigits: 3, maximumSignificantDigits: 4, expected: "0.00"}, + {minimumSignificantDigits: 3, maximumSignificantDigits: 5, expected: "0.00"}, +]; + +for (var i = 0; i < testData.length; i++) { + var min = testData[i].minimumSignificantDigits; + var max = testData[i].maximumSignificantDigits; + var options = {minimumSignificantDigits: min, maximumSignificantDigits: max}; + var format = new Intl.NumberFormat("en-US", options); + var actual = format.format(0); + var expected = testData[i].expected; + assertEq(actual, expected, + "Wrong formatted string for 0 with " + + "minimumSignificantDigits " + min + + ", maximumSignificantDigits " + max + + ": expected \"" + expected + + "\", actual \"" + actual + "\""); +} + +reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js b/js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js new file mode 100644 index 0000000000..5ba4470a98 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/supportedLocalesOf.js @@ -0,0 +1,371 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")||xulRuntime.shell) +// -- test in browser only that ICU has locale data for all Mozilla languages + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This array contains the locales that ICU supports in +// number formatting whose languages Mozilla localizes Firefox into. +// Current as of ICU 50.1.2 and Firefox March 2013. +var locales = [ + "af", + "af-NA", + "af-ZA", + "ar", + "ar-001", + "ar-AE", + "ar-BH", + "ar-DJ", + "ar-DZ", + "ar-EG", + "ar-EH", + "ar-ER", + "ar-IL", + "ar-IQ", + "ar-JO", + "ar-KM", + "ar-KW", + "ar-LB", + "ar-LY", + "ar-MA", + "ar-MR", + "ar-OM", + "ar-PS", + "ar-QA", + "ar-SA", + "ar-SD", + "ar-SO", + "ar-SY", + "ar-TD", + "ar-TN", + "ar-YE", + "as", + "as-IN", + "be", + "be-BY", + "bg", + "bg-BG", + "bn", + "bn-BD", + "bn-IN", + "br", + "br-FR", + "bs", + "bs-Cyrl", + "bs-Cyrl-BA", + "bs-Latn", + "bs-Latn-BA", + "ca", + "ca-AD", + "ca-ES", + "cs", + "cs-CZ", + "cy", + "cy-GB", + "da", + "da-DK", + "de", + "de-AT", + "de-BE", + "de-CH", + "de-DE", + "de-LI", + "de-LU", + "el", + "el-CY", + "el-GR", + "en", + "en-150", + "en-AG", + "en-AS", + "en-AU", + "en-BB", + "en-BE", + "en-BM", + "en-BS", + "en-BW", + "en-BZ", + "en-CA", + "en-CM", + "en-DM", + "en-FJ", + "en-FM", + "en-GB", + "en-GD", + "en-GG", + "en-GH", + "en-GI", + "en-GM", + "en-GU", + "en-GY", + "en-HK", + "en-IE", + "en-IM", + "en-IN", + "en-JE", + "en-JM", + "en-KE", + "en-KI", + "en-KN", + "en-KY", + "en-LC", + "en-LR", + "en-LS", + "en-MG", + "en-MH", + "en-MP", + "en-MT", + "en-MU", + "en-MW", + "en-NA", + "en-NG", + "en-NZ", + "en-PG", + "en-PH", + "en-PK", + "en-PR", + "en-PW", + "en-SB", + "en-SC", + "en-SG", + "en-SL", + "en-SS", + "en-SZ", + "en-TC", + "en-TO", + "en-TT", + "en-TZ", + "en-UG", + "en-UM", + "en-US", + "en-US-POSIX", + "en-VC", + "en-VG", + "en-VI", + "en-VU", + "en-WS", + "en-ZA", + "en-ZM", + "en-ZW", + "eo", + "es", + "es-419", + "es-AR", + "es-BO", + "es-CL", + "es-CO", + "es-CR", + "es-CU", + "es-DO", + "es-EA", + "es-EC", + "es-ES", + "es-GQ", + "es-GT", + "es-HN", + "es-IC", + "es-MX", + "es-NI", + "es-PA", + "es-PE", + "es-PH", + "es-PR", + "es-PY", + "es-SV", + "es-US", + "es-UY", + "es-VE", + "et", + "et-EE", + "eu", + "eu-ES", + "fa", + "fa-AF", + "fa-IR", + "ff", + "ff-SN", + "fi", + "fi-FI", + "fr", + "fr-BE", + "fr-BF", + "fr-BI", + "fr-BJ", + "fr-BL", + "fr-CA", + "fr-CD", + "fr-CF", + "fr-CG", + "fr-CH", + "fr-CI", + "fr-CM", + "fr-DJ", + "fr-DZ", + "fr-FR", + "fr-GA", + "fr-GF", + "fr-GN", + "fr-GP", + "fr-GQ", + "fr-HT", + "fr-KM", + "fr-LU", + "fr-MA", + "fr-MC", + "fr-MF", + "fr-MG", + "fr-ML", + "fr-MQ", + "fr-MR", + "fr-MU", + "fr-NC", + "fr-NE", + "fr-PF", + "fr-RE", + "fr-RW", + "fr-SC", + "fr-SN", + "fr-SY", + "fr-TD", + "fr-TG", + "fr-TN", + "fr-VU", + "fr-YT", + "ga", + "ga-IE", + "gl", + "gl-ES", + "gu", + "gu-IN", + "he", + "he-IL", + "hi", + "hi-IN", + "hr", + "hr-BA", + "hr-HR", + "hu", + "hu-HU", + "hy", + "hy-AM", + "id", + "id-ID", + "is", + "is-IS", + "it", + "it-CH", + "it-IT", + "it-SM", + "ja", + "ja-JP", + "kk", + "kk-Cyrl", + "kk-Cyrl-KZ", + "km", + "km-KH", + "kn", + "kn-IN", + "ko", + "ko-KP", + "ko-KR", + "lt", + "lt-LT", + "lv", + "lv-LV", + "mk", + "mk-MK", + "ml", + "ml-IN", + "mr", + "mr-IN", + "nb", + "nb-NO", + "nl", + "nl-AW", + "nl-BE", + "nl-CW", + "nl-NL", + "nl-SR", + "nl-SX", + "nn", + "nn-NO", + "or", + "or-IN", + "pa", + "pa-Arab", + "pa-Arab-PK", + "pa-Guru", + "pa-Guru-IN", + "pl", + "pl-PL", + "pt", + "pt-AO", + "pt-BR", + "pt-CV", + "pt-GW", + "pt-MO", + "pt-MZ", + "pt-PT", + "pt-ST", + "pt-TL", + "rm", + "rm-CH", + "ro", + "ro-MD", + "ro-RO", + "ru", + "ru-BY", + "ru-KG", + "ru-KZ", + "ru-MD", + "ru-RU", + "ru-UA", + "si", + "si-LK", + "sk", + "sk-SK", + "sl", + "sl-SI", + "sq", + "sq-AL", + "sq-MK", + "sr", + "sr-Cyrl", + "sr-Cyrl-BA", + "sr-Cyrl-ME", + "sr-Cyrl-RS", + "sr-Latn", + "sr-Latn-BA", + "sr-Latn-ME", + "sr-Latn-RS", + "sv", + "sv-AX", + "sv-FI", + "sv-SE", + "te", + "te-IN", + "th", + "th-TH", + "tr", + "tr-CY", + "tr-TR", + "uk", + "uk-UA", + "vi", + "vi-VN", + "zh", + "zh-Hans", + "zh-Hans-CN", + "zh-Hans-HK", + "zh-Hans-MO", + "zh-Hans-SG", + "zh-Hant", + "zh-Hant-HK", + "zh-Hant-MO", + "zh-Hant-TW", +]; + +var count = Intl.NumberFormat.supportedLocalesOf(locales).length; + +reportCompare(locales.length, count, "Number of supported locales in Intl.NumberFormat"); diff --git a/js/src/tests/non262/Intl/NumberFormat/toStringTag.js b/js/src/tests/non262/Intl/NumberFormat/toStringTag.js new file mode 100644 index 0000000000..9ad0901a0b --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/toStringTag.js @@ -0,0 +1,29 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var desc = Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, Symbol.toStringTag); + +assertEq(desc !== undefined, true); +assertEq(desc.value, "Intl.NumberFormat"); +assertEq(desc.writable, false); +assertEq(desc.enumerable, false); +assertEq(desc.configurable, true); + +assertEq(Object.prototype.toString.call(Intl.NumberFormat.prototype), "[object Intl.NumberFormat]"); +assertEq(Object.prototype.toString.call(new Intl.NumberFormat), "[object Intl.NumberFormat]"); + +Object.defineProperty(Intl.NumberFormat.prototype, Symbol.toStringTag, {value: "NumberFormat"}); + +assertEq(Object.prototype.toString.call(Intl.NumberFormat.prototype), "[object NumberFormat]"); +assertEq(Object.prototype.toString.call(new Intl.NumberFormat), "[object NumberFormat]"); + +delete Intl.NumberFormat.prototype[Symbol.toStringTag]; + +assertEq(Object.prototype.toString.call(Intl.NumberFormat.prototype), "[object Object]"); +assertEq(Object.prototype.toString.call(new Intl.NumberFormat), "[object Object]"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/trailing-zero-display.js b/js/src/tests/non262/Intl/NumberFormat/trailing-zero-display.js new file mode 100644 index 0000000000..666053971d --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/trailing-zero-display.js @@ -0,0 +1,98 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// "stripIfInteger" with fractional digits. +{ + let nf1 = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + }); + + let nf2 = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + trailingZeroDisplay: "auto", + }); + + let nf3 = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + trailingZeroDisplay: "stripIfInteger", + }); + + assertEq(nf1.resolvedOptions().trailingZeroDisplay, "auto"); + assertEq(nf2.resolvedOptions().trailingZeroDisplay, "auto"); + assertEq(nf3.resolvedOptions().trailingZeroDisplay, "stripIfInteger"); + + assertEq(nf1.format(123), "123.00"); + assertEq(nf2.format(123), "123.00"); + assertEq(nf3.format(123), "123"); +} + +// "stripIfInteger" with significand digits. +{ + let nf1 = new Intl.NumberFormat("en", { + minimumSignificantDigits: 2, + }); + + let nf2 = new Intl.NumberFormat("en", { + minimumSignificantDigits: 2, + trailingZeroDisplay: "auto", + }); + + let nf3 = new Intl.NumberFormat("en", { + minimumSignificantDigits: 2, + trailingZeroDisplay: "stripIfInteger", + }); + + assertEq(nf1.resolvedOptions().trailingZeroDisplay, "auto"); + assertEq(nf2.resolvedOptions().trailingZeroDisplay, "auto"); + assertEq(nf3.resolvedOptions().trailingZeroDisplay, "stripIfInteger"); + + assertEq(nf1.format(1), "1.0"); + assertEq(nf2.format(1), "1.0"); + assertEq(nf3.format(1), "1"); +} + +// "stripIfInteger" with rounding increment. +{ + let nf1 = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + roundingIncrement: 5, + }); + let nf2 = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + roundingIncrement: 5, + trailingZeroDisplay: "auto", + }); + let nf3 = new Intl.NumberFormat("en", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + roundingIncrement: 5, + trailingZeroDisplay: "stripIfInteger", + }); + + assertEq(nf1.resolvedOptions().trailingZeroDisplay, "auto"); + assertEq(nf2.resolvedOptions().trailingZeroDisplay, "auto"); + assertEq(nf3.resolvedOptions().trailingZeroDisplay, "stripIfInteger"); + + // NB: Tests 1.975 twice b/c of <https://unicode-org.atlassian.net/browse/ICU-21674>. + + assertEq(nf1.format(1.975), "2.00"); + assertEq(nf1.format(1.97), "1.95"); + assertEq(nf1.format(1.975), "2.00"); + + assertEq(nf2.format(1.975), "2.00"); + assertEq(nf2.format(1.97), "1.95"); + assertEq(nf2.format(1.975), "2.00"); + + assertEq(nf3.format(1.975), "2"); + assertEq(nf3.format(1.97), "1.95"); + assertEq(nf3.format(1.975), "2"); +} + +// Invalid values. +for (let trailingZeroDisplay of ["", "true", true, "none", "yes", "no"]){ + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {trailingZeroDisplay}), RangeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js b/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js new file mode 100644 index 0000000000..2aad94b98e --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js @@ -0,0 +1,65 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Generated by make_intl_data.py. DO NOT EDIT. + +const sanctionedSimpleUnitIdentifiers = [ + "acre", + "bit", + "byte", + "celsius", + "centimeter", + "day", + "degree", + "fahrenheit", + "fluid-ounce", + "foot", + "gallon", + "gigabit", + "gigabyte", + "gram", + "hectare", + "hour", + "inch", + "kilobit", + "kilobyte", + "kilogram", + "kilometer", + "liter", + "megabit", + "megabyte", + "meter", + "microsecond", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "nanosecond", + "ounce", + "percent", + "petabyte", + "pound", + "second", + "stone", + "terabit", + "terabyte", + "week", + "yard", + "year" +]; + +// Test all simple unit identifier combinations are allowed. + +for (const numerator of sanctionedSimpleUnitIdentifiers) { + for (const denominator of sanctionedSimpleUnitIdentifiers) { + const unit = `${numerator}-per-${denominator}`; + const nf = new Intl.NumberFormat("en", {style: "unit", unit}); + + assertEq(nf.format(1), nf.formatToParts(1).map(p => p.value).join("")); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js b/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js new file mode 100644 index 0000000000..9292c8ac73 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js @@ -0,0 +1,90 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Generated by make_intl_data.py. DO NOT EDIT. + +const sanctionedSimpleUnitIdentifiers = [ + "acre", + "bit", + "byte", + "celsius", + "centimeter", + "day", + "degree", + "fahrenheit", + "fluid-ounce", + "foot", + "gallon", + "gigabit", + "gigabyte", + "gram", + "hectare", + "hour", + "inch", + "kilobit", + "kilobyte", + "kilogram", + "kilometer", + "liter", + "megabit", + "megabyte", + "meter", + "microsecond", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "nanosecond", + "ounce", + "percent", + "petabyte", + "pound", + "second", + "stone", + "terabit", + "terabyte", + "week", + "yard", + "year" +]; + +// Test only English and Chinese to keep the overall runtime reasonable. +// +// Chinese is included because it contains more than one "unit" element for +// certain unit combinations. +const locales = ["en", "zh"]; + +// Plural rules for English only differentiate between "one" and "other". Plural +// rules for Chinese only use "other". That means we only need to test two values +// per unit. +const values = [0, 1]; + +// Ensure unit formatters contain at least one "unit" element. + +for (const locale of locales) { + for (const unit of sanctionedSimpleUnitIdentifiers) { + const nf = new Intl.NumberFormat(locale, {style: "unit", unit}); + + for (const value of values) { + assertEq(nf.formatToParts(value).some(e => e.type === "unit"), true, + `locale=${locale}, unit=${unit}`); + } + } + + for (const numerator of sanctionedSimpleUnitIdentifiers) { + for (const denominator of sanctionedSimpleUnitIdentifiers) { + const unit = `${numerator}-per-${denominator}`; + const nf = new Intl.NumberFormat(locale, {style: "unit", unit}); + + for (const value of values) { + assertEq(nf.formatToParts(value).some(e => e.type === "unit"), true, + `locale=${locale}, unit=${unit}`); + } + } + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js b/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js new file mode 100644 index 0000000000..8eca3b58cf --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js @@ -0,0 +1,267 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Generated by make_intl_data.py. DO NOT EDIT. + +const sanctionedSimpleUnitIdentifiers = [ + "acre", + "bit", + "byte", + "celsius", + "centimeter", + "day", + "degree", + "fahrenheit", + "fluid-ounce", + "foot", + "gallon", + "gigabit", + "gigabyte", + "gram", + "hectare", + "hour", + "inch", + "kilobit", + "kilobyte", + "kilogram", + "kilometer", + "liter", + "megabit", + "megabyte", + "meter", + "microsecond", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "nanosecond", + "ounce", + "percent", + "petabyte", + "pound", + "second", + "stone", + "terabit", + "terabyte", + "week", + "yard", + "year" +]; + +const allUnits = [ + "acceleration-g-force", + "acceleration-meter-per-square-second", + "angle-arc-minute", + "angle-arc-second", + "angle-degree", + "angle-radian", + "angle-revolution", + "area-acre", + "area-dunam", + "area-hectare", + "area-square-centimeter", + "area-square-foot", + "area-square-inch", + "area-square-kilometer", + "area-square-meter", + "area-square-mile", + "area-square-yard", + "concentr-item", + "concentr-karat", + "concentr-milligram-ofglucose-per-deciliter", + "concentr-millimole-per-liter", + "concentr-mole", + "concentr-percent", + "concentr-permille", + "concentr-permillion", + "concentr-permyriad", + "consumption-liter-per-100-kilometer", + "consumption-liter-per-kilometer", + "consumption-mile-per-gallon", + "consumption-mile-per-gallon-imperial", + "digital-bit", + "digital-byte", + "digital-gigabit", + "digital-gigabyte", + "digital-kilobit", + "digital-kilobyte", + "digital-megabit", + "digital-megabyte", + "digital-petabyte", + "digital-terabit", + "digital-terabyte", + "duration-century", + "duration-day", + "duration-day-person", + "duration-decade", + "duration-hour", + "duration-microsecond", + "duration-millisecond", + "duration-minute", + "duration-month", + "duration-month-person", + "duration-nanosecond", + "duration-quarter", + "duration-second", + "duration-week", + "duration-week-person", + "duration-year", + "duration-year-person", + "electric-ampere", + "electric-milliampere", + "electric-ohm", + "electric-volt", + "energy-british-thermal-unit", + "energy-calorie", + "energy-electronvolt", + "energy-foodcalorie", + "energy-joule", + "energy-kilocalorie", + "energy-kilojoule", + "energy-kilowatt-hour", + "energy-therm-us", + "force-kilowatt-hour-per-100-kilometer", + "force-newton", + "force-pound-force", + "frequency-gigahertz", + "frequency-hertz", + "frequency-kilohertz", + "frequency-megahertz", + "graphics-dot", + "graphics-dot-per-centimeter", + "graphics-dot-per-inch", + "graphics-em", + "graphics-megapixel", + "graphics-pixel", + "graphics-pixel-per-centimeter", + "graphics-pixel-per-inch", + "length-astronomical-unit", + "length-centimeter", + "length-decimeter", + "length-earth-radius", + "length-fathom", + "length-foot", + "length-furlong", + "length-inch", + "length-kilometer", + "length-light-year", + "length-meter", + "length-micrometer", + "length-mile", + "length-mile-scandinavian", + "length-millimeter", + "length-nanometer", + "length-nautical-mile", + "length-parsec", + "length-picometer", + "length-point", + "length-solar-radius", + "length-yard", + "light-candela", + "light-lumen", + "light-lux", + "light-solar-luminosity", + "mass-carat", + "mass-dalton", + "mass-earth-mass", + "mass-grain", + "mass-gram", + "mass-kilogram", + "mass-microgram", + "mass-milligram", + "mass-ounce", + "mass-ounce-troy", + "mass-pound", + "mass-solar-mass", + "mass-stone", + "mass-ton", + "mass-tonne", + "power-gigawatt", + "power-horsepower", + "power-kilowatt", + "power-megawatt", + "power-milliwatt", + "power-watt", + "pressure-atmosphere", + "pressure-bar", + "pressure-hectopascal", + "pressure-inch-ofhg", + "pressure-kilopascal", + "pressure-megapascal", + "pressure-millibar", + "pressure-millimeter-ofhg", + "pressure-pascal", + "pressure-pound-force-per-square-inch", + "speed-kilometer-per-hour", + "speed-knot", + "speed-meter-per-second", + "speed-mile-per-hour", + "temperature-celsius", + "temperature-fahrenheit", + "temperature-generic", + "temperature-kelvin", + "torque-newton-meter", + "torque-pound-force-foot", + "volume-acre-foot", + "volume-barrel", + "volume-bushel", + "volume-centiliter", + "volume-cubic-centimeter", + "volume-cubic-foot", + "volume-cubic-inch", + "volume-cubic-kilometer", + "volume-cubic-meter", + "volume-cubic-mile", + "volume-cubic-yard", + "volume-cup", + "volume-cup-metric", + "volume-deciliter", + "volume-dessert-spoon", + "volume-dessert-spoon-imperial", + "volume-dram", + "volume-drop", + "volume-fluid-ounce", + "volume-fluid-ounce-imperial", + "volume-gallon", + "volume-gallon-imperial", + "volume-hectoliter", + "volume-jigger", + "volume-liter", + "volume-megaliter", + "volume-milliliter", + "volume-pinch", + "volume-pint", + "volume-pint-metric", + "volume-quart", + "volume-quart-imperial", + "volume-tablespoon", + "volume-teaspoon" +]; + +// Test only sanctioned unit identifiers are allowed. + +for (const typeAndUnit of allUnits) { + const [_, type, unit] = typeAndUnit.match(/(\w+)-(.+)/); + + let allowed; + if (unit.includes("-per-")) { + const [numerator, denominator] = unit.split("-per-"); + allowed = sanctionedSimpleUnitIdentifiers.includes(numerator) && + sanctionedSimpleUnitIdentifiers.includes(denominator); + } else { + allowed = sanctionedSimpleUnitIdentifiers.includes(unit); + } + + if (allowed) { + const nf = new Intl.NumberFormat("en", {style: "unit", unit}); + assertEq(nf.format(1), nf.formatToParts(1).map(p => p.value).join("")); + } else { + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {style: "unit", unit}), + RangeError, `Missing error for "${typeAndUnit}"`); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/unit.js b/js/src/tests/non262/Intl/NumberFormat/unit.js new file mode 100644 index 0000000000..7abc5142e0 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const { + Nan, Inf, Integer, MinusSign, PlusSign, Literal, + Unit +} = NumberFormatParts; + +const testcases = [ + { + locale: "en", + options: { + style: "unit", + unit: "meter", + unitDisplay: "short", + }, + values: [ + {value: +0, string: "0 m", parts: [Integer("0"), Literal(" "), Unit("m")]}, + {value: -0, string: "-0 m", parts: [MinusSign("-"), Integer("0"), Literal(" "), Unit("m")]}, + {value: 0n, string: "0 m", parts: [Integer("0"), Literal(" "), Unit("m")]}, + + {value: 1, string: "1 m", parts: [Integer("1"), Literal(" "), Unit("m")]}, + {value: -1, string: "-1 m", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("m")]}, + {value: 1n, string: "1 m", parts: [Integer("1"), Literal(" "), Unit("m")]}, + {value: -1n, string: "-1 m", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("m")]}, + + {value: Infinity, string: "∞ m", parts: [Inf("∞"), Literal(" "), Unit("m")]}, + {value: -Infinity, string: "-∞ m", parts: [MinusSign("-"), Inf("∞"), Literal(" "), Unit("m")]}, + + {value: NaN, string: "NaN m", parts: [Nan("NaN"), Literal(" "), Unit("m")]}, + {value: -NaN, string: "NaN m", parts: [Nan("NaN"), Literal(" "), Unit("m")]}, + ], + }, + + { + locale: "en", + options: { + style: "unit", + unit: "meter", + unitDisplay: "narrow", + }, + values: [ + {value: +0, string: "0m", parts: [Integer("0"), Unit("m")]}, + {value: -0, string: "-0m", parts: [MinusSign("-"), Integer("0"), Unit("m")]}, + {value: 0n, string: "0m", parts: [Integer("0"), Unit("m")]}, + + {value: 1, string: "1m", parts: [Integer("1"), Unit("m")]}, + {value: -1, string: "-1m", parts: [MinusSign("-"), Integer("1"), Unit("m")]}, + {value: 1n, string: "1m", parts: [Integer("1"), Unit("m")]}, + {value: -1n, string: "-1m", parts: [MinusSign("-"), Integer("1"), Unit("m")]}, + + {value: Infinity, string: "∞m", parts: [Inf("∞"), Unit("m")]}, + {value: -Infinity, string: "-∞m", parts: [MinusSign("-"), Inf("∞"), Unit("m")]}, + + {value: NaN, string: "NaNm", parts: [Nan("NaN"), Unit("m")]}, + {value: -NaN, string: "NaNm", parts: [Nan("NaN"), Unit("m")]}, + ], + }, + + { + locale: "en", + options: { + style: "unit", + unit: "meter", + unitDisplay: "long", + }, + values: [ + {value: +0, string: "0 meters", parts: [Integer("0"), Literal(" "), Unit("meters")]}, + {value: -0, string: "-0 meters", parts: [MinusSign("-"), Integer("0"), Literal(" "), Unit("meters")]}, + {value: 0n, string: "0 meters", parts: [Integer("0"), Literal(" "), Unit("meters")]}, + + {value: 1, string: "1 meter", parts: [Integer("1"), Literal(" "), Unit("meter")]}, + {value: -1, string: "-1 meter", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("meter")]}, + {value: 1n, string: "1 meter", parts: [Integer("1"), Literal(" "), Unit("meter")]}, + {value: -1n, string: "-1 meter", parts: [MinusSign("-"), Integer("1"), Literal(" "), Unit("meter")]}, + + {value: Infinity, string: "∞ meters", parts: [Inf("∞"), Literal(" "), Unit("meters")]}, + {value: -Infinity, string: "-∞ meters", parts: [MinusSign("-"), Inf("∞"), Literal(" "), Unit("meters")]}, + + {value: NaN, string: "NaN meters", parts: [Nan("NaN"), Literal(" "), Unit("meters")]}, + {value: -NaN, string: "NaN meters", parts: [Nan("NaN"), Literal(" "), Unit("meters")]}, + ], + }, + + // Ensure the correct compound unit is automatically selected by ICU. For + // example instead of "50 chilometri al orari", 50 km/h should return + // "50 chilometri orari" in Italian. + + { + locale: "it", + options: { + style: "unit", + unit: "kilometer-per-hour", + unitDisplay: "long", + }, + values: [ + {value: 50, string: "50 chilometri orari", parts: [Integer("50"), Literal(" "), Unit("chilometri orari")]}, + ], + }, +]; + +runNumberFormattingTestcases(testcases); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/unwrapping.js b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js new file mode 100644 index 0000000000..df0b15d6fc --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js @@ -0,0 +1,248 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Test UnwrapNumberFormat operation. + +const numberFormatFunctions = []; +numberFormatFunctions.push({ + function: Intl.NumberFormat.prototype.resolvedOptions, + unwrap: true, +}); +numberFormatFunctions.push({ + function: Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get, + unwrap: true, +}); +numberFormatFunctions.push({ + function: Intl.NumberFormat.prototype.formatToParts, + unwrap: false, +}); + +function IsIntlService(c) { + return typeof c === "function" && + c.hasOwnProperty("prototype") && + c.prototype.hasOwnProperty("resolvedOptions"); +} + +function IsObject(o) { + return Object(o) === o; +} + +function IsPrimitive(o) { + return Object(o) !== o; +} + +function intlObjects(ctor) { + let args = []; + if (ctor === Intl.DisplayNames) { + // Intl.DisplayNames can't be constructed without any arguments. + args = [undefined, {type: "language"}]; + } + + return [ + // Instance of an Intl constructor. + new ctor(...args), + + // Instance of a subclassed Intl constructor. + new class extends ctor {}(...args), + + // Intl object not inheriting from its default prototype. + Object.setPrototypeOf(new ctor(...args), Object.prototype), + ]; +} + +function thisValues(C) { + const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService); + + return [ + // Primitive values. + ...[undefined, null, true, "abc", Symbol(), 123], + + // Object values. + ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})], + + // Intl objects. + ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)), + + // Object inheriting from an Intl constructor prototype. + ...intlConstructors.map(ctor => Object.create(ctor.prototype)), + ]; +} + +const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0]; + +// Test Intl.NumberFormat.prototype methods. +for (let {function: numberFormatFunction, unwrap} of numberFormatFunctions) { + // Test a TypeError is thrown when the this-value isn't an initialized + // Intl.NumberFormat instance. + for (let thisValue of thisValues(Intl.NumberFormat)) { + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + } + + // And test no error is thrown for initialized Intl.NumberFormat instances. + for (let thisValue of intlObjects(Intl.NumberFormat)) { + numberFormatFunction.call(thisValue); + } + + // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above. + for (let thisValue of thisValues(Intl.NumberFormat)) { + assertThrowsInstanceOf(() => numberFormatFunction.call({ + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: thisValue, + }), TypeError); + } + + for (let thisValue of intlObjects(Intl.NumberFormat)) { + let obj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: thisValue, + }; + if (unwrap) { + numberFormatFunction.call(obj); + } else { + assertThrowsInstanceOf(() => numberFormatFunction.call(obj), TypeError); + } + } + + // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances. + for (let thisValue of intlObjects(Intl.NumberFormat)) { + Object.defineProperty(thisValue, intlFallbackSymbol, { + get() { assertEq(false, true); } + }); + numberFormatFunction.call(thisValue); + } + + // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { + if (Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) + continue; + Object.defineProperty(thisValue, intlFallbackSymbol, { + get() { assertEq(false, true); } + }); + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + } + + // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance] + // so it always returns |true|. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { + let isPrototypeOf = Intl.NumberFormat.prototype.isPrototypeOf(thisValue); + let hasInstanceCalled = false, symbolGetterCalled = false; + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { + assertEq(hasInstanceCalled, false); + hasInstanceCalled = true; + return true; + }, configurable: true + }); + Object.defineProperty(thisValue, intlFallbackSymbol, { + get() { + assertEq(symbolGetterCalled, false); + symbolGetterCalled = true; + return null; + }, configurable: true + }); + + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + + delete Intl.NumberFormat[Symbol.hasInstance]; + + assertEq(hasInstanceCalled, false); + assertEq(symbolGetterCalled, unwrap && isPrototypeOf); + } + + // Test with primitive values. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsPrimitive)) { + // Ensure @@hasInstance is not called. + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { assertEq(true, false); }, configurable: true + }); + let isUndefinedOrNull = thisValue === undefined || thisValue === null; + let symbolHolder; + if (!isUndefinedOrNull) { + // Ensure the fallback symbol isn't retrieved from the primitive wrapper prototype. + symbolHolder = Object.getPrototypeOf(thisValue); + Object.defineProperty(symbolHolder, intlFallbackSymbol, { + get() { assertEq(true, false); }, configurable: true + }); + } + + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + + delete Intl.NumberFormat[Symbol.hasInstance]; + if (!isUndefinedOrNull) + delete symbolHolder[intlFallbackSymbol]; + } +} + +// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances. +{ + // An actual Intl.NumberFormat instance. + let numberFormat = new Intl.NumberFormat(); + + // An object initialized as a NumberFormat instance. + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + + // Object with [[FallbackSymbol]] set to NumberFormat instance. + let fakeObj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: numberFormat, + }; + + for (let number of [0, 1, 1.5, Infinity, NaN]) { + let expected = numberFormat.format(number); + assertEq(thisValue.format(number), expected); + assertEq(thisValue[intlFallbackSymbol].format(number), expected); + assertEq(fakeObj.format(number), expected); + } +} + +// Ensure formatToParts() doesn't use the fallback semantics. +{ + let formatToParts = Intl.NumberFormat.prototype.formatToParts; + + // An object initialized as a NumberFormat instance. + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + assertThrowsInstanceOf(() => formatToParts.call(thisValue), TypeError); + + // Object with [[FallbackSymbol]] set to NumberFormat instance. + let fakeObj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: new Intl.NumberFormat(), + }; + assertThrowsInstanceOf(() => formatToParts.call(fakeObj), TypeError); +} + +// Test resolvedOptions() returns the same results. +{ + // An actual Intl.NumberFormat instance. + let numberFormat = new Intl.NumberFormat(); + + // An object initialized as a NumberFormat instance. + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + + // Object with [[FallbackSymbol]] set to NumberFormat instance. + let fakeObj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: numberFormat, + }; + + function assertEqOptions(actual, expected) { + actual = Object.entries(actual); + expected = Object.entries(expected); + + assertEq(actual.length, expected.length, "options count mismatch"); + for (var i = 0; i < expected.length; i++) { + assertEq(actual[i][0], expected[i][0], "key mismatch at " + i); + assertEq(actual[i][1], expected[i][1], "value mismatch at " + i); + } + } + + let expected = numberFormat.resolvedOptions(); + assertEqOptions(thisValue.resolvedOptions(), expected); + assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected); + assertEqOptions(fakeObj.resolvedOptions(), expected); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/use-grouping-bool-string.js b/js/src/tests/non262/Intl/NumberFormat/use-grouping-bool-string.js new file mode 100644 index 0000000000..4216c874f0 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/use-grouping-bool-string.js @@ -0,0 +1,10 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +let nf1 = new Intl.NumberFormat("en", {useGrouping: "true"}); +assertEq(nf1.format(1000), "1,000"); + +let nf2 = new Intl.NumberFormat("en", {useGrouping: "false"}); +assertEq(nf2.format(1000), "1,000"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/NumberFormat/use-grouping.js b/js/src/tests/non262/Intl/NumberFormat/use-grouping.js new file mode 100644 index 0000000000..b10398dc67 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/use-grouping.js @@ -0,0 +1,101 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +const tests = { + // minimumGroupingDigits is one for "en" (English). + "en": { + values: [ + { + value: 1000, + useGroupings: { + auto: "1,000", + always: "1,000", + min2: "1000", + "": "1000", + }, + }, + { + value: 10000, + useGroupings: { + auto: "10,000", + always: "10,000", + min2: "10,000", + "": "10000", + }, + }, + ], + }, + + // minimumGroupingDigits is two for "pl" (Polish). + "pl": { + values: [ + { + value: 1000, + useGroupings: { + auto: "1000", + always: "1 000", + min2: "1000", + "": "1000", + }, + }, + { + value: 10000, + useGroupings: { + auto: "10 000", + always: "10 000", + min2: "10 000", + "": "10000", + }, + }, + ], + }, +}; + +for (let [locale, {options = {}, values}] of Object.entries(tests)) { + for (let {value, useGroupings} of values) { + for (let [useGrouping, expected] of Object.entries(useGroupings)) { + let nf = new Intl.NumberFormat(locale, {...options, useGrouping}); + assertEq(nf.format(value), expected, `locale=${locale}, value=${value}, useGrouping=${useGrouping}`); + } + } +} + +// Resolved options. +for (let [useGrouping, expected] of [ + [false, false], + ["", false], + [0, false], + [null, false], + + ["auto", "auto"], + [undefined, "auto"], + ["true", "auto"], + ["false", "auto"], + + ["always", "always"], + [true, "always"], + + ["min2", "min2"], +]) { + let nf = new Intl.NumberFormat("en", {useGrouping}); + assertEq(nf.resolvedOptions().useGrouping , expected); +} + +// Throws a RangeError for unsupported values. +for (let useGrouping of [ + "none", + "yes", + "no", + {}, + 123, + 123n, +]) { + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {useGrouping}), RangeError); +} + +// Throws a TypeError if ToString fails. +for (let useGrouping of [Object.create(null), Symbol()]) { + assertThrowsInstanceOf(() => new Intl.NumberFormat("en", {useGrouping}), TypeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); |