diff options
Diffstat (limited to 'js/src/tests/non262/Intl/NumberFormat/unwrapping.js')
-rw-r--r-- | js/src/tests/non262/Intl/NumberFormat/unwrapping.js | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/js/src/tests/non262/Intl/NumberFormat/unwrapping.js b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js new file mode 100644 index 0000000000..df0b15d6fc --- /dev/null +++ b/js/src/tests/non262/Intl/NumberFormat/unwrapping.js @@ -0,0 +1,248 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +// Test UnwrapNumberFormat operation. + +const numberFormatFunctions = []; +numberFormatFunctions.push({ + function: Intl.NumberFormat.prototype.resolvedOptions, + unwrap: true, +}); +numberFormatFunctions.push({ + function: Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get, + unwrap: true, +}); +numberFormatFunctions.push({ + function: Intl.NumberFormat.prototype.formatToParts, + unwrap: false, +}); + +function IsIntlService(c) { + return typeof c === "function" && + c.hasOwnProperty("prototype") && + c.prototype.hasOwnProperty("resolvedOptions"); +} + +function IsObject(o) { + return Object(o) === o; +} + +function IsPrimitive(o) { + return Object(o) !== o; +} + +function intlObjects(ctor) { + let args = []; + if (ctor === Intl.DisplayNames) { + // Intl.DisplayNames can't be constructed without any arguments. + args = [undefined, {type: "language"}]; + } + + return [ + // Instance of an Intl constructor. + new ctor(...args), + + // Instance of a subclassed Intl constructor. + new class extends ctor {}(...args), + + // Intl object not inheriting from its default prototype. + Object.setPrototypeOf(new ctor(...args), Object.prototype), + ]; +} + +function thisValues(C) { + const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService); + + return [ + // Primitive values. + ...[undefined, null, true, "abc", Symbol(), 123], + + // Object values. + ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})], + + // Intl objects. + ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)), + + // Object inheriting from an Intl constructor prototype. + ...intlConstructors.map(ctor => Object.create(ctor.prototype)), + ]; +} + +const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0]; + +// Test Intl.NumberFormat.prototype methods. +for (let {function: numberFormatFunction, unwrap} of numberFormatFunctions) { + // Test a TypeError is thrown when the this-value isn't an initialized + // Intl.NumberFormat instance. + for (let thisValue of thisValues(Intl.NumberFormat)) { + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + } + + // And test no error is thrown for initialized Intl.NumberFormat instances. + for (let thisValue of intlObjects(Intl.NumberFormat)) { + numberFormatFunction.call(thisValue); + } + + // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above. + for (let thisValue of thisValues(Intl.NumberFormat)) { + assertThrowsInstanceOf(() => numberFormatFunction.call({ + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: thisValue, + }), TypeError); + } + + for (let thisValue of intlObjects(Intl.NumberFormat)) { + let obj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: thisValue, + }; + if (unwrap) { + numberFormatFunction.call(obj); + } else { + assertThrowsInstanceOf(() => numberFormatFunction.call(obj), TypeError); + } + } + + // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances. + for (let thisValue of intlObjects(Intl.NumberFormat)) { + Object.defineProperty(thisValue, intlFallbackSymbol, { + get() { assertEq(false, true); } + }); + numberFormatFunction.call(thisValue); + } + + // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { + if (Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) + continue; + Object.defineProperty(thisValue, intlFallbackSymbol, { + get() { assertEq(false, true); } + }); + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + } + + // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance] + // so it always returns |true|. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { + let isPrototypeOf = Intl.NumberFormat.prototype.isPrototypeOf(thisValue); + let hasInstanceCalled = false, symbolGetterCalled = false; + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { + assertEq(hasInstanceCalled, false); + hasInstanceCalled = true; + return true; + }, configurable: true + }); + Object.defineProperty(thisValue, intlFallbackSymbol, { + get() { + assertEq(symbolGetterCalled, false); + symbolGetterCalled = true; + return null; + }, configurable: true + }); + + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + + delete Intl.NumberFormat[Symbol.hasInstance]; + + assertEq(hasInstanceCalled, false); + assertEq(symbolGetterCalled, unwrap && isPrototypeOf); + } + + // Test with primitive values. + for (let thisValue of thisValues(Intl.NumberFormat).filter(IsPrimitive)) { + // Ensure @@hasInstance is not called. + Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { + value() { assertEq(true, false); }, configurable: true + }); + let isUndefinedOrNull = thisValue === undefined || thisValue === null; + let symbolHolder; + if (!isUndefinedOrNull) { + // Ensure the fallback symbol isn't retrieved from the primitive wrapper prototype. + symbolHolder = Object.getPrototypeOf(thisValue); + Object.defineProperty(symbolHolder, intlFallbackSymbol, { + get() { assertEq(true, false); }, configurable: true + }); + } + + assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); + + delete Intl.NumberFormat[Symbol.hasInstance]; + if (!isUndefinedOrNull) + delete symbolHolder[intlFallbackSymbol]; + } +} + +// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances. +{ + // An actual Intl.NumberFormat instance. + let numberFormat = new Intl.NumberFormat(); + + // An object initialized as a NumberFormat instance. + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + + // Object with [[FallbackSymbol]] set to NumberFormat instance. + let fakeObj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: numberFormat, + }; + + for (let number of [0, 1, 1.5, Infinity, NaN]) { + let expected = numberFormat.format(number); + assertEq(thisValue.format(number), expected); + assertEq(thisValue[intlFallbackSymbol].format(number), expected); + assertEq(fakeObj.format(number), expected); + } +} + +// Ensure formatToParts() doesn't use the fallback semantics. +{ + let formatToParts = Intl.NumberFormat.prototype.formatToParts; + + // An object initialized as a NumberFormat instance. + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + assertThrowsInstanceOf(() => formatToParts.call(thisValue), TypeError); + + // Object with [[FallbackSymbol]] set to NumberFormat instance. + let fakeObj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: new Intl.NumberFormat(), + }; + assertThrowsInstanceOf(() => formatToParts.call(fakeObj), TypeError); +} + +// Test resolvedOptions() returns the same results. +{ + // An actual Intl.NumberFormat instance. + let numberFormat = new Intl.NumberFormat(); + + // An object initialized as a NumberFormat instance. + let thisValue = Object.create(Intl.NumberFormat.prototype); + Intl.NumberFormat.call(thisValue); + + // Object with [[FallbackSymbol]] set to NumberFormat instance. + let fakeObj = { + __proto__: Intl.NumberFormat.prototype, + [intlFallbackSymbol]: numberFormat, + }; + + function assertEqOptions(actual, expected) { + actual = Object.entries(actual); + expected = Object.entries(expected); + + assertEq(actual.length, expected.length, "options count mismatch"); + for (var i = 0; i < expected.length; i++) { + assertEq(actual[i][0], expected[i][0], "key mismatch at " + i); + assertEq(actual[i][1], expected[i][1], "value mismatch at " + i); + } + } + + let expected = numberFormat.resolvedOptions(); + assertEqOptions(thisValue.resolvedOptions(), expected); + assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected); + assertEqOptions(fakeObj.resolvedOptions(), expected); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); |