unwrapping.js (8984B)
1 // |reftest| skip-if(!this.hasOwnProperty("Intl")) 2 3 // Test UnwrapNumberFormat operation. 4 5 const numberFormatFunctions = []; 6 numberFormatFunctions.push({ 7 function: Intl.NumberFormat.prototype.resolvedOptions, 8 unwrap: true, 9 }); 10 numberFormatFunctions.push({ 11 function: Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get, 12 unwrap: true, 13 }); 14 numberFormatFunctions.push({ 15 function: Intl.NumberFormat.prototype.formatToParts, 16 unwrap: false, 17 }); 18 19 function IsIntlService(c) { 20 return typeof c === "function" && 21 c.hasOwnProperty("prototype") && 22 c.prototype.hasOwnProperty("resolvedOptions"); 23 } 24 25 function IsObject(o) { 26 return Object(o) === o; 27 } 28 29 function IsPrimitive(o) { 30 return Object(o) !== o; 31 } 32 33 function intlObjects(ctor) { 34 let args = []; 35 if (ctor === Intl.DisplayNames) { 36 // Intl.DisplayNames can't be constructed without any arguments. 37 args = [undefined, {type: "language"}]; 38 } 39 40 return [ 41 // Instance of an Intl constructor. 42 new ctor(...args), 43 44 // Instance of a subclassed Intl constructor. 45 new class extends ctor {}(...args), 46 47 // Intl object not inheriting from its default prototype. 48 Object.setPrototypeOf(new ctor(...args), Object.prototype), 49 ]; 50 } 51 52 function thisValues(C) { 53 const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService); 54 55 return [ 56 // Primitive values. 57 ...[undefined, null, true, "abc", Symbol(), 123], 58 59 // Object values. 60 ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})], 61 62 // Intl objects. 63 ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)), 64 65 // Object inheriting from an Intl constructor prototype. 66 ...intlConstructors.map(ctor => Object.create(ctor.prototype)), 67 ]; 68 } 69 70 const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0]; 71 72 // Test Intl.NumberFormat.prototype methods. 73 for (let {function: numberFormatFunction, unwrap} of numberFormatFunctions) { 74 // Test a TypeError is thrown when the this-value isn't an initialized 75 // Intl.NumberFormat instance. 76 for (let thisValue of thisValues(Intl.NumberFormat)) { 77 assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); 78 } 79 80 // And test no error is thrown for initialized Intl.NumberFormat instances. 81 for (let thisValue of intlObjects(Intl.NumberFormat)) { 82 numberFormatFunction.call(thisValue); 83 } 84 85 // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above. 86 for (let thisValue of thisValues(Intl.NumberFormat)) { 87 assertThrowsInstanceOf(() => numberFormatFunction.call({ 88 __proto__: Intl.NumberFormat.prototype, 89 [intlFallbackSymbol]: thisValue, 90 }), TypeError); 91 } 92 93 for (let thisValue of intlObjects(Intl.NumberFormat)) { 94 let obj = { 95 __proto__: Intl.NumberFormat.prototype, 96 [intlFallbackSymbol]: thisValue, 97 }; 98 if (unwrap) { 99 numberFormatFunction.call(obj); 100 } else { 101 assertThrowsInstanceOf(() => numberFormatFunction.call(obj), TypeError); 102 } 103 } 104 105 // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances. 106 for (let thisValue of intlObjects(Intl.NumberFormat)) { 107 Object.defineProperty(thisValue, intlFallbackSymbol, { 108 get() { assertEq(false, true); } 109 }); 110 numberFormatFunction.call(thisValue); 111 } 112 113 // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype. 114 for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { 115 if (Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) 116 continue; 117 Object.defineProperty(thisValue, intlFallbackSymbol, { 118 get() { assertEq(false, true); } 119 }); 120 assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); 121 } 122 123 // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance] 124 // so it always returns |true|. 125 for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) { 126 let isPrototypeOf = Intl.NumberFormat.prototype.isPrototypeOf(thisValue); 127 let hasInstanceCalled = false, symbolGetterCalled = false; 128 Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { 129 value() { 130 assertEq(hasInstanceCalled, false); 131 hasInstanceCalled = true; 132 return true; 133 }, configurable: true 134 }); 135 Object.defineProperty(thisValue, intlFallbackSymbol, { 136 get() { 137 assertEq(symbolGetterCalled, false); 138 symbolGetterCalled = true; 139 return null; 140 }, configurable: true 141 }); 142 143 assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); 144 145 delete Intl.NumberFormat[Symbol.hasInstance]; 146 147 assertEq(hasInstanceCalled, false); 148 assertEq(symbolGetterCalled, unwrap && isPrototypeOf); 149 } 150 151 // Test with primitive values. 152 for (let thisValue of thisValues(Intl.NumberFormat).filter(IsPrimitive)) { 153 // Ensure @@hasInstance is not called. 154 Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, { 155 value() { assertEq(true, false); }, configurable: true 156 }); 157 let isUndefinedOrNull = thisValue === undefined || thisValue === null; 158 let symbolHolder; 159 if (!isUndefinedOrNull) { 160 // Ensure the fallback symbol isn't retrieved from the primitive wrapper prototype. 161 symbolHolder = Object.getPrototypeOf(thisValue); 162 Object.defineProperty(symbolHolder, intlFallbackSymbol, { 163 get() { assertEq(true, false); }, configurable: true 164 }); 165 } 166 167 assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError); 168 169 delete Intl.NumberFormat[Symbol.hasInstance]; 170 if (!isUndefinedOrNull) 171 delete symbolHolder[intlFallbackSymbol]; 172 } 173 } 174 175 // Test format() returns the correct result for objects initialized as Intl.NumberFormat instances. 176 { 177 // An actual Intl.NumberFormat instance. 178 let numberFormat = new Intl.NumberFormat(); 179 180 // An object initialized as a NumberFormat instance. 181 let thisValue = Object.create(Intl.NumberFormat.prototype); 182 Intl.NumberFormat.call(thisValue); 183 184 // Object with [[FallbackSymbol]] set to NumberFormat instance. 185 let fakeObj = { 186 __proto__: Intl.NumberFormat.prototype, 187 [intlFallbackSymbol]: numberFormat, 188 }; 189 190 for (let number of [0, 1, 1.5, Infinity, NaN]) { 191 let expected = numberFormat.format(number); 192 assertEq(thisValue.format(number), expected); 193 assertEq(thisValue[intlFallbackSymbol].format(number), expected); 194 assertEq(fakeObj.format(number), expected); 195 } 196 } 197 198 // Ensure formatToParts() doesn't use the fallback semantics. 199 { 200 let formatToParts = Intl.NumberFormat.prototype.formatToParts; 201 202 // An object initialized as a NumberFormat instance. 203 let thisValue = Object.create(Intl.NumberFormat.prototype); 204 Intl.NumberFormat.call(thisValue); 205 assertThrowsInstanceOf(() => formatToParts.call(thisValue), TypeError); 206 207 // Object with [[FallbackSymbol]] set to NumberFormat instance. 208 let fakeObj = { 209 __proto__: Intl.NumberFormat.prototype, 210 [intlFallbackSymbol]: new Intl.NumberFormat(), 211 }; 212 assertThrowsInstanceOf(() => formatToParts.call(fakeObj), TypeError); 213 } 214 215 // Test resolvedOptions() returns the same results. 216 { 217 // An actual Intl.NumberFormat instance. 218 let numberFormat = new Intl.NumberFormat(); 219 220 // An object initialized as a NumberFormat instance. 221 let thisValue = Object.create(Intl.NumberFormat.prototype); 222 Intl.NumberFormat.call(thisValue); 223 224 // Object with [[FallbackSymbol]] set to NumberFormat instance. 225 let fakeObj = { 226 __proto__: Intl.NumberFormat.prototype, 227 [intlFallbackSymbol]: numberFormat, 228 }; 229 230 function assertEqOptions(actual, expected) { 231 actual = Object.entries(actual); 232 expected = Object.entries(expected); 233 234 assertEq(actual.length, expected.length, "options count mismatch"); 235 for (var i = 0; i < expected.length; i++) { 236 assertEq(actual[i][0], expected[i][0], "key mismatch at " + i); 237 assertEq(actual[i][1], expected[i][1], "value mismatch at " + i); 238 } 239 } 240 241 let expected = numberFormat.resolvedOptions(); 242 assertEqOptions(thisValue.resolvedOptions(), expected); 243 assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected); 244 assertEqOptions(fakeObj.resolvedOptions(), expected); 245 } 246 247 if (typeof reportCompare === "function") 248 reportCompare(true, true);