From d71ffa59d5fa2df454bf1c1853b0ccfc87fe3bcd Mon Sep 17 00:00:00 2001 From: Norbert Lindenberg Date: Sun, 26 Aug 2012 20:50:24 -0700 Subject: Added new tests for chapters 10 to 13 of the ECMAScript Internationalization API Specification. --- test/harness/testIntl.js | 471 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) (limited to 'test/harness/testIntl.js') diff --git a/test/harness/testIntl.js b/test/harness/testIntl.js index 251f0a2ca..365b1555d 100644 --- a/test/harness/testIntl.js +++ b/test/harness/testIntl.js @@ -656,3 +656,474 @@ function isCanonicalizedStructurallyValidLanguageTag(locale) { canonicalizeLanguageTag(locale) === locale; } + +/** + * Tests whether the named options property is correctly handled by the given constructor. + * @param {object} Constructor the constructor to test. + * @param {string} property the name of the options property to test. + * @param {string} type the type that values of the property are expected to have + * @param {Array} [values] an array of allowed values for the property. Not needed for boolean. + * @param {any} fallback the fallback value that the property assumes if not provided. + * @param {object} testOptions additional options: + * @param {boolean} isOptional whether support for this property is optional for implementations. + * @param {boolean} noReturn whether the resulting value of the property is not returned. + * @param {boolean} isILD whether the resulting value of the property is implementation and locale dependent. + * @param {object} extra additional option to pass along, properties are value -> {option: value}. + * @return {boolean} whether the test succeeded. + */ +function testOption(Constructor, property, type, values, fallback, testOptions) { + var isOptional = testOptions !== undefined && testOptions.isOptional === true; + var noReturn = testOptions !== undefined && testOptions.noReturn === true; + var isILD = testOptions !== undefined && testOptions.isILD === true; + + function addExtraOptions(options, value, testOptions) { + if (testOptions !== undefined && testOptions.extra !== undefined) { + var extra; + if (value !== undefined && testOptions.extra[value] !== undefined) { + extra = testOptions.extra[value]; + } else if (testOptions.extra.any !== undefined) { + extra = testOptions.extra.any; + } + if (extra !== undefined) { + Object.getOwnPropertyNames(extra).forEach(function (prop) { + options[prop] = extra[prop]; + }); + } + } + } + + var testValues, options, obj, expected, actual, error; + + // test that the specified values are accepted. Also add values that convert to specified values. + if (type === "boolean") { + if (values === undefined) { + values = [true, false]; + } + testValues = values.slice(0); + testValues.push(888); + testValues.push(0); + } else if (type === "string") { + testValues = values.slice(0); + testValues.push({toString: function () { return values[0]; }}); + } + testValues.forEach(function (value) { + options = {}; + options[property] = value; + addExtraOptions(options, value, testOptions); + obj = new Constructor(undefined, options); + if (noReturn) { + if (obj.resolvedOptions().hasOwnProperty(property)) { + $ERROR("Option property " + property + " is returned, but shouldn't be."); + } + } else { + actual = obj.resolvedOptions()[property]; + if (isILD) { + if (actual !== undefined && values.indexOf(actual) === -1) { + $ERROR("Invalid value " + actual + " returned for property " + property + "."); + } + } else { + if (type === "boolean") { + expected = Boolean(value); + } else if (type === "string") { + expected = String(value); + } + if (actual !== expected && !(isOptional && actual === undefined)) { + $ERROR("Option value " + value + " for property " + property + + " was not accepted; got " + actual + " instead."); + } + } + } + }); + + // test that invalid values are rejected + if (type === "string") { + var invalidValues = ["invalidValue", -1, null]; + // assume that we won't have values in caseless scripts + if (values[0].toUpperCase() !== values[0]) { + invalidValues.push(values[0].toUpperCase()); + } else { + invalidValues.push(values[0].toLowerCase()); + } + invalidValues.forEach(function (value) { + options = {}; + options[property] = value; + addExtraOptions(options, value, testOptions); + error = undefined; + try { + obj = new Constructor(undefined, options); + } catch (e) { + error = e; + } + if (error === undefined) { + $ERROR("Invalid option value " + value + " for property " + property + " was not rejected."); + } else if (error.name !== "RangeError") { + $ERROR("Invalid option value " + value + " for property " + property + " was rejected with wrong error " + error.name + "."); + } + }); + } + + // test that fallback value or another valid value is used if no options value is provided + if (!noReturn) { + options = {}; + addExtraOptions(options, undefined, testOptions); + obj = new Constructor(undefined, options); + actual = obj.resolvedOptions()[property]; + if (!(isOptional && actual === undefined)) { + if (fallback !== undefined) { + if (actual !== fallback) { + $ERROR("Option fallback value " + fallback + " for property " + property + + " was not used; got " + actual + " instead."); + } + } else { + if (values.indexOf(actual) === -1 && !(isILD && actual === undefined)) { + $ERROR("Invalid value " + actual + " returned for property " + property + "."); + } + } + } + } + + return true; +} + + +/** + * Tests whether the named property of the given object has a valid value + * and the default attributes of the properties of an object literal. + * @param {Object} obj the object to be tested. + * @param {string} property the name of the property + * @param {Function|Array} valid either a function that tests value for validity and returns a boolean, + * an array of valid values. + * @exception if the property has an invalid value. + */ +function testProperty(obj, property, valid) { + var desc = Object.getOwnPropertyDescriptor(obj, property); + if (!desc.writable) { + $ERROR("Property " + property + " must be writable."); + } + if (!desc.enumerable) { + $ERROR("Property " + property + " must be enumerable."); + } + if (!desc.configurable) { + $ERROR("Property " + property + " must be configurable."); + } + var value = desc.value; + var isValid = (typeof valid === "function") ? valid(value) : (valid.indexOf(value) !== -1); + if (!isValid) { + $ERROR("Property value " + value + " is not allowed for property " + property + "."); + } +} + + +/** + * Tests whether the named property of the given object, if present at all, has a valid value + * and the default attributes of the properties of an object literal. + * @param {Object} obj the object to be tested. + * @param {string} property the name of the property + * @param {Function|Array} valid either a function that tests value for validity and returns a boolean, + * an array of valid values. + * @exception if the property is present and has an invalid value. + */ +function mayHaveProperty(obj, property, valid) { + if (obj.hasOwnProperty(property)) { + testProperty(obj, property, valid); + } +} + + +/** + * Tests whether the given object has the named property with a valid value + * and the default attributes of the properties of an object literal. + * @param {Object} obj the object to be tested. + * @param {string} property the name of the property + * @param {Function|Array} valid either a function that tests value for validity and returns a boolean, + * an array of valid values. + * @exception if the property is missing or has an invalid value. + */ +function mustHaveProperty(obj, property, valid) { + if (!obj.hasOwnProperty(property)) { + $ERROR("Object is missing property " + property + "."); + } + testProperty(obj, property, valid); +} + + +/** + * Tests whether the given object does not have the named property. + * @param {Object} obj the object to be tested. + * @param {string} property the name of the property + * @exception if the property is present. + */ +function mustNotHaveProperty(obj, property) { + if (obj.hasOwnProperty(property)) { + $ERROR("Object has property it mustn't have: " + property + "."); + } +} + + +/** + * Tests whether name is a valid BCP 47 numbering system name + * and not excluded from use in the ECMAScript Internationalization API. + * @param {string} name the name to be tested. + * @return {boolean} whether name is a valid BCP 47 numbering system name and + * allowed for use in the ECMAScript Internationalization API. + */ + +function isValidNumberingSystem(name) { + + // source: CLDR file common/bcp47/number.xml; version CLDR 21. + var numberingSystems = [ + "arab", + "arabext", + "armn", + "armnlow", + "bali", + "beng", + "brah", + "cakm", + "cham", + "deva", + "ethi", + "finance", + "fullwide", + "geor", + "grek", + "greklow", + "gujr", + "guru", + "hanidec", + "hans", + "hansfin", + "hant", + "hantfin", + "hebr", + "java", + "jpan", + "jpanfin", + "kali", + "khmr", + "knda", + "osma", + "lana", + "lanatham", + "laoo", + "latn", + "lepc", + "limb", + "mlym", + "mong", + "mtei", + "mymr", + "mymrshan", + "native", + "nkoo", + "olck", + "orya", + "roman", + "romanlow", + "saur", + "shrd", + "sora", + "sund", + "talu", + "takr", + "taml", + "tamldec", + "telu", + "thai", + "tibt", + "traditio", + "vaii" + ]; + + var excluded = [ + "finance", + "native", + "traditio" + ]; + + + return numberingSystems.indexOf(name) !== -1 && excluded.indexOf(name) === -1; +} + + +/** + * Provides the digits of numbering systems with simple digit mappings, + * as specified in 11.3.2. + */ + +var numberingSystemDigits = { + arab: "٠١٢٣٤٥٦٧٨٩", + arabext: "۰۱۲۳۴۵۶۷۸۹", + beng: "০১২৩৪৫৬৭৮৯", + deva: "०१२३४५६७८९", + fullwide: "0123456789", + gujr: "૦૧૨૩૪૫૬૭૮૯", + guru: "੦੧੨੩੪੫੬੭੮੯", + hanidec: "〇一二三四五六七八九", + khmr: "០១២៣៤៥៦៧៨៩", + knda: "೦೧೨೩೪೫೬೭೮೯", + laoo: "໐໑໒໓໔໕໖໗໘໙", + latn: "0123456789", + mlym: "൦൧൨൩൪൫൬൭൮൯", + mong: "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙", + mymr: "၀၁၂၃၄၅၆၇၈၉", + orya: "୦୧୨୩୪୫୬୭୮୯", + tamldec: "௦௧௨௩௪௫௬௭௮௯", + telu: "౦౧౨౩౪౫౬౭౮౯", + thai: "๐๑๒๓๔๕๖๗๘๙", + tibt: "༠༡༢༣༤༥༦༧༨༩" +}; + + +/** + * Tests that number formatting is handled correctly. The function checks that the + * digit sequences in formatted output are as specified, converted to the + * selected numbering system, and embedded in consistent localized patterns. + * @param {Array} locales the locales to be tested. + * @param {Array} numberingSystems the numbering systems to be tested. + * @param {Object} options the options to pass to Intl.NumberFormat. Options + * must include {useGrouping: false}, and must cause 1.1 to be formatted + * pre- and post-decimal digits. + * @param {Object} testData maps input data (in ES5 9.3.1 format) to expected output strings + * in unlocalized format with Western digits. + */ + +function testNumberFormat(locales, numberingSystems, options, testData) { + locales.forEach(function (locale) { + numberingSystems.forEach(function (numbering) { + var digits = numberingSystemDigits[numbering]; + var format = new Intl.NumberFormat([locale + "-u-nu-" + numbering], options); + + function getPatternParts(positive) { + var n = positive ? 1.1 : -1.1; + var formatted = format.format(n); + var oneoneRE = "([^" + digits + "]*)[" + digits + "]+([^" + digits + "]+)[" + digits + "]+([^" + digits + "]*)"; + var match = formatted.match(new RegExp(oneoneRE)); + if (match === null) { + $ERROR("Unexpected formatted " + n + " for " + + format.resolvedOptions().locale + " and options " + + JSON.stringify(options) + ": " + formatted); + } + return match; + } + + function toNumbering(raw) { + return raw.replace(/[0-9]/g, function (digit) { + return digits[digit.charCodeAt(0) - "0".charCodeAt(0)]; + }); + } + + function buildExpected(raw, patternParts) { + var period = raw.indexOf("."); + if (period === -1) { + return patternParts[1] + toNumbering(raw) + patternParts[3]; + } else { + return patternParts[1] + + toNumbering(raw.substring(0, period)) + + patternParts[2] + + toNumbering(raw.substring(period + 1)) + + patternParts[3]; + } + } + + if (format.resolvedOptions().numberingSystem === numbering) { + // figure out prefixes, infixes, suffixes for positive and negative values + var posPatternParts = getPatternParts(true); + var negPatternParts = getPatternParts(false); + + Object.getOwnPropertyNames(testData).forEach(function (input) { + var rawExpected = testData[input]; + var patternParts; + if (rawExpected[0] === "-") { + patternParts = negPatternParts; + rawExpected = rawExpected.substring(1); + } else { + patternParts = posPatternParts; + } + var expected = buildExpected(rawExpected, patternParts); + var actual = format.format(input); + if (actual !== expected) { + $ERROR("Formatted value for " + input + ", " + + format.resolvedOptions().locale + " and options " + + JSON.stringify(options) + " is " + actual + "; expected " + expected + "."); + } + }); + } + }); + }); +} + + +/** + * Return the components of date-time formats. + * @return {Array} an array with all date-time components. + */ + +function getDateTimeComponents() { + return ["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"]; +} + + +/** + * Return the valid values for the given date-time component, as specified + * by the table in section 12.1.1. + * @param {string} component a date-time component. + * @return {Array} an array with the valid values for the component. + */ + +function getDateTimeComponentValues(component) { + + var components = { + weekday: ["narrow", "short", "long"], + era: ["narrow", "short", "long"], + year: ["2-digit", "numeric"], + month: ["2-digit", "numeric", "narrow", "short", "long"], + day: ["2-digit", "numeric"], + hour: ["2-digit", "numeric"], + minute: ["2-digit", "numeric"], + second: ["2-digit", "numeric"], + timeZoneName: ["short", "long"] + }; + + var result = components[component]; + if (result === undefined) { + $ERROR("Internal error: No values defined for date-time component " + component + "."); + } + return result; +} + + +/** + * Tests that the given value is valid for the given date-time component. + * @param {string} component a date-time component. + * @param {string} value the value to be tested. + * @return {boolean} true if the test succeeds. + * @exception if the test fails. + */ + +function testValidDateTimeComponentValue(component, value) { + if (getDateTimeComponentValues(component).indexOf(value) === -1) { + $ERROR("Invalid value " + value + " for date-time component " + component + "."); + } + return true; +} + + +/** + * Verifies that the actual array matches the expected one in length, elements, + * and element order. + * @param {Array} expected the expected array. + * @param {Array} actual the actual array. + * @return {boolean} true if the test succeeds. + * @exception if the test fails. + */ +function testArraysAreSame(expected, actual) { + for (i = 0; i < Math.max(actual.length, expected.length); i++) { + if (actual[i] !== expected[i]) { + $ERROR("Result array element at index " + i + " should be \"" + + expected[i] + "\" but is \"" + actual[i] + "\"."); + } + } + return true; +} + -- cgit v1.2.3