diff options
Diffstat (limited to 'js/src/tests/non262/Intl/NumberFormat')
32 files changed, 3317 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..9711a4d6a9 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/call.js @@ -0,0 +1,182 @@ +// |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 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), true); + assertEq(hasInstanceCalled, true); + assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]); +} +// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false. +for (let thisValue of thisValues().filter(IsObject)) { + 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), false); + assertEq(obj instanceof Intl.NumberFormat, true); + assertEq(hasInstanceCalled, true); + assertEqArray(Object.getOwnPropertySymbols(thisValue), []); +} +// - 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..4873045685 --- /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: "$123.00", + parts: [Currency("$"), 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.js b/js/src/tests/non262/Intl/NumberFormat/format.js new file mode 100644 index 0000000000..a71a877566 --- /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), "٠٫٠٠٠ د.أ."); +assertEq(format.format(-1), "-١٫٠٠٠ د.أ."); +assertEq(format.format(123456789.123456789), "١٢٣٬٤٥٦٬٧٨٩٫١٢٣ د.أ."); + +// 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/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..576c45858c --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/notation-compact-long.js @@ -0,0 +1,134 @@ +// |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: 100000e11, string: "10,000\u5146", parts: [Integer("10"), Group(","), Integer("000"), Compact("\u5146")]}, + ], + }, +]; + +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-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/shell.js b/js/src/tests/non262/Intl/NumberFormat/shell.js new file mode 100644 index 0000000000..da786bbcc2 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/shell.js @@ -0,0 +1,48 @@ +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 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 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/unit-compound-combinations.js b/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js new file mode 100644 index 0000000000..5160d08d74 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit-compound-combinations.js @@ -0,0 +1,63 @@ +// |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", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "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..fc433711e7 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit-formatToParts-has-unit-field.js @@ -0,0 +1,88 @@ +// |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", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "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..ec1ab4beda --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unit-well-formed.js @@ -0,0 +1,250 @@ +// |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", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "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-karat", + "concentr-milligram-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-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-newton", + "force-pound-force", + "frequency-gigahertz", + "frequency-hertz", + "frequency-kilohertz", + "frequency-megahertz", + "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-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-lux", + "light-solar-luminosity", + "mass-carat", + "mass-dalton", + "mass-earth-mass", + "mass-gram", + "mass-kilogram", + "mass-metric-ton", + "mass-microgram", + "mass-milligram", + "mass-ounce", + "mass-ounce-troy", + "mass-pound", + "mass-solar-mass", + "mass-stone", + "mass-ton", + "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-fluid-ounce", + "volume-fluid-ounce-imperial", + "volume-gallon", + "volume-gallon-imperial", + "volume-hectoliter", + "volume-liter", + "volume-megaliter", + "volume-milliliter", + "volume-pint", + "volume-pint-metric", + "volume-quart", + "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..7e66c43916 --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js @@ -0,0 +1,247 @@ +// |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 |null|. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { + 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, unwrap); + assertEq(symbolGetterCalled, unwrap); + } + + // 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); |