js/src/tests/test262/intl402/browser.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial