|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* |
|
6 * Undo at least the damage done by taintArray so that the jstests |
|
7 * harness won't die. The properties added to Object.prototype by the various |
|
8 * tests have names that are less likely to cause trouble. |
|
9 */ |
|
10 setRestoreFunction((function () { |
|
11 var Array_indexOf = Array.prototype.indexOf; |
|
12 var Array_join = Array.prototype.join; |
|
13 var Array_push = Array.prototype.push; |
|
14 var Array_slice = Array.prototype.slice; |
|
15 var Array_sort = Array.prototype.sort; |
|
16 return function () { |
|
17 delete Array.prototype["0"]; |
|
18 Array.prototype.indexOf = Array_indexOf; |
|
19 Array.prototype.join = Array_join; |
|
20 Array.prototype.push = Array_push; |
|
21 Array.prototype.slice = Array_slice; |
|
22 Array.prototype.sort = Array_sort; |
|
23 }; |
|
24 }())); |
|
25 // Copyright 2012 Mozilla Corporation. All rights reserved. |
|
26 // This code is governed by the BSD license found in the LICENSE file. |
|
27 |
|
28 /** |
|
29 * @description Tests that obj meets the requirements for built-in objects |
|
30 * defined by the introduction of chapter 15 of the ECMAScript Language Specification. |
|
31 * @param {Object} obj the object to be tested. |
|
32 * @param {boolean} isFunction whether the specification describes obj as a function. |
|
33 * @param {boolean} isConstructor whether the specification describes obj as a constructor. |
|
34 * @param {String[]} properties an array with the names of the built-in properties of obj, |
|
35 * excluding length, prototype, or properties with non-default attributes. |
|
36 * @param {number} length for functions only: the length specified for the function |
|
37 * or derived from the argument list. |
|
38 * @author Norbert Lindenberg |
|
39 */ |
|
40 |
|
41 function testBuiltInObject(obj, isFunction, isConstructor, properties, length) { |
|
42 |
|
43 if (obj === undefined) { |
|
44 $ERROR("Object being tested is undefined."); |
|
45 } |
|
46 |
|
47 var objString = Object.prototype.toString.call(obj); |
|
48 if (isFunction) { |
|
49 if (objString !== "[object Function]") { |
|
50 $ERROR("The [[Class]] internal property of a built-in function must be " + |
|
51 "\"Function\", but toString() returns " + objString); |
|
52 } |
|
53 } else { |
|
54 if (objString !== "[object Object]") { |
|
55 $ERROR("The [[Class]] internal property of a built-in non-function object must be " + |
|
56 "\"Object\", but toString() returns " + objString); |
|
57 } |
|
58 } |
|
59 |
|
60 if (!Object.isExtensible(obj)) { |
|
61 $ERROR("Built-in objects must be extensible."); |
|
62 } |
|
63 |
|
64 if (isFunction && Object.getPrototypeOf(obj) !== Function.prototype) { |
|
65 $ERROR("Built-in functions must have Function.prototype as their prototype."); |
|
66 } |
|
67 |
|
68 if (isConstructor && Object.getPrototypeOf(obj.prototype) !== Object.prototype) { |
|
69 $ERROR("Built-in prototype objects must have Object.prototype as their prototype."); |
|
70 } |
|
71 |
|
72 // verification of the absence of the [[Construct]] internal property has |
|
73 // been moved to the end of the test |
|
74 |
|
75 // verification of the absence of the prototype property has |
|
76 // been moved to the end of the test |
|
77 |
|
78 if (isFunction) { |
|
79 |
|
80 if (typeof obj.length !== "number" || obj.length !== Math.floor(obj.length)) { |
|
81 $ERROR("Built-in functions must have a length property with an integer value."); |
|
82 } |
|
83 |
|
84 if (obj.length !== length) { |
|
85 $ERROR("Function's length property doesn't have specified value; expected " + |
|
86 length + ", got " + obj.length + "."); |
|
87 } |
|
88 |
|
89 var desc = Object.getOwnPropertyDescriptor(obj, "length"); |
|
90 if (desc.writable) { |
|
91 $ERROR("The length property of a built-in function must not be writable."); |
|
92 } |
|
93 if (desc.enumerable) { |
|
94 $ERROR("The length property of a built-in function must not be enumerable."); |
|
95 } |
|
96 if (desc.configurable) { |
|
97 $ERROR("The length property of a built-in function must not be configurable."); |
|
98 } |
|
99 } |
|
100 |
|
101 properties.forEach(function(prop) { |
|
102 var desc = Object.getOwnPropertyDescriptor(obj, prop); |
|
103 if (desc === undefined) { |
|
104 $ERROR("Missing property " + prop + "."); |
|
105 } |
|
106 // accessor properties don't have writable attribute |
|
107 if (desc.hasOwnProperty("writable") && !desc.writable) { |
|
108 $ERROR("The " + prop + " property of this built-in function must be writable."); |
|
109 } |
|
110 if (desc.enumerable) { |
|
111 $ERROR("The " + prop + " property of this built-in function must not be enumerable."); |
|
112 } |
|
113 if (!desc.configurable) { |
|
114 $ERROR("The " + prop + " property of this built-in function must be configurable."); |
|
115 } |
|
116 }); |
|
117 |
|
118 // The remaining sections have been moved to the end of the test because |
|
119 // unbound non-constructor functions written in JavaScript cannot possibly |
|
120 // pass them, and we still want to test JavaScript implementations as much |
|
121 // as possible. |
|
122 |
|
123 var exception; |
|
124 if (isFunction && !isConstructor) { |
|
125 // this is not a complete test for the presence of [[Construct]]: |
|
126 // if it's absent, the exception must be thrown, but it may also |
|
127 // be thrown if it's present and just has preconditions related to |
|
128 // arguments or the this value that this statement doesn't meet. |
|
129 try { |
|
130 /*jshint newcap:false*/ |
|
131 var instance = new obj(); |
|
132 } catch (e) { |
|
133 exception = e; |
|
134 } |
|
135 if (exception === undefined || exception.name !== "TypeError") { |
|
136 $ERROR("Built-in functions that aren't constructors must throw TypeError when " + |
|
137 "used in a \"new\" statement."); |
|
138 } |
|
139 } |
|
140 |
|
141 if (isFunction && !isConstructor && obj.hasOwnProperty("prototype")) { |
|
142 $ERROR("Built-in functions that aren't constructors must not have a prototype property."); |
|
143 } |
|
144 |
|
145 // passed the complete test! |
|
146 return true; |
|
147 } |
|
148 |
|
149 // Copyright 2011-2012 Norbert Lindenberg. All rights reserved. |
|
150 // Copyright 2012-2013 Mozilla Corporation. All rights reserved. |
|
151 // This code is governed by the BSD license found in the LICENSE file. |
|
152 |
|
153 /** |
|
154 * This file contains shared functions for the tests in the conformance test |
|
155 * suite for the ECMAScript Internationalization API. |
|
156 * @author Norbert Lindenberg |
|
157 */ |
|
158 |
|
159 |
|
160 /** |
|
161 * @description Calls the provided function for every service constructor in |
|
162 * the Intl object, until f returns a falsy value. It returns the result of the |
|
163 * last call to f, mapped to a boolean. |
|
164 * @param {Function} f the function to call for each service constructor in |
|
165 * the Intl object. |
|
166 * @param {Function} Constructor the constructor object to test with. |
|
167 * @result {Boolean} whether the test succeeded. |
|
168 */ |
|
169 function testWithIntlConstructors(f) { |
|
170 var constructors = ["Collator", "NumberFormat", "DateTimeFormat"]; |
|
171 return constructors.every(function (constructor) { |
|
172 var Constructor = Intl[constructor]; |
|
173 var result; |
|
174 try { |
|
175 result = f(Constructor); |
|
176 } catch (e) { |
|
177 e.message += " (Testing with " + constructor + ".)"; |
|
178 throw e; |
|
179 } |
|
180 return result; |
|
181 }); |
|
182 } |
|
183 |
|
184 |
|
185 /** |
|
186 * Returns the name of the given constructor object, which must be one of |
|
187 * Intl.Collator, Intl.NumberFormat, or Intl.DateTimeFormat. |
|
188 * @param {object} Constructor a constructor |
|
189 * @return {string} the name of the constructor |
|
190 */ |
|
191 function getConstructorName(Constructor) { |
|
192 switch (Constructor) { |
|
193 case Intl.Collator: |
|
194 return "Collator"; |
|
195 case Intl.NumberFormat: |
|
196 return "NumberFormat"; |
|
197 case Intl.DateTimeFormat: |
|
198 return "DateTimeFormat"; |
|
199 default: |
|
200 $ERROR("test internal error: unknown Constructor"); |
|
201 } |
|
202 } |
|
203 |
|
204 |
|
205 /** |
|
206 * Taints a named data property of the given object by installing |
|
207 * a setter that throws an exception. |
|
208 * @param {object} obj the object whose data property to taint |
|
209 * @param {string} property the property to taint |
|
210 */ |
|
211 function taintDataProperty(obj, property) { |
|
212 Object.defineProperty(obj, property, { |
|
213 set: function(value) { |
|
214 $ERROR("Client code can adversely affect behavior: setter for " + property + "."); |
|
215 }, |
|
216 enumerable: false, |
|
217 configurable: true |
|
218 }); |
|
219 } |
|
220 |
|
221 |
|
222 /** |
|
223 * Taints a named method of the given object by replacing it with a function |
|
224 * that throws an exception. |
|
225 * @param {object} obj the object whose method to taint |
|
226 * @param {string} property the name of the method to taint |
|
227 */ |
|
228 function taintMethod(obj, property) { |
|
229 Object.defineProperty(obj, property, { |
|
230 value: function() { |
|
231 $ERROR("Client code can adversely affect behavior: method " + property + "."); |
|
232 }, |
|
233 writable: true, |
|
234 enumerable: false, |
|
235 configurable: true |
|
236 }); |
|
237 } |
|
238 |
|
239 |
|
240 /** |
|
241 * Taints the given properties (and similarly named properties) by installing |
|
242 * setters on Object.prototype that throw exceptions. |
|
243 * @param {Array} properties an array of property names to taint |
|
244 */ |
|
245 function taintProperties(properties) { |
|
246 properties.forEach(function (property) { |
|
247 var adaptedProperties = [property, "__" + property, "_" + property, property + "_", property + "__"]; |
|
248 adaptedProperties.forEach(function (property) { |
|
249 taintDataProperty(Object.prototype, property); |
|
250 }); |
|
251 }); |
|
252 } |
|
253 |
|
254 |
|
255 /** |
|
256 * Taints the Array object by creating a setter for the property "0" and |
|
257 * replacing some key methods with functions that throw exceptions. |
|
258 */ |
|
259 function taintArray() { |
|
260 taintDataProperty(Array.prototype, "0"); |
|
261 taintMethod(Array.prototype, "indexOf"); |
|
262 taintMethod(Array.prototype, "join"); |
|
263 taintMethod(Array.prototype, "push"); |
|
264 taintMethod(Array.prototype, "slice"); |
|
265 taintMethod(Array.prototype, "sort"); |
|
266 } |
|
267 |
|
268 |
|
269 // auxiliary data for getLocaleSupportInfo |
|
270 var languages = ["zh", "es", "en", "hi", "ur", "ar", "ja", "pa"]; |
|
271 var scripts = ["Latn", "Hans", "Deva", "Arab", "Jpan", "Hant"]; |
|
272 var countries = ["CN", "IN", "US", "PK", "JP", "TW", "HK", "SG"]; |
|
273 var localeSupportInfo = {}; |
|
274 |
|
275 |
|
276 /** |
|
277 * Gets locale support info for the given constructor object, which must be one |
|
278 * of Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat. |
|
279 * @param {object} Constructor the constructor for which to get locale support info |
|
280 * @return {object} locale support info with the following properties: |
|
281 * supported: array of fully supported language tags |
|
282 * byFallback: array of language tags that are supported through fallbacks |
|
283 * unsupported: array of unsupported language tags |
|
284 */ |
|
285 function getLocaleSupportInfo(Constructor) { |
|
286 var constructorName = getConstructorName(Constructor); |
|
287 if (localeSupportInfo[constructorName] !== undefined) { |
|
288 return localeSupportInfo[constructorName]; |
|
289 } |
|
290 |
|
291 var allTags = []; |
|
292 var i, j, k; |
|
293 var language, script, country; |
|
294 for (i = 0; i < languages.length; i++) { |
|
295 language = languages[i]; |
|
296 allTags.push(language); |
|
297 for (j = 0; j < scripts.length; j++) { |
|
298 script = scripts[j]; |
|
299 allTags.push(language + "-" + script); |
|
300 for (k = 0; k < countries.length; k++) { |
|
301 country = countries[k]; |
|
302 allTags.push(language + "-" + script + "-" + country); |
|
303 } |
|
304 } |
|
305 for (k = 0; k < countries.length; k++) { |
|
306 country = countries[k]; |
|
307 allTags.push(language + "-" + country); |
|
308 } |
|
309 } |
|
310 |
|
311 var supported = []; |
|
312 var byFallback = []; |
|
313 var unsupported = []; |
|
314 for (i = 0; i < allTags.length; i++) { |
|
315 var request = allTags[i]; |
|
316 var result = new Constructor([request], {localeMatcher: "lookup"}).resolvedOptions().locale; |
|
317 if (request === result) { |
|
318 supported.push(request); |
|
319 } else if (request.indexOf(result) === 0) { |
|
320 byFallback.push(request); |
|
321 } else { |
|
322 unsupported.push(request); |
|
323 } |
|
324 } |
|
325 |
|
326 localeSupportInfo[constructorName] = { |
|
327 supported: supported, |
|
328 byFallback: byFallback, |
|
329 unsupported: unsupported |
|
330 }; |
|
331 |
|
332 return localeSupportInfo[constructorName]; |
|
333 } |
|
334 |
|
335 |
|
336 /** |
|
337 * @description Tests whether locale is a String value representing a |
|
338 * structurally valid and canonicalized BCP 47 language tag, as defined in |
|
339 * sections 6.2.2 and 6.2.3 of the ECMAScript Internationalization API |
|
340 * Specification. |
|
341 * @param {String} locale the string to be tested. |
|
342 * @result {Boolean} whether the test succeeded. |
|
343 */ |
|
344 function isCanonicalizedStructurallyValidLanguageTag(locale) { |
|
345 |
|
346 /** |
|
347 * Regular expression defining BCP 47 language tags. |
|
348 * |
|
349 * Spec: RFC 5646 section 2.1. |
|
350 */ |
|
351 var alpha = "[a-zA-Z]", |
|
352 digit = "[0-9]", |
|
353 alphanum = "(" + alpha + "|" + digit + ")", |
|
354 regular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)", |
|
355 irregular = "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)", |
|
356 grandfathered = "(" + irregular + "|" + regular + ")", |
|
357 privateuse = "(x(-[a-z0-9]{1,8})+)", |
|
358 singleton = "(" + digit + "|[A-WY-Za-wy-z])", |
|
359 extension = "(" + singleton + "(-" + alphanum + "{2,8})+)", |
|
360 variant = "(" + alphanum + "{5,8}|(" + digit + alphanum + "{3}))", |
|
361 region = "(" + alpha + "{2}|" + digit + "{3})", |
|
362 script = "(" + alpha + "{4})", |
|
363 extlang = "(" + alpha + "{3}(-" + alpha + "{3}){0,2})", |
|
364 language = "(" + alpha + "{2,3}(-" + extlang + ")?|" + alpha + "{4}|" + alpha + "{5,8})", |
|
365 langtag = language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*(-" + extension + ")*(-" + privateuse + ")?", |
|
366 languageTag = "^(" + langtag + "|" + privateuse + "|" + grandfathered + ")$", |
|
367 languageTagRE = new RegExp(languageTag, "i"); |
|
368 var duplicateSingleton = "-" + singleton + "-(.*-)?\\1(?!" + alphanum + ")", |
|
369 duplicateSingletonRE = new RegExp(duplicateSingleton, "i"), |
|
370 duplicateVariant = "(" + alphanum + "{2,8}-)+" + variant + "-(" + alphanum + "{2,8}-)*\\3(?!" + alphanum + ")", |
|
371 duplicateVariantRE = new RegExp(duplicateVariant, "i"); |
|
372 |
|
373 |
|
374 /** |
|
375 * Verifies that the given string is a well-formed BCP 47 language tag |
|
376 * with no duplicate variant or singleton subtags. |
|
377 * |
|
378 * Spec: ECMAScript Internationalization API Specification, draft, 6.2.2. |
|
379 */ |
|
380 function isStructurallyValidLanguageTag(locale) { |
|
381 if (!languageTagRE.test(locale)) { |
|
382 return false; |
|
383 } |
|
384 locale = locale.split(/-x-/)[0]; |
|
385 return !duplicateSingletonRE.test(locale) && !duplicateVariantRE.test(locale); |
|
386 } |
|
387 |
|
388 |
|
389 /** |
|
390 * Mappings from complete tags to preferred values. |
|
391 * |
|
392 * Spec: IANA Language Subtag Registry. |
|
393 */ |
|
394 var __tagMappings = { |
|
395 // property names must be in lower case; values in canonical form |
|
396 |
|
397 // grandfathered tags from IANA language subtag registry, file date 2011-08-25 |
|
398 "art-lojban": "jbo", |
|
399 "cel-gaulish": "cel-gaulish", |
|
400 "en-gb-oed": "en-GB-oed", |
|
401 "i-ami": "ami", |
|
402 "i-bnn": "bnn", |
|
403 "i-default": "i-default", |
|
404 "i-enochian": "i-enochian", |
|
405 "i-hak": "hak", |
|
406 "i-klingon": "tlh", |
|
407 "i-lux": "lb", |
|
408 "i-mingo": "i-mingo", |
|
409 "i-navajo": "nv", |
|
410 "i-pwn": "pwn", |
|
411 "i-tao": "tao", |
|
412 "i-tay": "tay", |
|
413 "i-tsu": "tsu", |
|
414 "no-bok": "nb", |
|
415 "no-nyn": "nn", |
|
416 "sgn-be-fr": "sfb", |
|
417 "sgn-be-nl": "vgt", |
|
418 "sgn-ch-de": "sgg", |
|
419 "zh-guoyu": "cmn", |
|
420 "zh-hakka": "hak", |
|
421 "zh-min": "zh-min", |
|
422 "zh-min-nan": "nan", |
|
423 "zh-xiang": "hsn", |
|
424 // deprecated redundant tags from IANA language subtag registry, file date 2011-08-25 |
|
425 "sgn-br": "bzs", |
|
426 "sgn-co": "csn", |
|
427 "sgn-de": "gsg", |
|
428 "sgn-dk": "dsl", |
|
429 "sgn-es": "ssp", |
|
430 "sgn-fr": "fsl", |
|
431 "sgn-gb": "bfi", |
|
432 "sgn-gr": "gss", |
|
433 "sgn-ie": "isg", |
|
434 "sgn-it": "ise", |
|
435 "sgn-jp": "jsl", |
|
436 "sgn-mx": "mfs", |
|
437 "sgn-ni": "ncs", |
|
438 "sgn-nl": "dse", |
|
439 "sgn-no": "nsl", |
|
440 "sgn-pt": "psr", |
|
441 "sgn-se": "swl", |
|
442 "sgn-us": "ase", |
|
443 "sgn-za": "sfs", |
|
444 "zh-cmn": "cmn", |
|
445 "zh-cmn-hans": "cmn-Hans", |
|
446 "zh-cmn-hant": "cmn-Hant", |
|
447 "zh-gan": "gan", |
|
448 "zh-wuu": "wuu", |
|
449 "zh-yue": "yue", |
|
450 // deprecated variant with prefix from IANA language subtag registry, file date 2011-08-25 |
|
451 "ja-latn-hepburn-heploc": "ja-Latn-alalc97" |
|
452 }; |
|
453 |
|
454 |
|
455 /** |
|
456 * Mappings from non-extlang subtags to preferred values. |
|
457 * |
|
458 * Spec: IANA Language Subtag Registry. |
|
459 */ |
|
460 var __subtagMappings = { |
|
461 // property names and values must be in canonical case |
|
462 // language subtags with Preferred-Value mappings from IANA language subtag registry, file date 2011-08-25 |
|
463 "in": "id", |
|
464 "iw": "he", |
|
465 "ji": "yi", |
|
466 "jw": "jv", |
|
467 "mo": "ro", |
|
468 "ayx": "nun", |
|
469 "cjr": "mom", |
|
470 "cmk": "xch", |
|
471 "drh": "khk", |
|
472 "drw": "prs", |
|
473 "gav": "dev", |
|
474 "mst": "mry", |
|
475 "myt": "mry", |
|
476 "tie": "ras", |
|
477 "tkk": "twm", |
|
478 "tnf": "prs", |
|
479 // region subtags with Preferred-Value mappings from IANA language subtag registry, file date 2011-08-25 |
|
480 "BU": "MM", |
|
481 "DD": "DE", |
|
482 "FX": "FR", |
|
483 "TP": "TL", |
|
484 "YD": "YE", |
|
485 "ZR": "CD" |
|
486 }; |
|
487 |
|
488 |
|
489 /** |
|
490 * Mappings from extlang subtags to preferred values. |
|
491 * |
|
492 * Spec: IANA Language Subtag Registry. |
|
493 */ |
|
494 var __extlangMappings = { |
|
495 // extlang subtags with Preferred-Value mappings from IANA language subtag registry, file date 2011-08-25 |
|
496 // values are arrays with [0] the replacement value, [1] (if present) the prefix to be removed |
|
497 "aao": ["aao", "ar"], |
|
498 "abh": ["abh", "ar"], |
|
499 "abv": ["abv", "ar"], |
|
500 "acm": ["acm", "ar"], |
|
501 "acq": ["acq", "ar"], |
|
502 "acw": ["acw", "ar"], |
|
503 "acx": ["acx", "ar"], |
|
504 "acy": ["acy", "ar"], |
|
505 "adf": ["adf", "ar"], |
|
506 "ads": ["ads", "sgn"], |
|
507 "aeb": ["aeb", "ar"], |
|
508 "aec": ["aec", "ar"], |
|
509 "aed": ["aed", "sgn"], |
|
510 "aen": ["aen", "sgn"], |
|
511 "afb": ["afb", "ar"], |
|
512 "afg": ["afg", "sgn"], |
|
513 "ajp": ["ajp", "ar"], |
|
514 "apc": ["apc", "ar"], |
|
515 "apd": ["apd", "ar"], |
|
516 "arb": ["arb", "ar"], |
|
517 "arq": ["arq", "ar"], |
|
518 "ars": ["ars", "ar"], |
|
519 "ary": ["ary", "ar"], |
|
520 "arz": ["arz", "ar"], |
|
521 "ase": ["ase", "sgn"], |
|
522 "asf": ["asf", "sgn"], |
|
523 "asp": ["asp", "sgn"], |
|
524 "asq": ["asq", "sgn"], |
|
525 "asw": ["asw", "sgn"], |
|
526 "auz": ["auz", "ar"], |
|
527 "avl": ["avl", "ar"], |
|
528 "ayh": ["ayh", "ar"], |
|
529 "ayl": ["ayl", "ar"], |
|
530 "ayn": ["ayn", "ar"], |
|
531 "ayp": ["ayp", "ar"], |
|
532 "bbz": ["bbz", "ar"], |
|
533 "bfi": ["bfi", "sgn"], |
|
534 "bfk": ["bfk", "sgn"], |
|
535 "bjn": ["bjn", "ms"], |
|
536 "bog": ["bog", "sgn"], |
|
537 "bqn": ["bqn", "sgn"], |
|
538 "bqy": ["bqy", "sgn"], |
|
539 "btj": ["btj", "ms"], |
|
540 "bve": ["bve", "ms"], |
|
541 "bvl": ["bvl", "sgn"], |
|
542 "bvu": ["bvu", "ms"], |
|
543 "bzs": ["bzs", "sgn"], |
|
544 "cdo": ["cdo", "zh"], |
|
545 "cds": ["cds", "sgn"], |
|
546 "cjy": ["cjy", "zh"], |
|
547 "cmn": ["cmn", "zh"], |
|
548 "coa": ["coa", "ms"], |
|
549 "cpx": ["cpx", "zh"], |
|
550 "csc": ["csc", "sgn"], |
|
551 "csd": ["csd", "sgn"], |
|
552 "cse": ["cse", "sgn"], |
|
553 "csf": ["csf", "sgn"], |
|
554 "csg": ["csg", "sgn"], |
|
555 "csl": ["csl", "sgn"], |
|
556 "csn": ["csn", "sgn"], |
|
557 "csq": ["csq", "sgn"], |
|
558 "csr": ["csr", "sgn"], |
|
559 "czh": ["czh", "zh"], |
|
560 "czo": ["czo", "zh"], |
|
561 "doq": ["doq", "sgn"], |
|
562 "dse": ["dse", "sgn"], |
|
563 "dsl": ["dsl", "sgn"], |
|
564 "dup": ["dup", "ms"], |
|
565 "ecs": ["ecs", "sgn"], |
|
566 "esl": ["esl", "sgn"], |
|
567 "esn": ["esn", "sgn"], |
|
568 "eso": ["eso", "sgn"], |
|
569 "eth": ["eth", "sgn"], |
|
570 "fcs": ["fcs", "sgn"], |
|
571 "fse": ["fse", "sgn"], |
|
572 "fsl": ["fsl", "sgn"], |
|
573 "fss": ["fss", "sgn"], |
|
574 "gan": ["gan", "zh"], |
|
575 "gom": ["gom", "kok"], |
|
576 "gse": ["gse", "sgn"], |
|
577 "gsg": ["gsg", "sgn"], |
|
578 "gsm": ["gsm", "sgn"], |
|
579 "gss": ["gss", "sgn"], |
|
580 "gus": ["gus", "sgn"], |
|
581 "hab": ["hab", "sgn"], |
|
582 "haf": ["haf", "sgn"], |
|
583 "hak": ["hak", "zh"], |
|
584 "hds": ["hds", "sgn"], |
|
585 "hji": ["hji", "ms"], |
|
586 "hks": ["hks", "sgn"], |
|
587 "hos": ["hos", "sgn"], |
|
588 "hps": ["hps", "sgn"], |
|
589 "hsh": ["hsh", "sgn"], |
|
590 "hsl": ["hsl", "sgn"], |
|
591 "hsn": ["hsn", "zh"], |
|
592 "icl": ["icl", "sgn"], |
|
593 "ils": ["ils", "sgn"], |
|
594 "inl": ["inl", "sgn"], |
|
595 "ins": ["ins", "sgn"], |
|
596 "ise": ["ise", "sgn"], |
|
597 "isg": ["isg", "sgn"], |
|
598 "isr": ["isr", "sgn"], |
|
599 "jak": ["jak", "ms"], |
|
600 "jax": ["jax", "ms"], |
|
601 "jcs": ["jcs", "sgn"], |
|
602 "jhs": ["jhs", "sgn"], |
|
603 "jls": ["jls", "sgn"], |
|
604 "jos": ["jos", "sgn"], |
|
605 "jsl": ["jsl", "sgn"], |
|
606 "jus": ["jus", "sgn"], |
|
607 "kgi": ["kgi", "sgn"], |
|
608 "knn": ["knn", "kok"], |
|
609 "kvb": ["kvb", "ms"], |
|
610 "kvk": ["kvk", "sgn"], |
|
611 "kvr": ["kvr", "ms"], |
|
612 "kxd": ["kxd", "ms"], |
|
613 "lbs": ["lbs", "sgn"], |
|
614 "lce": ["lce", "ms"], |
|
615 "lcf": ["lcf", "ms"], |
|
616 "liw": ["liw", "ms"], |
|
617 "lls": ["lls", "sgn"], |
|
618 "lsg": ["lsg", "sgn"], |
|
619 "lsl": ["lsl", "sgn"], |
|
620 "lso": ["lso", "sgn"], |
|
621 "lsp": ["lsp", "sgn"], |
|
622 "lst": ["lst", "sgn"], |
|
623 "lsy": ["lsy", "sgn"], |
|
624 "ltg": ["ltg", "lv"], |
|
625 "lvs": ["lvs", "lv"], |
|
626 "lzh": ["lzh", "zh"], |
|
627 "max": ["max", "ms"], |
|
628 "mdl": ["mdl", "sgn"], |
|
629 "meo": ["meo", "ms"], |
|
630 "mfa": ["mfa", "ms"], |
|
631 "mfb": ["mfb", "ms"], |
|
632 "mfs": ["mfs", "sgn"], |
|
633 "min": ["min", "ms"], |
|
634 "mnp": ["mnp", "zh"], |
|
635 "mqg": ["mqg", "ms"], |
|
636 "mre": ["mre", "sgn"], |
|
637 "msd": ["msd", "sgn"], |
|
638 "msi": ["msi", "ms"], |
|
639 "msr": ["msr", "sgn"], |
|
640 "mui": ["mui", "ms"], |
|
641 "mzc": ["mzc", "sgn"], |
|
642 "mzg": ["mzg", "sgn"], |
|
643 "mzy": ["mzy", "sgn"], |
|
644 "nan": ["nan", "zh"], |
|
645 "nbs": ["nbs", "sgn"], |
|
646 "ncs": ["ncs", "sgn"], |
|
647 "nsi": ["nsi", "sgn"], |
|
648 "nsl": ["nsl", "sgn"], |
|
649 "nsp": ["nsp", "sgn"], |
|
650 "nsr": ["nsr", "sgn"], |
|
651 "nzs": ["nzs", "sgn"], |
|
652 "okl": ["okl", "sgn"], |
|
653 "orn": ["orn", "ms"], |
|
654 "ors": ["ors", "ms"], |
|
655 "pel": ["pel", "ms"], |
|
656 "pga": ["pga", "ar"], |
|
657 "pks": ["pks", "sgn"], |
|
658 "prl": ["prl", "sgn"], |
|
659 "prz": ["prz", "sgn"], |
|
660 "psc": ["psc", "sgn"], |
|
661 "psd": ["psd", "sgn"], |
|
662 "pse": ["pse", "ms"], |
|
663 "psg": ["psg", "sgn"], |
|
664 "psl": ["psl", "sgn"], |
|
665 "pso": ["pso", "sgn"], |
|
666 "psp": ["psp", "sgn"], |
|
667 "psr": ["psr", "sgn"], |
|
668 "pys": ["pys", "sgn"], |
|
669 "rms": ["rms", "sgn"], |
|
670 "rsi": ["rsi", "sgn"], |
|
671 "rsl": ["rsl", "sgn"], |
|
672 "sdl": ["sdl", "sgn"], |
|
673 "sfb": ["sfb", "sgn"], |
|
674 "sfs": ["sfs", "sgn"], |
|
675 "sgg": ["sgg", "sgn"], |
|
676 "sgx": ["sgx", "sgn"], |
|
677 "shu": ["shu", "ar"], |
|
678 "slf": ["slf", "sgn"], |
|
679 "sls": ["sls", "sgn"], |
|
680 "sqs": ["sqs", "sgn"], |
|
681 "ssh": ["ssh", "ar"], |
|
682 "ssp": ["ssp", "sgn"], |
|
683 "ssr": ["ssr", "sgn"], |
|
684 "svk": ["svk", "sgn"], |
|
685 "swc": ["swc", "sw"], |
|
686 "swh": ["swh", "sw"], |
|
687 "swl": ["swl", "sgn"], |
|
688 "syy": ["syy", "sgn"], |
|
689 "tmw": ["tmw", "ms"], |
|
690 "tse": ["tse", "sgn"], |
|
691 "tsm": ["tsm", "sgn"], |
|
692 "tsq": ["tsq", "sgn"], |
|
693 "tss": ["tss", "sgn"], |
|
694 "tsy": ["tsy", "sgn"], |
|
695 "tza": ["tza", "sgn"], |
|
696 "ugn": ["ugn", "sgn"], |
|
697 "ugy": ["ugy", "sgn"], |
|
698 "ukl": ["ukl", "sgn"], |
|
699 "uks": ["uks", "sgn"], |
|
700 "urk": ["urk", "ms"], |
|
701 "uzn": ["uzn", "uz"], |
|
702 "uzs": ["uzs", "uz"], |
|
703 "vgt": ["vgt", "sgn"], |
|
704 "vkk": ["vkk", "ms"], |
|
705 "vkt": ["vkt", "ms"], |
|
706 "vsi": ["vsi", "sgn"], |
|
707 "vsl": ["vsl", "sgn"], |
|
708 "vsv": ["vsv", "sgn"], |
|
709 "wuu": ["wuu", "zh"], |
|
710 "xki": ["xki", "sgn"], |
|
711 "xml": ["xml", "sgn"], |
|
712 "xmm": ["xmm", "ms"], |
|
713 "xms": ["xms", "sgn"], |
|
714 "yds": ["yds", "sgn"], |
|
715 "ysl": ["ysl", "sgn"], |
|
716 "yue": ["yue", "zh"], |
|
717 "zib": ["zib", "sgn"], |
|
718 "zlm": ["zlm", "ms"], |
|
719 "zmi": ["zmi", "ms"], |
|
720 "zsl": ["zsl", "sgn"], |
|
721 "zsm": ["zsm", "ms"] |
|
722 }; |
|
723 |
|
724 |
|
725 /** |
|
726 * Canonicalizes the given well-formed BCP 47 language tag, including regularized case of subtags. |
|
727 * |
|
728 * Spec: ECMAScript Internationalization API Specification, draft, 6.2.3. |
|
729 * Spec: RFC 5646, section 4.5. |
|
730 */ |
|
731 function canonicalizeLanguageTag(locale) { |
|
732 |
|
733 // start with lower case for easier processing, and because most subtags will need to be lower case anyway |
|
734 locale = locale.toLowerCase(); |
|
735 |
|
736 // handle mappings for complete tags |
|
737 if (__tagMappings.hasOwnProperty(locale)) { |
|
738 return __tagMappings[locale]; |
|
739 } |
|
740 |
|
741 var subtags = locale.split("-"); |
|
742 var i = 0; |
|
743 |
|
744 // handle standard part: all subtags before first singleton or "x" |
|
745 while (i < subtags.length) { |
|
746 var subtag = subtags[i]; |
|
747 if (subtag.length === 1 && (i > 0 || subtag === "x")) { |
|
748 break; |
|
749 } else if (i !== 0 && subtag.length === 2) { |
|
750 subtag = subtag.toUpperCase(); |
|
751 } else if (subtag.length === 4) { |
|
752 subtag = subtag[0].toUpperCase() + subtag.substring(1).toLowerCase(); |
|
753 } |
|
754 if (__subtagMappings.hasOwnProperty(subtag)) { |
|
755 subtag = __subtagMappings[subtag]; |
|
756 } else if (__extlangMappings.hasOwnProperty(subtag)) { |
|
757 subtag = __extlangMappings[subtag][0]; |
|
758 if (i === 1 && __extlangMappings[subtag][1] === subtags[0]) { |
|
759 subtags.shift(); |
|
760 i--; |
|
761 } |
|
762 } |
|
763 subtags[i] = subtag; |
|
764 i++; |
|
765 } |
|
766 var normal = subtags.slice(0, i).join("-"); |
|
767 |
|
768 // handle extensions |
|
769 var extensions = []; |
|
770 while (i < subtags.length && subtags[i] !== "x") { |
|
771 var extensionStart = i; |
|
772 i++; |
|
773 while (i < subtags.length && subtags[i].length > 1) { |
|
774 i++; |
|
775 } |
|
776 var extension = subtags.slice(extensionStart, i).join("-"); |
|
777 extensions.push(extension); |
|
778 } |
|
779 extensions.sort(); |
|
780 |
|
781 // handle private use |
|
782 var privateUse; |
|
783 if (i < subtags.length) { |
|
784 privateUse = subtags.slice(i).join("-"); |
|
785 } |
|
786 |
|
787 // put everything back together |
|
788 var canonical = normal; |
|
789 if (extensions.length > 0) { |
|
790 canonical += "-" + extensions.join("-"); |
|
791 } |
|
792 if (privateUse !== undefined) { |
|
793 if (canonical.length > 0) { |
|
794 canonical += "-" + privateUse; |
|
795 } else { |
|
796 canonical = privateUse; |
|
797 } |
|
798 } |
|
799 |
|
800 return canonical; |
|
801 } |
|
802 |
|
803 return typeof locale === "string" && isStructurallyValidLanguageTag(locale) && |
|
804 canonicalizeLanguageTag(locale) === locale; |
|
805 } |
|
806 |
|
807 |
|
808 /** |
|
809 * Tests whether the named options property is correctly handled by the given constructor. |
|
810 * @param {object} Constructor the constructor to test. |
|
811 * @param {string} property the name of the options property to test. |
|
812 * @param {string} type the type that values of the property are expected to have |
|
813 * @param {Array} [values] an array of allowed values for the property. Not needed for boolean. |
|
814 * @param {any} fallback the fallback value that the property assumes if not provided. |
|
815 * @param {object} testOptions additional options: |
|
816 * @param {boolean} isOptional whether support for this property is optional for implementations. |
|
817 * @param {boolean} noReturn whether the resulting value of the property is not returned. |
|
818 * @param {boolean} isILD whether the resulting value of the property is implementation and locale dependent. |
|
819 * @param {object} extra additional option to pass along, properties are value -> {option: value}. |
|
820 * @return {boolean} whether the test succeeded. |
|
821 */ |
|
822 function testOption(Constructor, property, type, values, fallback, testOptions) { |
|
823 var isOptional = testOptions !== undefined && testOptions.isOptional === true; |
|
824 var noReturn = testOptions !== undefined && testOptions.noReturn === true; |
|
825 var isILD = testOptions !== undefined && testOptions.isILD === true; |
|
826 |
|
827 function addExtraOptions(options, value, testOptions) { |
|
828 if (testOptions !== undefined && testOptions.extra !== undefined) { |
|
829 var extra; |
|
830 if (value !== undefined && testOptions.extra[value] !== undefined) { |
|
831 extra = testOptions.extra[value]; |
|
832 } else if (testOptions.extra.any !== undefined) { |
|
833 extra = testOptions.extra.any; |
|
834 } |
|
835 if (extra !== undefined) { |
|
836 Object.getOwnPropertyNames(extra).forEach(function (prop) { |
|
837 options[prop] = extra[prop]; |
|
838 }); |
|
839 } |
|
840 } |
|
841 } |
|
842 |
|
843 var testValues, options, obj, expected, actual, error; |
|
844 |
|
845 // test that the specified values are accepted. Also add values that convert to specified values. |
|
846 if (type === "boolean") { |
|
847 if (values === undefined) { |
|
848 values = [true, false]; |
|
849 } |
|
850 testValues = values.slice(0); |
|
851 testValues.push(888); |
|
852 testValues.push(0); |
|
853 } else if (type === "string") { |
|
854 testValues = values.slice(0); |
|
855 testValues.push({toString: function () { return values[0]; }}); |
|
856 } |
|
857 testValues.forEach(function (value) { |
|
858 options = {}; |
|
859 options[property] = value; |
|
860 addExtraOptions(options, value, testOptions); |
|
861 obj = new Constructor(undefined, options); |
|
862 if (noReturn) { |
|
863 if (obj.resolvedOptions().hasOwnProperty(property)) { |
|
864 $ERROR("Option property " + property + " is returned, but shouldn't be."); |
|
865 } |
|
866 } else { |
|
867 actual = obj.resolvedOptions()[property]; |
|
868 if (isILD) { |
|
869 if (actual !== undefined && values.indexOf(actual) === -1) { |
|
870 $ERROR("Invalid value " + actual + " returned for property " + property + "."); |
|
871 } |
|
872 } else { |
|
873 if (type === "boolean") { |
|
874 expected = Boolean(value); |
|
875 } else if (type === "string") { |
|
876 expected = String(value); |
|
877 } |
|
878 if (actual !== expected && !(isOptional && actual === undefined)) { |
|
879 $ERROR("Option value " + value + " for property " + property + |
|
880 " was not accepted; got " + actual + " instead."); |
|
881 } |
|
882 } |
|
883 } |
|
884 }); |
|
885 |
|
886 // test that invalid values are rejected |
|
887 if (type === "string") { |
|
888 var invalidValues = ["invalidValue", -1, null]; |
|
889 // assume that we won't have values in caseless scripts |
|
890 if (values[0].toUpperCase() !== values[0]) { |
|
891 invalidValues.push(values[0].toUpperCase()); |
|
892 } else { |
|
893 invalidValues.push(values[0].toLowerCase()); |
|
894 } |
|
895 invalidValues.forEach(function (value) { |
|
896 options = {}; |
|
897 options[property] = value; |
|
898 addExtraOptions(options, value, testOptions); |
|
899 error = undefined; |
|
900 try { |
|
901 obj = new Constructor(undefined, options); |
|
902 } catch (e) { |
|
903 error = e; |
|
904 } |
|
905 if (error === undefined) { |
|
906 $ERROR("Invalid option value " + value + " for property " + property + " was not rejected."); |
|
907 } else if (error.name !== "RangeError") { |
|
908 $ERROR("Invalid option value " + value + " for property " + property + " was rejected with wrong error " + error.name + "."); |
|
909 } |
|
910 }); |
|
911 } |
|
912 |
|
913 // test that fallback value or another valid value is used if no options value is provided |
|
914 if (!noReturn) { |
|
915 options = {}; |
|
916 addExtraOptions(options, undefined, testOptions); |
|
917 obj = new Constructor(undefined, options); |
|
918 actual = obj.resolvedOptions()[property]; |
|
919 if (!(isOptional && actual === undefined)) { |
|
920 if (fallback !== undefined) { |
|
921 if (actual !== fallback) { |
|
922 $ERROR("Option fallback value " + fallback + " for property " + property + |
|
923 " was not used; got " + actual + " instead."); |
|
924 } |
|
925 } else { |
|
926 if (values.indexOf(actual) === -1 && !(isILD && actual === undefined)) { |
|
927 $ERROR("Invalid value " + actual + " returned for property " + property + "."); |
|
928 } |
|
929 } |
|
930 } |
|
931 } |
|
932 |
|
933 return true; |
|
934 } |
|
935 |
|
936 |
|
937 /** |
|
938 * Tests whether the named property of the given object has a valid value |
|
939 * and the default attributes of the properties of an object literal. |
|
940 * @param {Object} obj the object to be tested. |
|
941 * @param {string} property the name of the property |
|
942 * @param {Function|Array} valid either a function that tests value for validity and returns a boolean, |
|
943 * an array of valid values. |
|
944 * @exception if the property has an invalid value. |
|
945 */ |
|
946 function testProperty(obj, property, valid) { |
|
947 var desc = Object.getOwnPropertyDescriptor(obj, property); |
|
948 if (!desc.writable) { |
|
949 $ERROR("Property " + property + " must be writable."); |
|
950 } |
|
951 if (!desc.enumerable) { |
|
952 $ERROR("Property " + property + " must be enumerable."); |
|
953 } |
|
954 if (!desc.configurable) { |
|
955 $ERROR("Property " + property + " must be configurable."); |
|
956 } |
|
957 var value = desc.value; |
|
958 var isValid = (typeof valid === "function") ? valid(value) : (valid.indexOf(value) !== -1); |
|
959 if (!isValid) { |
|
960 $ERROR("Property value " + value + " is not allowed for property " + property + "."); |
|
961 } |
|
962 } |
|
963 |
|
964 |
|
965 /** |
|
966 * Tests whether the named property of the given object, if present at all, has a valid value |
|
967 * and the default attributes of the properties of an object literal. |
|
968 * @param {Object} obj the object to be tested. |
|
969 * @param {string} property the name of the property |
|
970 * @param {Function|Array} valid either a function that tests value for validity and returns a boolean, |
|
971 * an array of valid values. |
|
972 * @exception if the property is present and has an invalid value. |
|
973 */ |
|
974 function mayHaveProperty(obj, property, valid) { |
|
975 if (obj.hasOwnProperty(property)) { |
|
976 testProperty(obj, property, valid); |
|
977 } |
|
978 } |
|
979 |
|
980 |
|
981 /** |
|
982 * Tests whether the given object has the named property with a valid value |
|
983 * and the default attributes of the properties of an object literal. |
|
984 * @param {Object} obj the object to be tested. |
|
985 * @param {string} property the name of the property |
|
986 * @param {Function|Array} valid either a function that tests value for validity and returns a boolean, |
|
987 * an array of valid values. |
|
988 * @exception if the property is missing or has an invalid value. |
|
989 */ |
|
990 function mustHaveProperty(obj, property, valid) { |
|
991 if (!obj.hasOwnProperty(property)) { |
|
992 $ERROR("Object is missing property " + property + "."); |
|
993 } |
|
994 testProperty(obj, property, valid); |
|
995 } |
|
996 |
|
997 |
|
998 /** |
|
999 * Tests whether the given object does not have the named property. |
|
1000 * @param {Object} obj the object to be tested. |
|
1001 * @param {string} property the name of the property |
|
1002 * @exception if the property is present. |
|
1003 */ |
|
1004 function mustNotHaveProperty(obj, property) { |
|
1005 if (obj.hasOwnProperty(property)) { |
|
1006 $ERROR("Object has property it mustn't have: " + property + "."); |
|
1007 } |
|
1008 } |
|
1009 |
|
1010 |
|
1011 /** |
|
1012 * Properties of the RegExp constructor that may be affected by use of regular |
|
1013 * expressions, and the default values of these properties. Properties are from |
|
1014 * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Deprecated_and_obsolete_features#RegExp_Properties |
|
1015 */ |
|
1016 var regExpProperties = ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9", |
|
1017 "$_", "$*", "$&", "$+", "$`", "$'", |
|
1018 "input", "lastMatch", "lastParen", "leftContext", "rightContext" |
|
1019 ]; |
|
1020 |
|
1021 var regExpPropertiesDefaultValues = (function () { |
|
1022 var values = Object.create(null); |
|
1023 regExpProperties.forEach(function (property) { |
|
1024 values[property] = RegExp[property]; |
|
1025 }); |
|
1026 return values; |
|
1027 }()); |
|
1028 |
|
1029 |
|
1030 /** |
|
1031 * Tests that executing the provided function (which may use regular expressions |
|
1032 * in its implementation) does not create or modify unwanted properties on the |
|
1033 * RegExp constructor. |
|
1034 */ |
|
1035 function testForUnwantedRegExpChanges(testFunc) { |
|
1036 regExpProperties.forEach(function (property) { |
|
1037 RegExp[property] = regExpPropertiesDefaultValues[property]; |
|
1038 }); |
|
1039 testFunc(); |
|
1040 regExpProperties.forEach(function (property) { |
|
1041 if (RegExp[property] !== regExpPropertiesDefaultValues[property]) { |
|
1042 $ERROR("RegExp has unexpected property " + property + " with value " + |
|
1043 RegExp[property] + "."); |
|
1044 } |
|
1045 }); |
|
1046 } |
|
1047 |
|
1048 |
|
1049 /** |
|
1050 * Tests whether name is a valid BCP 47 numbering system name |
|
1051 * and not excluded from use in the ECMAScript Internationalization API. |
|
1052 * @param {string} name the name to be tested. |
|
1053 * @return {boolean} whether name is a valid BCP 47 numbering system name and |
|
1054 * allowed for use in the ECMAScript Internationalization API. |
|
1055 */ |
|
1056 |
|
1057 function isValidNumberingSystem(name) { |
|
1058 |
|
1059 // source: CLDR file common/bcp47/number.xml; version CLDR 21. |
|
1060 var numberingSystems = [ |
|
1061 "arab", |
|
1062 "arabext", |
|
1063 "armn", |
|
1064 "armnlow", |
|
1065 "bali", |
|
1066 "beng", |
|
1067 "brah", |
|
1068 "cakm", |
|
1069 "cham", |
|
1070 "deva", |
|
1071 "ethi", |
|
1072 "finance", |
|
1073 "fullwide", |
|
1074 "geor", |
|
1075 "grek", |
|
1076 "greklow", |
|
1077 "gujr", |
|
1078 "guru", |
|
1079 "hanidec", |
|
1080 "hans", |
|
1081 "hansfin", |
|
1082 "hant", |
|
1083 "hantfin", |
|
1084 "hebr", |
|
1085 "java", |
|
1086 "jpan", |
|
1087 "jpanfin", |
|
1088 "kali", |
|
1089 "khmr", |
|
1090 "knda", |
|
1091 "osma", |
|
1092 "lana", |
|
1093 "lanatham", |
|
1094 "laoo", |
|
1095 "latn", |
|
1096 "lepc", |
|
1097 "limb", |
|
1098 "mlym", |
|
1099 "mong", |
|
1100 "mtei", |
|
1101 "mymr", |
|
1102 "mymrshan", |
|
1103 "native", |
|
1104 "nkoo", |
|
1105 "olck", |
|
1106 "orya", |
|
1107 "roman", |
|
1108 "romanlow", |
|
1109 "saur", |
|
1110 "shrd", |
|
1111 "sora", |
|
1112 "sund", |
|
1113 "talu", |
|
1114 "takr", |
|
1115 "taml", |
|
1116 "tamldec", |
|
1117 "telu", |
|
1118 "thai", |
|
1119 "tibt", |
|
1120 "traditio", |
|
1121 "vaii" |
|
1122 ]; |
|
1123 |
|
1124 var excluded = [ |
|
1125 "finance", |
|
1126 "native", |
|
1127 "traditio" |
|
1128 ]; |
|
1129 |
|
1130 |
|
1131 return numberingSystems.indexOf(name) !== -1 && excluded.indexOf(name) === -1; |
|
1132 } |
|
1133 |
|
1134 |
|
1135 /** |
|
1136 * Provides the digits of numbering systems with simple digit mappings, |
|
1137 * as specified in 11.3.2. |
|
1138 */ |
|
1139 |
|
1140 var numberingSystemDigits = { |
|
1141 arab: "٠١٢٣٤٥٦٧٨٩", |
|
1142 arabext: "۰۱۲۳۴۵۶۷۸۹", |
|
1143 beng: "০১২৩৪৫৬৭৮৯", |
|
1144 deva: "०१२३४५६७८९", |
|
1145 fullwide: "0123456789", |
|
1146 gujr: "૦૧૨૩૪૫૬૭૮૯", |
|
1147 guru: "੦੧੨੩੪੫੬੭੮੯", |
|
1148 hanidec: "〇一二三四五六七八九", |
|
1149 khmr: "០១២៣៤៥៦៧៨៩", |
|
1150 knda: "೦೧೨೩೪೫೬೭೮೯", |
|
1151 laoo: "໐໑໒໓໔໕໖໗໘໙", |
|
1152 latn: "0123456789", |
|
1153 mlym: "൦൧൨൩൪൫൬൭൮൯", |
|
1154 mong: "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙", |
|
1155 mymr: "၀၁၂၃၄၅၆၇၈၉", |
|
1156 orya: "୦୧୨୩୪୫୬୭୮୯", |
|
1157 tamldec: "௦௧௨௩௪௫௬௭௮௯", |
|
1158 telu: "౦౧౨౩౪౫౬౭౮౯", |
|
1159 thai: "๐๑๒๓๔๕๖๗๘๙", |
|
1160 tibt: "༠༡༢༣༤༥༦༧༨༩" |
|
1161 }; |
|
1162 |
|
1163 |
|
1164 /** |
|
1165 * Tests that number formatting is handled correctly. The function checks that the |
|
1166 * digit sequences in formatted output are as specified, converted to the |
|
1167 * selected numbering system, and embedded in consistent localized patterns. |
|
1168 * @param {Array} locales the locales to be tested. |
|
1169 * @param {Array} numberingSystems the numbering systems to be tested. |
|
1170 * @param {Object} options the options to pass to Intl.NumberFormat. Options |
|
1171 * must include {useGrouping: false}, and must cause 1.1 to be formatted |
|
1172 * pre- and post-decimal digits. |
|
1173 * @param {Object} testData maps input data (in ES5 9.3.1 format) to expected output strings |
|
1174 * in unlocalized format with Western digits. |
|
1175 */ |
|
1176 |
|
1177 function testNumberFormat(locales, numberingSystems, options, testData) { |
|
1178 locales.forEach(function (locale) { |
|
1179 numberingSystems.forEach(function (numbering) { |
|
1180 var digits = numberingSystemDigits[numbering]; |
|
1181 var format = new Intl.NumberFormat([locale + "-u-nu-" + numbering], options); |
|
1182 |
|
1183 function getPatternParts(positive) { |
|
1184 var n = positive ? 1.1 : -1.1; |
|
1185 var formatted = format.format(n); |
|
1186 var oneoneRE = "([^" + digits + "]*)[" + digits + "]+([^" + digits + "]+)[" + digits + "]+([^" + digits + "]*)"; |
|
1187 var match = formatted.match(new RegExp(oneoneRE)); |
|
1188 if (match === null) { |
|
1189 $ERROR("Unexpected formatted " + n + " for " + |
|
1190 format.resolvedOptions().locale + " and options " + |
|
1191 JSON.stringify(options) + ": " + formatted); |
|
1192 } |
|
1193 return match; |
|
1194 } |
|
1195 |
|
1196 function toNumbering(raw) { |
|
1197 return raw.replace(/[0-9]/g, function (digit) { |
|
1198 return digits[digit.charCodeAt(0) - "0".charCodeAt(0)]; |
|
1199 }); |
|
1200 } |
|
1201 |
|
1202 function buildExpected(raw, patternParts) { |
|
1203 var period = raw.indexOf("."); |
|
1204 if (period === -1) { |
|
1205 return patternParts[1] + toNumbering(raw) + patternParts[3]; |
|
1206 } else { |
|
1207 return patternParts[1] + |
|
1208 toNumbering(raw.substring(0, period)) + |
|
1209 patternParts[2] + |
|
1210 toNumbering(raw.substring(period + 1)) + |
|
1211 patternParts[3]; |
|
1212 } |
|
1213 } |
|
1214 |
|
1215 if (format.resolvedOptions().numberingSystem === numbering) { |
|
1216 // figure out prefixes, infixes, suffixes for positive and negative values |
|
1217 var posPatternParts = getPatternParts(true); |
|
1218 var negPatternParts = getPatternParts(false); |
|
1219 |
|
1220 Object.getOwnPropertyNames(testData).forEach(function (input) { |
|
1221 var rawExpected = testData[input]; |
|
1222 var patternParts; |
|
1223 if (rawExpected[0] === "-") { |
|
1224 patternParts = negPatternParts; |
|
1225 rawExpected = rawExpected.substring(1); |
|
1226 } else { |
|
1227 patternParts = posPatternParts; |
|
1228 } |
|
1229 var expected = buildExpected(rawExpected, patternParts); |
|
1230 var actual = format.format(input); |
|
1231 if (actual !== expected) { |
|
1232 $ERROR("Formatted value for " + input + ", " + |
|
1233 format.resolvedOptions().locale + " and options " + |
|
1234 JSON.stringify(options) + " is " + actual + "; expected " + expected + "."); |
|
1235 } |
|
1236 }); |
|
1237 } |
|
1238 }); |
|
1239 }); |
|
1240 } |
|
1241 |
|
1242 |
|
1243 /** |
|
1244 * Return the components of date-time formats. |
|
1245 * @return {Array} an array with all date-time components. |
|
1246 */ |
|
1247 |
|
1248 function getDateTimeComponents() { |
|
1249 return ["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"]; |
|
1250 } |
|
1251 |
|
1252 |
|
1253 /** |
|
1254 * Return the valid values for the given date-time component, as specified |
|
1255 * by the table in section 12.1.1. |
|
1256 * @param {string} component a date-time component. |
|
1257 * @return {Array} an array with the valid values for the component. |
|
1258 */ |
|
1259 |
|
1260 function getDateTimeComponentValues(component) { |
|
1261 |
|
1262 var components = { |
|
1263 weekday: ["narrow", "short", "long"], |
|
1264 era: ["narrow", "short", "long"], |
|
1265 year: ["2-digit", "numeric"], |
|
1266 month: ["2-digit", "numeric", "narrow", "short", "long"], |
|
1267 day: ["2-digit", "numeric"], |
|
1268 hour: ["2-digit", "numeric"], |
|
1269 minute: ["2-digit", "numeric"], |
|
1270 second: ["2-digit", "numeric"], |
|
1271 timeZoneName: ["short", "long"] |
|
1272 }; |
|
1273 |
|
1274 var result = components[component]; |
|
1275 if (result === undefined) { |
|
1276 $ERROR("Internal error: No values defined for date-time component " + component + "."); |
|
1277 } |
|
1278 return result; |
|
1279 } |
|
1280 |
|
1281 |
|
1282 /** |
|
1283 * Tests that the given value is valid for the given date-time component. |
|
1284 * @param {string} component a date-time component. |
|
1285 * @param {string} value the value to be tested. |
|
1286 * @return {boolean} true if the test succeeds. |
|
1287 * @exception if the test fails. |
|
1288 */ |
|
1289 |
|
1290 function testValidDateTimeComponentValue(component, value) { |
|
1291 if (getDateTimeComponentValues(component).indexOf(value) === -1) { |
|
1292 $ERROR("Invalid value " + value + " for date-time component " + component + "."); |
|
1293 } |
|
1294 return true; |
|
1295 } |
|
1296 |
|
1297 |
|
1298 /** |
|
1299 * Verifies that the actual array matches the expected one in length, elements, |
|
1300 * and element order. |
|
1301 * @param {Array} expected the expected array. |
|
1302 * @param {Array} actual the actual array. |
|
1303 * @return {boolean} true if the test succeeds. |
|
1304 * @exception if the test fails. |
|
1305 */ |
|
1306 function testArraysAreSame(expected, actual) { |
|
1307 for (i = 0; i < Math.max(actual.length, expected.length); i++) { |
|
1308 if (actual[i] !== expected[i]) { |
|
1309 $ERROR("Result array element at index " + i + " should be \"" + |
|
1310 expected[i] + "\" but is \"" + actual[i] + "\"."); |
|
1311 } |
|
1312 } |
|
1313 return true; |
|
1314 } |
|
1315 |