// |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);