flutter/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart
Tae Hyung Kim 5c44b1df0f
Refactor "gen-l10n" command to use same code path when "l10n.yaml" is present or not present (#125429)
I think this is a long needed change to the `gen-l10n` command.
Essentially, the arguments to `flutter gen-l10n` can be provided by two
different methods: via command line arguments or via the `l10n.yaml`
file. The existence of a `l10n.yaml` file causes the latter approach to
take precedence.

However, currently, there's several differences in how the two
approaches are handled, and most of the default arguments are all over
the place, causing unexpected issues such as #120457 or #120023.

This PR refactors the command so that
* `LocalizationOptions` are more consistent with the actual argument
names/yaml options.
* All default values are determined in `LocalizationOptions`'s
constructor (or in `argParser.addOption(...)` in the case a boolean
value needs to be explicitly true).
* New `parseLocalizationsOptionsFromCommand` function to parse
arguments.
* Parse `LocalizationOptions` at the beginning of `runCommand()` and
pass it to `generateLocalizations`.

Fixes #120023.
2023-04-26 11:49:25 -07:00

438 lines
16 KiB
Dart

// 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 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/generate_localizations.dart';
import '../../integration.shard/test_data/basic_project.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
import '../../src/test_flutter_command_runner.dart';
void main() {
late FileSystem fileSystem;
late BufferLogger logger;
late Artifacts artifacts;
late FakeProcessManager processManager;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
artifacts = Artifacts.test();
processManager = FakeProcessManager.empty();
});
testUsingContext('default l10n settings', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), true);
expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('not using synthetic packages', () async {
final Directory l10nDirectory = fileSystem.directory(
fileSystem.path.join('lib', 'l10n'),
);
final File arbFile = l10nDirectory.childFile(
'app_en.arb',
)..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>[
'gen-l10n',
'--no-synthetic-package',
]);
expect(l10nDirectory.existsSync(), true);
expect(l10nDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(l10nDirectory.childFile('app_localizations.dart').existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('throws error when arguments are invalid', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
fileSystem.file('header.txt').writeAsStringSync('a header file');
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
expect(
() => createTestCommandRunner(command).run(<String>[
'gen-l10n',
'--header="some header',
'--header-file="header.txt"',
]),
throwsToolExit(),
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('l10n yaml file takes precedence over command line arguments', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
fileSystem.file('l10n.yaml').createSync();
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
expect(logger.statusText, contains('Because l10n.yaml exists, the options defined there will be used instead.'));
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), true);
expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('nullable-getter help message is expected string', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
fileSystem.file('l10n.yaml').createSync();
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
expect(command.usage, contains(' If this value is set to false, then '));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('dart format is run when --format is passed', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
processManager.addCommand(
const FakeCommand(
command: <String>[
'Artifact.engineDartBinary',
'format',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations.dart',
]
)
);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n', '--format']);
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), true);
expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('dart format is run when format: true is passed into l10n.yaml', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
final File configFile = fileSystem.file('l10n.yaml')..createSync();
configFile.writeAsStringSync('''
format: true
''');
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
processManager.addCommand(
const FakeCommand(
command: <String>[
'Artifact.engineDartBinary',
'format',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations.dart',
]
)
);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), true);
expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
// Regression test for https://github.com/flutter/flutter/issues/119594
testUsingContext('dart format is working when the untranslated messages file is produced', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"untranslated": "Test untranslated message."
}''');
fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_es.arb'))
..createSync(recursive: true)
..writeAsStringSync('''
{
"helloWorld": "Hello, World!"
}''');
final File configFile = fileSystem.file('l10n.yaml')..createSync();
configFile.writeAsStringSync('''
format: true
untranslated-messages-file: lib/l10n/untranslated.json
''');
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
processManager.addCommand(
const FakeCommand(
command: <String>[
'Artifact.engineDartBinary',
'format',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations_es.dart',
'/.dart_tool/flutter_gen/gen_l10n/app_localizations.dart',
]
)
);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), true);
expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations_es.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true);
final File untranslatedMessagesFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'untranslated.json'));
expect(untranslatedMessagesFile.existsSync(), true);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('nullable-getter defaults to true', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), isTrue);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), isTrue);
expect(
outputDirectory.childFile('app_localizations.dart').readAsStringSync(),
contains('static AppLocalizations? of(BuildContext context)'),
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('throw when generate: false and uses synthetic package when run with l10n.yaml', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
fileSystem.file('l10n.yaml').createSync();
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('''
name: test
environment:
sdk: '>=3.0.0-0 <4.0.0'
dependencies:
flutter:
sdk: flutter
flutter:
generate: false
''');
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
expect(
() async => createTestCommandRunner(command).run(<String>['gen-l10n']),
throwsToolExit(message: 'Attempted to generate localizations code without having the flutter: generate flag turned on.')
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('throw when generate: false and uses synthetic package when run via commandline options', () async {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('''
name: test
environment:
sdk: '>=3.0.0-0 <4.0.0'
dependencies:
flutter:
sdk: flutter
flutter:
generate: false
''');
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
expect(
() async => createTestCommandRunner(command).run(<String>['gen-l10n', '--synthetic-package']),
throwsToolExit(message: 'Attempted to generate localizations code without having the flutter: generate flag turned on.')
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
}