blob: a1ecd6dfbec3c4da2c3528783e9846ec82d7d39c [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:path/path.dart' as path;
import '../../localization/gen_l10n.dart';
import '../../localization/gen_l10n_types.dart';
import '../../localization/localizations_utils.dart';
import '../common.dart';
final String defaultArbPathString = path.join('lib', 'l10n');
const String defaultTemplateArbFileName = 'app_en_US.arb';
const String defaultOutputFileString = 'output-localization-file';
const String defaultClassNameString = 'AppLocalizations';
const String singleMessageArbFileString = '''{
"title": "Title",
"@title": {
"description": "Title for the application"
}
}''';
const String esArbFileName = 'app_es.arb';
const String singleEsMessageArbFileString = '''{
"title": "Título"
}''';
const String singleZhMessageArbFileString = '''{
"title": "标题"
}''';
void _standardFlutterDirectoryL10nSetup(FileSystem fs) {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile(esArbFileName)
.writeAsStringSync(singleEsMessageArbFileString);
}
void main() {
MemoryFileSystem fs;
setUp(() {
fs = MemoryFileSystem(
style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix
);
});
group('Setters', () {
test('happy path', () {
_standardFlutterDirectoryL10nSetup(fs);
expect(() {
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
}, returnsNormally);
});
test('setL10nDirectory fails if the directory does not exist', () {
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setL10nDirectory('lib');
} on FileSystemException catch (e) {
expect(e.message, contains('Make sure that the correct path was provided'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.setL10nDirectory should fail if the '
'directory does not exist.'
);
});
test('setL10nDirectory fails if input string is null', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setL10nDirectory(null);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.setL10nDirectory should fail if the '
'the input string is null.'
);
});
test('setTemplateArbFile fails if l10nDirectory is null', () {
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setTemplateArbFile(defaultTemplateArbFileName);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the '
'the l10nDirectory is null.'
);
});
test('setTemplateArbFile fails if templateArbFileName is null', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setTemplateArbFile(null);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the '
'the l10nDirectory is null.'
);
});
test('setTemplateArbFile fails if input string is null', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setTemplateArbFile(null);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the '
'the input string is null.'
);
});
test('setOutputFile fails if input string is null', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setOutputFile(null);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.setOutputFile should fail if the '
'the input string is null.'
);
});
test('setting className fails if input string is null', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.className = null;
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.className should fail if the '
'the input string is null.'
);
});
group('className should only take valid Dart class names', () {
LocalizationsGenerator generator;
setUp(() {
_standardFlutterDirectoryL10nSetup(fs);
generator = LocalizationsGenerator(fs);
try {
generator.setL10nDirectory(defaultArbPathString);
generator.setTemplateArbFile(defaultTemplateArbFileName);
generator.setOutputFile(defaultOutputFileString);
} on L10nException catch (e) {
throw TestFailure('Unexpected failure during test setup: ${e.message}');
}
});
test('fails on string with spaces', () {
try {
generator.className = 'String with spaces';
} on L10nException catch (e) {
expect(e.message, contains('is not a valid public Dart class name'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.className should fail if the '
'the input string is not a valid Dart class name.'
);
});
test('fails on non-alphanumeric symbols', () {
try {
generator.className = 'TestClass@123';
} on L10nException catch (e) {
expect(e.message, contains('is not a valid public Dart class name'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.className should fail if the '
'the input string is not a valid public Dart class name.'
);
});
test('fails on camel-case', () {
try {
generator.className = 'camelCaseClassName';
} on L10nException catch (e) {
expect(e.message, contains('is not a valid public Dart class name'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.className should fail if the '
'the input string is not a valid public Dart class name.'
);
});
test('fails when starting with a number', () {
try {
generator.className = '123ClassName';
} on L10nException catch (e) {
expect(e.message, contains('is not a valid public Dart class name'));
return;
}
fail(
'Attempting to set LocalizationsGenerator.className should fail if the '
'the input string is not a valid public Dart class name.'
);
});
});
});
group('parseArbFiles', () {
test('correctly initializes supportedLocales and supportedLanguageCodes properties', () {
_standardFlutterDirectoryL10nSetup(fs);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n$e');
}
expect(generator.supportedLocales.contains(LocaleInfo.fromString('en_US')), true);
expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), true);
});
test('correctly sorts supportedLocales and supportedLanguageCodes alphabetically', () {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
// Write files in non-alphabetical order so that read performs in that order
l10nDirectory.childFile('app_zh.arb')
.writeAsStringSync(singleZhMessageArbFileString);
l10nDirectory.childFile('app_es.arb')
.writeAsStringSync(singleEsMessageArbFileString);
l10nDirectory.childFile('app_en_US.arb')
.writeAsStringSync(singleMessageArbFileString);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n$e');
}
expect(generator.supportedLocales.first, LocaleInfo.fromString('en_US'));
expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es'));
expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('zh'));
});
test('adds preferred locales to the top of supportedLocales and supportedLanguageCodes', () {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_en_US.arb')
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile('app_es.arb')
.writeAsStringSync(singleEsMessageArbFileString);
l10nDirectory.childFile('app_zh.arb')
.writeAsStringSync(singleZhMessageArbFileString);
const String preferredSupportedLocaleString = '["zh", "es"]';
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
preferredSupportedLocaleString: preferredSupportedLocaleString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n$e');
}
expect(generator.supportedLocales.first, LocaleInfo.fromString('zh'));
expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es'));
expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('en_US'));
});
test(
'throws an error attempting to add preferred locales '
'with incorrect runtime type',
() {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_en_US.arb')
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile('app_es.arb')
.writeAsStringSync(singleEsMessageArbFileString);
l10nDirectory.childFile('app_zh.arb')
.writeAsStringSync(singleZhMessageArbFileString);
const String preferredSupportedLocaleString = '[44, "en_US"]';
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
preferredSupportedLocaleString: preferredSupportedLocaleString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
expect(
e.message,
contains('Incorrect runtime type'),
);
return;
}
fail(
'Should fail since an incorrect runtime type was used '
'in the preferredSupportedLocales list.'
);
},
);
test(
'throws an error attempting to add preferred locales '
'when there is no corresponding arb file for that '
'locale',
() {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_en_US.arb')
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile('app_es.arb')
.writeAsStringSync(singleEsMessageArbFileString);
l10nDirectory.childFile('app_zh.arb')
.writeAsStringSync(singleZhMessageArbFileString);
const String preferredSupportedLocaleString = '["am", "es"]';
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
preferredSupportedLocaleString: preferredSupportedLocaleString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
expect(
e.message,
contains("The preferred supported locale, 'am', cannot be added."),
);
return;
}
fail(
'Should fail since an unsupported locale was added '
'to the preferredSupportedLocales list.'
);
},
);
test('correctly sorts arbPathString alphabetically', () {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
// Write files in non-alphabetical order so that read performs in that order
l10nDirectory.childFile('app_zh.arb')
.writeAsStringSync(singleZhMessageArbFileString);
l10nDirectory.childFile('app_es.arb')
.writeAsStringSync(singleEsMessageArbFileString);
l10nDirectory.childFile('app_en_US.arb')
.writeAsStringSync(singleMessageArbFileString);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n$e');
}
if (Platform.isWindows) {
expect(generator.arbPathStrings.first, 'lib\\l10n\\app_en_US.arb');
expect(generator.arbPathStrings.elementAt(1), 'lib\\l10n\\app_es.arb');
expect(generator.arbPathStrings.elementAt(2), 'lib\\l10n\\app_zh.arb');
} else {
expect(generator.arbPathStrings.first, 'lib/l10n/app_en_US.arb');
expect(generator.arbPathStrings.elementAt(1), 'lib/l10n/app_es.arb');
expect(generator.arbPathStrings.elementAt(2), 'lib/l10n/app_zh.arb');
}
});
test('correctly parses @@locale property in arb file', () {
const String arbFileWithEnLocale = '''{
"@@locale": "en",
"title": "Title",
"@title": {
"description": "Title for the application"
}
}''';
const String arbFileWithZhLocale = '''{
"@@locale": "zh",
"title": "标题",
"@title": {
"description": "Title for the application"
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('first_file.arb')
.writeAsStringSync(arbFileWithEnLocale);
l10nDirectory.childFile('second_file.arb')
.writeAsStringSync(arbFileWithZhLocale);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: 'first_file.arb',
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n$e');
}
expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true);
expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true);
});
test('correctly prioritizes @@locale property in arb file over filename', () {
const String arbFileWithEnLocale = '''{
"@@locale": "en",
"title": "Stocks",
"@title": {
"description": "Title for the Stocks application"
}
}''';
const String arbFileWithZhLocale = '''{
"@@locale": "zh",
"title": "标题",
"@title": {
"description": "Title for the Stocks application"
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_es.arb')
.writeAsStringSync(arbFileWithEnLocale);
l10nDirectory.childFile('app_am.arb')
.writeAsStringSync(arbFileWithZhLocale);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: 'app_es.arb',
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n$e');
}
// @@locale property should hold higher priority
expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true);
expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true);
// filename should not be used since @@locale is specified
expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), false);
expect(generator.supportedLocales.contains(LocaleInfo.fromString('am')), false);
});
test('throws when arb file\'s locale could not be determined', () {
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true)
..childFile('app.arb')
.writeAsStringSync(singleMessageArbFileString);
try {
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: 'app.arb',
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
expect(e.message, contains('locale could not be determined'));
return;
}
fail(
'Since locale is not specified, setting languages and locales '
'should fail'
);
});
test('throws when the same locale is detected more than once', () {
const String secondMessageArbFileString = '''{
"market": "MARKET",
"@market": {
"description": "Label for the Market tab"
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_en.arb')
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile('app2_en.arb')
.writeAsStringSync(secondMessageArbFileString);
try {
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: 'app_en.arb',
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
} on L10nException catch (e) {
expect(e.message, contains('Multiple arb files with the same locale detected'));
return;
}
fail(
'Since en locale is specified twice, setting languages and locales '
'should fail'
);
});
});
group('generateClassMethods', () {
test('correctly generates a simple message with getter', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String get title {
return Intl.message(
'Title',
locale: _localeName,
name: 'title',
desc: 'Title for the application'
);
}
''');
});
test('correctly generates simple message method with parameters', () {
const String singleSimpleMessageWithPlaceholderArbFileString = '''{
"itemNumber": "Item {value}",
"@itemNumber": {
"description": "Item placement in list.",
"placeholders": {
"value": {
"example": "1"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleSimpleMessageWithPlaceholderArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String itemNumber(Object value) {
return Intl.message(
'Item \${value}',
locale: _localeName,
name: 'itemNumber',
desc: 'Item placement in list.',
args: <Object>[value]
);
}
''');
});
});
group('DateTime tests', () {
test('correctly generates simple message with dates', () {
const String singleDateMessageArbFileString = '''{
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String springBegins(DateTime springStartDate) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
String springBegins(Object springStartDate) {
return Intl.message(
'Spring begins on \${springStartDate}',
locale: _localeName,
name: 'springBegins',
desc: 'The first day of spring',
args: <Object>[springStartDate]
);
}
return springBegins(springStartDateString);
}
''');
});
test('throws an exception when improperly formatted date is passed in', () {
const String singleDateMessageArbFileString = '''{
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "asdf"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('asdf'));
expect(e.message, contains('springStartDate'));
expect(e.message, contains('does not have a corresponding DateFormat'));
return;
}
fail('Improper date formatting should throw an exception');
});
test('throws an exception when no format attribute is passed in', () {
const String singleDateMessageArbFileString = '''{
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('the "format" attribute needs to be set'));
return;
}
fail('Improper date formatting should throw an exception');
});
test('correctly generates simple message with date along with other placeholders', () {
const String singleDateMessageArbFileString = '''{
"springGreetings": "Since it's {springStartDate}, it's finally spring! {helloWorld}!",
"@springGreetings": {
"description": "A realization that it's finally the spring season, followed by a greeting.",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
},
"helloWorld": {}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String springGreetings(DateTime springStartDate, Object helloWorld) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
String springGreetings(Object springStartDate, Object helloWorld) {
return Intl.message(
"Since it's \${springStartDate}, it's finally spring! \${helloWorld}!",
locale: _localeName,
name: 'springGreetings',
desc: "A realization that it's finally the spring season, followed by a greeting.",
args: <Object>[springStartDate, helloWorld]
);
}
return springGreetings(springStartDateString, helloWorld);
}
''');
});
test('correctly generates simple message with multiple dates', () {
const String singleDateMessageArbFileString = '''{
"springRange": "Spring begins on {springStartDate} and ends on {springEndDate}",
"@springRange": {
"description": "The range of dates for spring in the year",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
},
"springEndDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String springRange(DateTime springStartDate, DateTime springEndDate) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
final DateFormat springEndDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springEndDateString = springEndDateDateFormat.format(springEndDate);
String springRange(Object springStartDate, Object springEndDate) {
return Intl.message(
'Spring begins on \${springStartDate} and ends on \${springEndDate}',
locale: _localeName,
name: 'springRange',
desc: 'The range of dates for spring in the year',
args: <Object>[springStartDate, springEndDate]
);
}
return springRange(springStartDateString, springEndDateString);
}
''');
});
test('correctly generates a plural message with DateTime placeholders', () {
const String pluralMessageWithDateTimePlaceholder = '''{
"helloWorlds": "{count,plural, =1{Hello World, today is {currentDate}}=2{Hello two worlds, today is {currentDate}}many{Hello all {count} worlds, today is {currentDate}}other{Hello other {count} worlds, today is {currentDate}}}",
"@helloWorlds": {
"placeholders": {
"count": {},
"currentDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithDateTimePlaceholder);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count, DateTime currentDate) {
final DateFormat currentDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String currentDateString = currentDateDateFormat.format(currentDate);
String helloWorlds(int count, Object currentDate) {
return Intl.plural(
count,
locale: _localeName,
name: \'helloWorlds\',
args: <Object>[count, currentDate],
one: \'Hello World, today is \${currentDateString}\',
two: \'Hello two worlds, today is \${currentDateString}\',
many: \'Hello all \${count} worlds, today is \${currentDateString}\',
other: \'Hello other \${count} worlds, today is \${currentDateString}\'
);
}
return helloWorlds(count, currentDateString);
}
''');
});
group('Number tests', () {
test('correctly generates simple message with numbers', () {
const String singleNumberMessage = '''{
"courseCompletion": "You have completed {progress} of the course.",
"@courseCompletion": {
"description": "The amount of progress the student has made in their class.",
"placeholders": {
"progress": {
"type": "double",
"format": "compact"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleNumberMessage);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String courseCompletion(double progress) {
final NumberFormat progressNumberFormat = NumberFormat.compact(
locale: _localeName,
);
final String progressString = progressNumberFormat.format(progress);
String courseCompletion(Object progress) {
return Intl.message(
'You have completed \${progress} of the course.',
locale: _localeName,
name: 'courseCompletion',
desc: 'The amount of progress the student has made in their class.',
args: <Object>[progress]
);
}
return courseCompletion(progressString);
}
''');
});
test('correctly adds optional named parameters to numbers', () {
const Set<String> numberFormatsWithNamedParameters = <String>{
'compact',
'compactCurrency',
'compactSimpleCurrency',
'compactLong',
'currency',
'decimalPercentPattern',
'simpleCurrency',
};
for (final String numberFormat in numberFormatsWithNamedParameters) {
final String singleNumberMessage = '''{
"courseCompletion": "You have completed {progress} of the course.",
"@courseCompletion": {
"description": "The amount of progress the student has made in their class.",
"placeholders": {
"progress": {
"type": "double",
"format": "$numberFormat",
"optionalParameters": {
"decimalDigits": 2
}
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleNumberMessage);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String courseCompletion(double progress) {
final NumberFormat progressNumberFormat = NumberFormat.$numberFormat(
locale: _localeName,
decimalDigits: 2,
);
final String progressString = progressNumberFormat.format(progress);
String courseCompletion(Object progress) {
return Intl.message(
'You have completed \${progress} of the course.',
locale: _localeName,
name: 'courseCompletion',
desc: 'The amount of progress the student has made in their class.',
args: <Object>[progress]
);
}
return courseCompletion(progressString);
}
''');}
});
test('correctly adds optional positional parameters to numbers', () {
const Set<String> numberFormatsWithPositionalParameters = <String>{
'decimalPattern',
'percentPattern',
'scientificPattern',
};
for (final String numberFormat in numberFormatsWithPositionalParameters) {
final String singleNumberMessage = '''{
"courseCompletion": "You have completed {progress} of the course.",
"@courseCompletion": {
"description": "The amount of progress the student has made in their class.",
"placeholders": {
"progress": {
"type": "double",
"format": "$numberFormat"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleNumberMessage);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String courseCompletion(double progress) {
final NumberFormat progressNumberFormat = NumberFormat.$numberFormat(_localeName);
final String progressString = progressNumberFormat.format(progress);
String courseCompletion(Object progress) {
return Intl.message(
'You have completed \${progress} of the course.',
locale: _localeName,
name: 'courseCompletion',
desc: 'The amount of progress the student has made in their class.',
args: <Object>[progress]
);
}
return courseCompletion(progressString);
}
''');
}
});
test('throws an exception when improperly formatted number is passed in', () {
const String singleDateMessageArbFileString = '''{
"courseCompletion": "You have completed {progress} of the course.",
"@courseCompletion": {
"description": "The amount of progress the student has made in their class.",
"placeholders": {
"progress": {
"type": "double",
"format": "asdf"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('asdf'));
expect(e.message, contains('progress'));
expect(e.message, contains('does not have a corresponding NumberFormat'));
return;
}
fail('Improper date formatting should throw an exception');
});
});
group('plural messages', () {
test('correctly generates a plural message', () {
const String singlePluralMessageArbFileString = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"placeholders": {
"count": {}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singlePluralMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count) {
return Intl.plural(
count,
locale: _localeName,
name: 'helloWorlds',
args: <Object>[count],
zero: 'Hello',
one: 'Hello World',
two: 'Hello two worlds',
few: 'Hello \${count} worlds',
many: 'Hello all \${count} worlds',
other: 'Hello other \${count} worlds'
);
}
'''
);
});
test('correctly generates a plural message with placeholders', () {
const String pluralMessageWithMultiplePlaceholders = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello {adjective} World}=2{Hello two {adjective} worlds}few{Hello {count} {adjective} worlds}many{Hello all {count} {adjective} worlds}other{Hello other {count} {adjective} worlds}}",
"@helloWorlds": {
"placeholders": {
"count": {},
"adjective": {}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithMultiplePlaceholders);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count, Object adjective) {
return Intl.plural(
count,
locale: _localeName,
name: 'helloWorlds',
args: <Object>[count, adjective],
zero: 'Hello',
one: 'Hello \${adjective} World',
two: 'Hello two \${adjective} worlds',
few: 'Hello \${count} \${adjective} worlds',
many: 'Hello all \${count} \${adjective} worlds',
other: 'Hello other \${count} \${adjective} worlds'
);
}
'''
);
});
test('correctly generates a plural message with DateTime placeholders', () {
const String pluralMessageWithDateTimePlaceholder = '''{
"helloWorlds": "{count,plural, =1{Hello World, today is {currentDate}}=2{Hello two worlds, today is {currentDate}}many{Hello all {count} worlds, today is {currentDate}}other{Hello other {count} worlds, today is {currentDate}}}",
"@helloWorlds": {
"placeholders": {
"count": {},
"currentDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithDateTimePlaceholder);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count, DateTime currentDate) {
final DateFormat currentDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String currentDateString = currentDateDateFormat.format(currentDate);
String helloWorlds(int count, Object currentDate) {
return Intl.plural(
count,
locale: _localeName,
name: \'helloWorlds\',
args: <Object>[count, currentDate],
one: \'Hello World, today is \${currentDateString}\',
two: \'Hello two worlds, today is \${currentDateString}\',
many: \'Hello all \${count} worlds, today is \${currentDateString}\',
other: \'Hello other \${count} worlds, today is \${currentDateString}\'
);
}
return helloWorlds(count, currentDateString);
}
'''
);
});
test('correctly generates a plural message with number placeholders', () {
const String pluralMessageWithDateTimePlaceholder = '''{
"helloWorlds": "{count,plural, =1{Hello World of {population} citizens}=2{Hello two worlds with {population} total citizens}many{Hello all {count} worlds, with a total of {population} citizens}other{Hello other {count} worlds, with a total of {population} citizens}}",
"@helloWorlds": {
"placeholders": {
"count": {},
"population": {
"type": "int",
"format": "compactLong"
}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithDateTimePlaceholder);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed: \n$e');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String helloWorlds(int count, int population) {
final NumberFormat populationNumberFormat = NumberFormat.compactLong(
locale: _localeName,
);
final String populationString = populationNumberFormat.format(population);
String helloWorlds(int count, Object population) {
return Intl.plural(
count,
locale: _localeName,
name: \'helloWorlds\',
args: <Object>[count, population],
one: \'Hello World of \${populationString} citizens\',
two: \'Hello two worlds with \${populationString} total citizens\',
many: \'Hello all \${count} worlds, with a total of \${populationString} citizens\',
other: \'Hello other \${count} worlds, with a total of \${populationString} citizens\'
);
}
return helloWorlds(count, populationString);
}
'''
);
});
test('should throw attempting to generate a plural message without placeholders', () {
const String pluralMessageWithoutPlaceholdersAttribute = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"description": "Improperly formatted since it has no placeholder attribute."
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithoutPlaceholdersAttribute);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format'));
return;
}
fail('Generating class methods without placeholders should not succeed');
});
test('should throw attempting to generate a plural message with an empty placeholders map', () {
const String pluralMessageWithEmptyPlaceholdersMap = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"description": "Improperly formatted since it has no placeholder attribute.",
"placeholders": {}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithEmptyPlaceholdersMap);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format'));
return;
}
fail('Generating class methods without placeholders should not succeed');
});
test('should throw attempting to generate a plural message with no resource attributes', () {
const String pluralMessageWithoutResourceAttributes = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}"
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithoutResourceAttributes);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Resource attribute "@helloWorlds" was not found'));
return;
}
fail('Generating plural class method without resource attributes should not succeed');
});
test('should throw attempting to generate a plural message with incorrect format for placeholders', () {
const String pluralMessageWithIncorrectPlaceholderFormat = '''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
"@helloWorlds": {
"placeholders": "Incorrectly a string, should be a map."
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(pluralMessageWithIncorrectPlaceholderFormat);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('is not properly formatted'));
expect(e.message, contains('Ensure that it is a map with string valued keys'));
return;
}
fail('Generating class methods with incorrect placeholder format should not succeed');
});
});
test('should throw when failing to parse the arb file', () {
const String arbFileWithTrailingComma = '''{
"title": "Stocks",
"@title": {
"description": "Title for the Stocks application"
},
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(arbFileWithTrailingComma);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on FormatException catch (e) {
expect(e.message, contains('Unexpected character'));
return;
}
fail(
'should fail with a FormatException due to a trailing comma in the '
'arb file.'
);
});
test('should throw when resource is missing resource attribute', () {
const String arbFileWithMissingResourceAttribute = '''{
"title": "Stocks"
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(arbFileWithMissingResourceAttribute);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Resource attribute "@title" was not found'));
return;
}
fail(
'should fail with a FormatException due to a trailing comma in the '
'arb file.'
);
});
group('checks for method/getter formatting', () {
test('cannot contain non-alphanumeric symbols', () {
const String nonAlphaNumericArbFile = '''{
"title!!": "Stocks",
"@title!!": {
"description": "Title for the Stocks application"
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(nonAlphaNumericArbFile);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Invalid key format'));
return;
}
fail('should fail due to non-alphanumeric character.');
});
test('must start with lowercase character', () {
const String nonAlphaNumericArbFile = '''{
"Title": "Stocks",
"@Title": {
"description": "Title for the Stocks application"
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(nonAlphaNumericArbFile);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Invalid key format'));
return;
}
fail('should fail since key starts with a non-lowercase.');
});
test('cannot start with a number', () {
const String nonAlphaNumericArbFile = '''{
"123title": "Stocks",
"@123title": {
"description": "Title for the Stocks application"
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(nonAlphaNumericArbFile);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on L10nException catch (e) {
expect(e.message, contains('Invalid key format'));
return;
}
fail('should fail since key starts with a number.');
});
});
});
group('generateOutputFile', () {
test('correctly generates the localizations classes', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
generator.generateOutputFile();
} on Exception catch (e) {
fail('Generating output localization file should succeed: \n$e');
}
final String outputFileString = generator.outputFile.readAsStringSync();
expect(outputFileString, contains('class AppLocalizations'));
expect(outputFileString, contains('class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>'));
});
});
group('generateString', () {
test('handles simple string', () {
expect(generateString('abc'), "'abc'");
});
test('handles string with quote', () {
expect(generateString("ab'c"), '''"ab'c"''');
});
test('handles string with double quote', () {
expect(generateString('ab"c'), """'ab"c'""");
});
test('handles string with both single and double quote', () {
expect(generateString('''a'b"c'''), """'''a'b"c'''""");
});
test('handles string with a triple single quote and a double quote', () {
expect(generateString("""a"b'''c"""), '''"""a"b\'''c"""''');
});
test('handles string with a triple double quote and a single quote', () {
expect(generateString('''a'b"""c'''), """'''a'b\"""c'''""");
});
test('handles string with both triple single and triple double quote', () {
expect(generateString('''a\'''\'''\''b"""c'''), """'a' "'''" "'''" '''''b\"""c'''""");
});
test('handles dollar', () {
expect(generateString(r'ab$c'), r"r'ab$c'");
});
test('handles back slash', () {
expect(generateString(r'ab\c'), r"r'ab\c'");
});
test("doesn't support multiline strings", () {
expect(() => generateString('ab\nc'), throwsA(isA<AssertionError>()));
});
});
}