blob: 6698e4d38eda022fdb9be40cfea3fcc92be5a77f [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Tests for the Locale class.
/// Currently, the primary intention of these tests is to exercise and
/// demonstrate the API: full test coverage is a non-goal for the prototype.
/// For production code, use of ICU would influence what needs and doesn't need
/// to be tested.
import 'package:test/test.dart';
import 'package:intl/locale.dart';
import 'locale_test_data.dart';
void main() {
group('Construction and properties:', () {
// Simple with normalization:
testFromSubtags('Zh', null, null, 'zh', null, null, 'zh');
testFromSubtags('zH', null, 'cn', 'zh', null, 'CN', 'zh-CN');
testFromSubtags('ZH', null, 'Cn', 'zh', null, 'CN', 'zh-CN');
testFromSubtags('zh', null, 'cN', 'zh', null, 'CN', 'zh-CN');
testFromSubtags('zh', 'hans', null, 'zh', 'Hans', null, 'zh-Hans');
testFromSubtags('ZH', 'HANS', 'CN', 'zh', 'Hans', 'CN', 'zh-Hans-CN');
// Region codes can be three digits.
testFromSubtags('es', null, '419', 'es', null, '419', 'es-419');
// While language is usually 2 characters, it can also be 3.
testFromSubtags('CKB', 'arab', null, 'ckb', 'Arab', null, 'ckb-Arab');
// With canonicalization:
testFromSubtags('Iw', null, null, 'he', null, null, 'he');
testFromSubtags('iW', null, null, 'he', null, null, 'he');
testFromSubtags('My', null, 'Bu', 'my', null, 'MM', 'my-MM');
group('Locale.fromSubtags() FormatExceptions:', () {
void testExceptionForSubtags(
String language, String script, String region) {
test('fromSubtags: "$language / $script / $region"', () {
() => Locale.fromSubtags(
languageCode: language,
scriptCode: script,
countryCode: region),
testExceptionForSubtags('a', null, null);
testExceptionForSubtags('en', 'ZA', null);
testExceptionForSubtags('en', null, 'Latn');
group('Locale normalization matching ICU.', () {
localeParsingTestData.forEach((unnormalized, normalized) {
test('Locale normalization: $unnormalized -> $normalized', () {
expect(Locale.parse(unnormalized).toLanguageTag(), normalized);
group('Unicode LDML Locale Identifier support', () {
// 'root' is a valid Unicode Locale Identifier, but should be taken as
// 'und'[1]. ICU's toLanguageTag still returns 'root'.
// [1]:
testParse('root', 'und', null, null, [], 'und');
testParse('Root', 'und', null, null, [], 'und');
testParse('ROOT', 'und', null, null, [], 'und');
// We support underscores, whereas ICU's `forLanguageTag` does
// not.
testParse('CKB_arab', 'ckb', 'Arab', null, [], 'ckb-Arab');
testParse('My_Bu', 'my', null, 'MM', [], 'my-MM');
// Normalises tags, sorts subtags alphabetically, including variants[1]:
// ICU is currently not sorting variants.
// [1]:
testParse('en-scouse-fonipa', 'en', null, null, ['fonipa', 'scouse'],
// Normalises tags, sorts subtags alphabetically and suppresses unneeded
// "true" in u extension (ICU is currently not dropping -true):
testParse('en-u-Foo-bar-nu-thai-ca-buddhist-kk-true', 'en', null, null, [],
// The specification does permit empty extensions for extensions other than
// u- and t-.
testParse('en-a', 'en', null, null, [], 'en');
testParse('en-x', 'en', null, null, [], 'en');
testParse('en-z', 'en', null, null, [], 'en');
// Normalization of `tlang` - ICU still returns -t-iw-bu.
testParse('en-t-iw-Bu', 'en', null, null, [], 'en-t-he-mm');
test('en-u-ca is equivalent to en-u-ca-true', () {
// Normalization: sorting of extension subtags:
testParse('en-z-abc-001-foo-fii-bar-u-cu-usd-co-phonebk', 'en', null, null,
[], 'en-u-co-phonebk-cu-usd-z-abc-001-foo-fii-bar');
group('Locale.parse() throws FormatException:', () {
void testExceptionForId(String x) {
test('"$x"', () {
expect(() => Locale.parse(x), throwsFormatException);
for (var badLocaleIdentifier in invalidLocales) {
// ICU permits '', taking it as 'und', but it is not a valid Unicode Locale
// Identifier: We reject it.
// abcd-Latn throws exceptions in our Dart implementation, whereas
// ECMAScript's Intl.Locale permits it. This is because the BCP47 spec
// still allows for the possible addition of 4-character languages in
// the future, whereas the Unicode Locale Identifiers spec bans it
// outright.
// ICU permits 'root-Latn' since it conforms to pure BCP47, but it is an
// invalid Unicode BCP47 Locale Identifier.
// ICU permits empty tkeys.
// ICU permits duplicate tkeys, returning the content of -t- verbatim.
// ICU permits duplicate keys, in this case dropping -ca-buddhist.
group('Locale.tryParse() returns null:', () {
for (var badLocaleIdentifier in invalidLocales) {
test('"$badLocaleIdentifier"', () {
expect(Locale.tryParse(badLocaleIdentifier), isNull);
// TODO: determine appropriate behaviour for the following examples.
// // 'mo' is deprecated, and is a tag that ought to be replaced by *two*
// // subtags (ro-MD), although Chrome Unstable also doesn't presently do
// // that (replaces it by 'ro' only).
// // TODO: check up on the Chrome implementation.
// testParse('mo', 'ro', null, 'MD', [], 'ro-MD');
// // Script deprecation.
// testParse('en-Qaai', 'en', 'Zinh', null, [], 'en-Zinh');
// // Variant deprecation.
// testParse('sv-aaland', 'sv', null, 'AX', [], 'sv-AX');
// // Variant deprecation.
// testParse('en-heploc', 'en', null, null, ['alalc97'], 'en-alalc97');
// // Variant deprecation.
// testParse('en-polytoni', 'en', null, null, ['polyton'], 'en-polyton');
test('Locale cannot be modified via the variants field', () {
var l = Locale.parse('en-scotland');
List<String> v = l.variants;
var good = false;
try {
} on Error {
good = true;
expect(l.toLanguageTag(), 'en-scotland');
expect(good, isTrue);
test('operator== and hashCode', () {
Locale l1, l2;
l1 = Locale.parse('en-Shaw-ZA');
l2 = Locale.fromSubtags(
languageCode: 'en', scriptCode: 'Shaw', countryCode: 'ZA');
expect(l1, l2);
expect(l1.hashCode, l2.hashCode);
l1 = Locale.parse('en');
l2 = Locale.fromSubtags(
languageCode: 'en', scriptCode: null, countryCode: null);
expect(l1, l2);
expect(l1.hashCode, l2.hashCode);
void testFromSubtags(
String language,
String script,
String region,
String expectedLanguage,
String expectedScript,
String expectedRegion,
String expectedTag) {
test('Locale.fromSubtags(...) with $language, $script, $region', () {
var l = Locale.fromSubtags(
languageCode: language, scriptCode: script, countryCode: region);
expect(l.languageCode, expectedLanguage);
expect(l.scriptCode, expectedScript);
expect(l.countryCode, expectedRegion);
expect(l.toLanguageTag(), expectedTag);
expect(l.toString(), expectedTag);
void testParse(
String bcp47Tag,
String expectedLanguage,
String expectedScript,
String expectedRegion,
Iterable<String> expectedVariants,
String expectedTag) {
test('Locale.parse("$bcp47Tag");', () {
var l = Locale.parse(bcp47Tag);
expect(l.languageCode, expectedLanguage);
expect(l.scriptCode, expectedScript);
expect(l.countryCode, expectedRegion);
expect(l.toLanguageTag(), expectedTag);
expect(l.variants, orderedEquals(expectedVariants));