Add tests for Intl.Locale.prototype.variants

Ref https://github.com/tc39/ecma402/pull/960
This commit is contained in:
Richard Gibson 2025-05-08 17:49:11 -04:00 committed by Ms2ger
parent c6ad284d99
commit c2bfc5bdcd
10 changed files with 341 additions and 22 deletions

View File

@ -44,6 +44,16 @@ new Intl.Locale(
}
},
get variants() {
order.push("get variants");
return {
toString() {
order.push("toString variants");
return "fonipa-1996";
}
}
},
get calendar() {
order.push("get calendar");
return {
@ -109,6 +119,8 @@ const expected_order = [
"toString script",
"get region",
"toString region",
"get variants",
"toString variants",
"get calendar",
"toString calendar",
"get collation",

View File

@ -13,6 +13,7 @@ const options = [
"language",
"script",
"region",
"variants",
"calendar",
"collation",
"hourCycle",

View File

@ -0,0 +1,54 @@
// Copyright 2025 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-intl.locale
description: >
Checks error cases for the options argument to the Locale
constructor.
info: |
Intl.Locale( tag [, options] )
12. Set _tag_ to ? UpdateLanguageId(_tag_, _options_).
UpdateLanguageId ( tag, options )
8. Let _variants_ be ? GetOption(_options_, *"variants"*, ~string~, ~empty~, GetLocaleVariants(_baseName_)).
9. If _variants_ is not *undefined*, then
a. If _variants_ cannot be matched by the <code>unicode_variant_subtag</code> Unicode locale nonterminal, throw a *RangeError* exception.
features: [Intl.Locale]
---*/
/*
unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
*/
const invalidVariantsOptions = [
"",
"a",
"1",
"ab",
"2x",
"abc",
"3xy",
"abcd",
"abcdefghi",
// Value contains more than just the 'variants' production.
"GB-scouse",
// Value contains duplicates.
"fonipa-fonipa",
"fonipa-valencia-Fonipa",
// Value contains out-of-place dashes.
"-",
"-spanglis",
"spanglis-",
"-spanglis-oxendict",
"spanglis-oxendict-",
"spanglis--oxendict",
];
for (const variants of invalidVariantsOptions) {
assert.throws(RangeError, function() {
new Intl.Locale("en", {variants});
}, `new Intl.Locale("en", {variants: "${variants}"}) throws RangeError`);
}

View File

@ -0,0 +1,60 @@
// Copyright 2025 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-intl.locale
description: >
Checks error cases for the options argument to the Locale
constructor.
info: |
Intl.Locale( tag [, options] )
12. Set _tag_ to ? UpdateLanguageId(_tag_, _options_).
UpdateLanguageId ( tag, options )
8. Let _variants_ be ? GetOption(_options_, *"variants"*, ~string~, ~empty~, GetLocaleVariants(_baseName_)).
...
13. If _variants_ is not *undefined*, set _newTag_ to the string-concatenation of _newTag_, *"-"*, and _variants_.
features: [Intl.Locale]
---*/
const validVariantsOptions = [
['en', undefined, undefined],
['en', 'spanglis', 'en-spanglis'],
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
['xx', '1xyz', 'xx-1xyz'],
['xx', '1234', 'xx-1234'],
['xx', 'abcde', 'xx-abcde'],
['xx', '12345678', 'xx-12345678'],
['xx', '1xyz-1234-abcde-12345678', 'xx-1234-12345678-1xyz-abcde'],
// Canonicalization affects subtag ordering.
['en', 'spanglis-oxendict', 'en-oxendict-spanglis'],
];
for (const [lang, variants, baseName] of validVariantsOptions) {
let options = { variants };
let optionsRepr = `{variants: ${typeof variants === "string" ? `"${variants}"` : variants}}`;
let instance;
let expect;
instance = new Intl.Locale(lang, options);
expect = baseName || lang;
assert.sameValue(instance.toString(), expect,
`new Intl.Locale("${lang}", ${optionsRepr}).toString() returns "${expect}"`);
instance = new Intl.Locale(lang + '-fonipa', options);
expect = baseName || (lang + '-fonipa');
assert.sameValue(instance.toString(), expect,
`new Intl.Locale("${lang}-fonipa", ${optionsRepr}).toString() returns "${expect}"`);
instance = new Intl.Locale(lang + '-u-ca-gregory', options);
expect = (baseName || lang) + '-u-ca-gregory';
assert.sameValue(instance.toString(), expect,
`new Intl.Locale("${lang}-u-ca-gregory", ${optionsRepr}).toString() returns "${expect}"`);
instance = new Intl.Locale(lang + '-fonipa-u-ca-gregory', options);
expect = (baseName || (lang + '-fonipa')) + '-u-ca-gregory';
assert.sameValue(instance.toString(), expect,
`new Intl.Locale("${lang}-fonipa-u-ca-gregory", ${optionsRepr}).toString() returns "${expect}"`);
}

View File

@ -7,21 +7,70 @@ description: >
Verifies getters with grandfathered tags.
info: |
get Intl.Locale.prototype.baseName
5. Return the substring of locale corresponding to the
language ["-" script] ["-" region] *("-" variant)
subsequence of the unicode_language_id grammar.
3. Return GetLocaleBaseName(_loc_.[[Locale]]).
GetLocaleBaseName
2. Return the longest prefix of _locale_ matched by the
<code>unicode_language_id</code> Unicode locale nonterminal.
get Intl.Locale.prototype.language
5. Return the substring of locale corresponding to the
unicode_language_subtag production.
3. Return GetLocaleLanguage(_loc_.[[Locale]]).
GetLocaleLanguage
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. Assert: The first subtag of _baseName_ can be matched by the
<code>unicode_language_subtag</code> Unicode locale nonterminal.
3. Return the first subtag of _baseName_.
get Intl.Locale.prototype.script
6. Return the substring of locale corresponding to the
unicode_script_subtag production.
3. Return GetLocaleScript(_loc_.[[Locale]]).
GetLocaleScript
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. Assert: _baseName_ contains at most one subtag that can be matched by the
<code>unicode_script_subtag</code> Unicode locale nonterminal.
3. If _baseName_ contains a subtag matched by the
<code>unicode_script_subtag</code> Unicode locale nonterminal, return
that subtag.
4. Return *undefined*.
get Intl.Locale.prototype.region
6. Return the substring of locale corresponding to the unicode_region_subtag
production.
3. Return GetLocaleRegion(_loc_.[[Locale]]).
GetLocaleRegion
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. NOTE: A <code>unicode_region_subtag</code> subtag is only valid
immediately after an initial <code>unicode_language_subtag</code> subtag,
optionally with a single <code>unicode_script_subtag</code> subtag
between them. In that position, <code>unicode_region_subtag</code> cannot
be confused with any other valid subtag because all their productions are
disjoint.
3. Assert: The first subtag of _baseName_ can be matched by the
<code>unicode_language_subtag</code> Unicode locale nonterminal.
4. Let _baseNameTail_ be the suffix of _baseName_ following the first
subtag.
5. Assert: _baseNameTail_ contains at most one subtag that can be matched by
the <code>unicode_region_subtag</code> Unicode locale nonterminal.
6. If _baseNameTail_ contains a subtag matched by the
<code>unicode_region_subtag</code> Unicode locale nonterminal, return
that subtag.
7. Return *undefined*.
get Intl.Locale.prototype.variants
3. Return GetLocaleVariants(_loc_.[[Locale]]).
GetLocaleVariants
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. NOTE: Each subtag in _baseName_ that is preceded by *"-"* is either a
<code>unicode_script_subtag</code>, <code>unicode_region_subtag</code>,
or <code>unicode_variant_subtag</code>, but any substring matched by
<code>unicode_variant_subtag</code> is strictly longer than any prefix
thereof which could also be matched by one of the other productions.
3. Let _variants_ be the longest suffix of _baseName_ that starts with a
*"-"* followed by a <emu-not-ref>substring</emu-not-ref> that is matched
by the <code>unicode_variant_subtag</code> Unicode locale nonterminal. If
there is no such suffix, return *undefined*.
4. Return the substring of _variants_ from 1.
features: [Intl.Locale]
---*/
@ -31,6 +80,14 @@ assert.sameValue(loc.baseName, "xtg");
assert.sameValue(loc.language, "xtg");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, undefined);
assert.sameValue(loc.variants, undefined);
loc = new Intl.Locale("cel", { variants: "gaulish" });
assert.sameValue(loc.baseName, "xtg");
assert.sameValue(loc.language, "xtg");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, undefined);
assert.sameValue(loc.variants, undefined);
// Regular grandfathered language tag.
assert.throws(RangeError, () => new Intl.Locale("zh-min"));

View File

@ -30,6 +30,7 @@ assert.sameValue(loc.baseName, "sv");
assert.sameValue(loc.language, "sv");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, undefined);
assert.sameValue(loc.variants, undefined);
// 'region' subtag not present.
var loc = new Intl.Locale("sv-Latn");
@ -37,6 +38,7 @@ assert.sameValue(loc.baseName, "sv-Latn");
assert.sameValue(loc.language, "sv");
assert.sameValue(loc.script, "Latn");
assert.sameValue(loc.region, undefined);
assert.sameValue(loc.variants, undefined);
// 'script' subtag not present.
var loc = new Intl.Locale("sv-SE");
@ -44,6 +46,7 @@ assert.sameValue(loc.baseName, "sv-SE");
assert.sameValue(loc.language, "sv");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, "SE");
assert.sameValue(loc.variants, undefined);
// 'variant' subtag present.
var loc = new Intl.Locale("de-1901");
@ -51,3 +54,4 @@ assert.sameValue(loc.baseName, "de-1901");
assert.sameValue(loc.language, "de");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, undefined);
assert.sameValue(loc.variants, '1901');

View File

@ -10,18 +10,70 @@ info: |
3. Return loc.[[Locale]].
get Intl.Locale.prototype.baseName
5. Return the substring of locale corresponding to the
language ["-" script] ["-" region] *("-" variant)
subsequence of the langtag grammar.
3. Return GetLocaleBaseName(_loc_.[[Locale]]).
GetLocaleBaseName
2. Return the longest prefix of _locale_ matched by the
<code>unicode_language_id</code> Unicode locale nonterminal.
get Intl.Locale.prototype.language
4. Return the substring of locale corresponding to the language production.
3. Return GetLocaleLanguage(_loc_.[[Locale]]).
GetLocaleLanguage
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. Assert: The first subtag of _baseName_ can be matched by the
<code>unicode_language_subtag</code> Unicode locale nonterminal.
3. Return the first subtag of _baseName_.
get Intl.Locale.prototype.script
7. Return the substring of locale corresponding to the script production.
3. Return GetLocaleScript(_loc_.[[Locale]]).
GetLocaleScript
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. Assert: _baseName_ contains at most one subtag that can be matched by the
<code>unicode_script_subtag</code> Unicode locale nonterminal.
3. If _baseName_ contains a subtag matched by the
<code>unicode_script_subtag</code> Unicode locale nonterminal, return
that subtag.
4. Return *undefined*.
get Intl.Locale.prototype.region
7. Return the substring of locale corresponding to the region production.
3. Return GetLocaleRegion(_loc_.[[Locale]]).
GetLocaleRegion
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. NOTE: A <code>unicode_region_subtag</code> subtag is only valid
immediately after an initial <code>unicode_language_subtag</code> subtag,
optionally with a single <code>unicode_script_subtag</code> subtag
between them. In that position, <code>unicode_region_subtag</code> cannot
be confused with any other valid subtag because all their productions are
disjoint.
3. Assert: The first subtag of _baseName_ can be matched by the
<code>unicode_language_subtag</code> Unicode locale nonterminal.
4. Let _baseNameTail_ be the suffix of _baseName_ following the first
subtag.
5. Assert: _baseNameTail_ contains at most one subtag that can be matched by
the <code>unicode_region_subtag</code> Unicode locale nonterminal.
6. If _baseNameTail_ contains a subtag matched by the
<code>unicode_region_subtag</code> Unicode locale nonterminal, return
that subtag.
7. Return *undefined*.
get Intl.Locale.prototype.variants
3. Return GetLocaleVariants(_loc_.[[Locale]]).
GetLocaleVariants
1. Let _baseName_ be GetLocaleBaseName(_locale_).
2. NOTE: Each subtag in _baseName_ that is preceded by *"-"* is either a
<code>unicode_script_subtag</code>, <code>unicode_region_subtag</code>,
or <code>unicode_variant_subtag</code>, but any substring matched by
<code>unicode_variant_subtag</code> is strictly longer than any prefix
thereof which could also be matched by one of the other productions.
3. Let _variants_ be the longest suffix of _baseName_ that starts with a
*"-"* followed by a <emu-not-ref>substring</emu-not-ref> that is matched
by the <code>unicode_variant_subtag</code> Unicode locale nonterminal. If
there is no such suffix, return *undefined*.
4. Return the substring of _variants_ from 1.
get Intl.Locale.prototype.calendar
3. Return loc.[[Calendar]].
@ -47,14 +99,15 @@ features: [Intl.Locale]
---*/
// Test all getters return the expected results.
var langtag = "de-latn-de-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn";
var langtag = "de-latn-de-fonipa-1996-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn";
var loc = new Intl.Locale(langtag);
assert.sameValue(loc.toString(), "de-Latn-DE-u-ca-gregory-co-phonebk-hc-h23-kf-kn-false-nu-latn");
assert.sameValue(loc.baseName, "de-Latn-DE");
assert.sameValue(loc.toString(), "de-Latn-DE-1996-fonipa-u-ca-gregory-co-phonebk-hc-h23-kf-kn-false-nu-latn");
assert.sameValue(loc.baseName, "de-Latn-DE-1996-fonipa");
assert.sameValue(loc.language, "de");
assert.sameValue(loc.script, "Latn");
assert.sameValue(loc.region, "DE");
assert.sameValue(loc.variants, "1996-fonipa");
assert.sameValue(loc.calendar, "gregory");
assert.sameValue(loc.collation, "phonebk");
assert.sameValue(loc.hourCycle, "h23");
@ -72,6 +125,7 @@ var loc = new Intl.Locale(langtag, {
language: "ja",
script: "jpan",
region: "jp",
variants: "Hepburn",
calendar: "japanese",
collation: "search",
hourCycle: "h24",
@ -80,11 +134,12 @@ var loc = new Intl.Locale(langtag, {
numberingSystem: "jpanfin",
});
assert.sameValue(loc.toString(), "ja-Jpan-JP-u-ca-japanese-co-search-hc-h24-kf-false-kn-nu-jpanfin");
assert.sameValue(loc.baseName, "ja-Jpan-JP");
assert.sameValue(loc.toString(), "ja-Jpan-JP-hepburn-u-ca-japanese-co-search-hc-h24-kf-false-kn-nu-jpanfin");
assert.sameValue(loc.baseName, "ja-Jpan-JP-hepburn");
assert.sameValue(loc.language, "ja");
assert.sameValue(loc.script, "Jpan");
assert.sameValue(loc.region, "JP");
assert.sameValue(loc.variants, "hepburn");
assert.sameValue(loc.calendar, "japanese");
assert.sameValue(loc.collation, "search");
assert.sameValue(loc.hourCycle, "h24");
@ -105,11 +160,12 @@ var loc = new Intl.Locale(langtag, {
hourCycle: "h11",
});
assert.sameValue(loc.toString(), "fr-Latn-CA-u-ca-gregory-co-standard-hc-h11-kf-kn-false-nu-latn");
assert.sameValue(loc.baseName, "fr-Latn-CA");
assert.sameValue(loc.toString(), "fr-Latn-CA-1996-fonipa-u-ca-gregory-co-standard-hc-h11-kf-kn-false-nu-latn");
assert.sameValue(loc.baseName, "fr-Latn-CA-1996-fonipa");
assert.sameValue(loc.language, "fr");
assert.sameValue(loc.script, "Latn");
assert.sameValue(loc.region, "CA");
assert.sameValue(loc.variants, "1996-fonipa");
assert.sameValue(loc.calendar, "gregory");
assert.sameValue(loc.collation, "standard");
assert.sameValue(loc.hourCycle, "h11");
@ -129,6 +185,7 @@ assert.sameValue(loc.baseName, "und");
assert.sameValue(loc.language, "und");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, undefined);
assert.sameValue(loc.variants, undefined);
var loc = new Intl.Locale("und-US-u-co-emoji");
@ -137,6 +194,7 @@ assert.sameValue(loc.baseName, "und-US");
assert.sameValue(loc.language, "und");
assert.sameValue(loc.script, undefined);
assert.sameValue(loc.region, "US");
assert.sameValue(loc.variants, undefined);
if ("collation" in loc) {
assert.sameValue(loc.collation, "emoji");
}

View File

@ -0,0 +1,28 @@
// Copyright 2025 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-Intl.Locale.prototype.variants
description: >
Verifies the branding check for the "variants" property of the Locale prototype object.
info: |
Intl.Locale.prototype.variants
2. Perform ? RequireInternalSlot(_loc_, [[InitializedLocale]]).
features: [Intl.Locale]
---*/
const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "variants");
const invalidValues = [
undefined,
null,
true,
"",
Symbol(),
1,
{},
Intl.Locale.prototype,
];
for (const invalidValue of invalidValues) {
assert.throws(TypeError, () => propdesc.get.call(invalidValue));
}

View File

@ -0,0 +1,22 @@
// Copyright 2025 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-intl.locale.prototype.variants
description: >
Checks the "name" property of Intl.Locale.prototype.variants.
info: |
Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
Every built-in function object, including constructors, that is not identified as an anonymous function has a name property whose value is a String. Unless otherwise specified, this value is the name that is given to the function in this specification. Functions that are specified as get or set accessor functions of built-in properties have "get " or "set " prepended to the property name string.
Unless otherwise specified, the name property of a built-in function object, if it exists, has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
includes: [propertyHelper.js]
features: [Intl.Locale]
---*/
const getter = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "variants").get;
verifyProperty(getter, "name", {
value: "get variants",
writable: false,
enumerable: false,
configurable: true,
});

View File

@ -0,0 +1,23 @@
// Copyright 2025 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-intl.locale
description: >
Checks the "variants" property of the Locale prototype object.
info: |
Unless specified otherwise in this document, the objects, functions, and constructors described in this standard are subject to the generic requirements and restrictions specified for standard built-in ECMAScript objects in the ECMAScript 2019 Language Specification, 10th edition, clause 17, or successor.
Every accessor property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified. If only a get accessor function is described, the set accessor function is the default value, undefined.
includes: [propertyHelper.js]
features: [Intl.Locale]
---*/
const propdesc = Object.getOwnPropertyDescriptor(Intl.Locale.prototype, "variants");
assert.sameValue(propdesc.set, undefined);
assert.sameValue(typeof propdesc.get, "function");
verifyProperty(Intl.Locale.prototype, "variants", {
enumerable: false,
configurable: true,
});