blob: 774fa5a0a887bdc238cccc237103fbce1b68f9b0 [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/src/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:', () {
testExceptionForSubtags(String language, String script, String region) {
test('fromSubtags: "$language / $script / $region"', () {
expect(
() => Locale.fromSubtags(
languageCode: language,
scriptCode: script,
countryCode: region),
throwsFormatException);
});
}
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]:
// http://unicode.org/reports/tr35/#Unicode_Locale_Identifier_CLDR_to_BCP_47
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]: http://unicode.org/reports/tr35/#Unicode_locale_identifier
testParse('en-scouse-fonipa', 'en', null, null, ['fonipa', 'scouse'],
'en-fonipa-scouse');
// Normalises tags, sorts subtags alphabetically and suppresses unneeded
// "true" in u extension (ICU is currently not dropping -true):
// http://unicode.org/reports/tr35/#u_Extension
testParse('en-u-Foo-bar-nu-thai-ca-buddhist-kk-true', 'en', null, null, [],
'en-u-bar-foo-ca-buddhist-kk-nu-thai');
// 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', () {
expect(Locale.parse('en-u-ca').toLanguageTag(),
Locale.parse('en-u-ca-true').toLanguageTag());
});
});
// 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:', () {
testExceptionForId(String x) {
test('"$x"', () {
expect(() => Locale.parse(x), throwsFormatException);
});
}
invalidLocales.forEach((badLocaleIdentifier) {
testExceptionForId(badLocaleIdentifier);
});
// ICU permits '', taking it as 'und', but it is not a valid Unicode Locale
// Identifier: We reject it.
testExceptionForId('');
// 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.
testExceptionForId('abcd-Latn');
// ICU permits 'root-Latn' since it conforms to pure BCP47, but it is an
// invalid Unicode BCP47 Locale Identifier.
testExceptionForId('root-Latn');
// ICU permits empty tkeys.
testExceptionForId('en-t-a0');
// ICU permits duplicate tkeys, returning the content of -t- verbatim.
testExceptionForId('en-t-a0-one-a0-two');
// ICU permits duplicate keys, in this case dropping -ca-buddhist.
testExceptionForId('en-u-ca-islamic-ca-buddhist');
});
group('Locale.tryParse() returns null:', () {
invalidLocales.forEach((badLocaleIdentifier) {
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;
bool good = false;
try {
v.add('basiceng');
} 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);
});
}
testFromSubtags(
String language,
String script,
String region,
String expectedLanguage,
String expectedScript,
String expectedRegion,
String expectedTag) {
test('Locale.fromSubtags(...) with $language, $script, $region', () {
Locale 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);
});
}
testParse(
String bcp47Tag,
String expectedLanguage,
String expectedScript,
String expectedRegion,
Iterable<String> expectedVariants,
String expectedTag) {
test('Locale.parse("$bcp47Tag");', () {
Locale 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));
});
}