Unverified Commit 8ed40ddd authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] refactor FlutterManifest to be context-free (#54555)

parent 62621507
...@@ -128,7 +128,11 @@ class _ManifestAssetBundle implements AssetBundle { ...@@ -128,7 +128,11 @@ class _ManifestAssetBundle implements AssetBundle {
packagesPath ??= globals.fs.path.absolute(PackageMap.globalPackagesPath); packagesPath ??= globals.fs.path.absolute(PackageMap.globalPackagesPath);
FlutterManifest flutterManifest; FlutterManifest flutterManifest;
try { try {
flutterManifest = FlutterManifest.createFromPath(manifestPath); flutterManifest = FlutterManifest.createFromPath(
manifestPath,
logger: globals.logger,
fileSystem: globals.fs,
);
} on Exception catch (e) { } on Exception catch (e) {
globals.printStatus('Error detected in pubspec.yaml:', emphasis: true); globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
globals.printError('$e'); globals.printError('$e');
...@@ -187,7 +191,11 @@ class _ManifestAssetBundle implements AssetBundle { ...@@ -187,7 +191,11 @@ class _ManifestAssetBundle implements AssetBundle {
final Uri packageUri = package.packageUriRoot; final Uri packageUri = package.packageUriRoot;
if (packageUri != null && packageUri.scheme == 'file') { if (packageUri != null && packageUri.scheme == 'file') {
final String packageManifestPath = globals.fs.path.fromUri(packageUri.resolve('../pubspec.yaml')); final String packageManifestPath = globals.fs.path.fromUri(packageUri.resolve('../pubspec.yaml'));
final FlutterManifest packageFlutterManifest = FlutterManifest.createFromPath(packageManifestPath); final FlutterManifest packageFlutterManifest = FlutterManifest.createFromPath(
packageManifestPath,
logger: globals.logger,
fileSystem: globals.fs,
);
if (packageFlutterManifest == null) { if (packageFlutterManifest == null) {
continue; continue;
} }
......
...@@ -7,42 +7,45 @@ import 'package:pub_semver/pub_semver.dart'; ...@@ -7,42 +7,45 @@ import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/user_messages.dart'; import 'base/user_messages.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'cache.dart'; import 'cache.dart';
import 'globals.dart' as globals;
import 'plugins.dart'; import 'plugins.dart';
/// A wrapper around the `flutter` section in the `pubspec.yaml` file. /// A wrapper around the `flutter` section in the `pubspec.yaml` file.
class FlutterManifest { class FlutterManifest {
FlutterManifest._(); FlutterManifest._(this._logger);
/// Returns an empty manifest. /// Returns an empty manifest.
static FlutterManifest empty() { factory FlutterManifest.empty({ @required Logger logger }) {
final FlutterManifest manifest = FlutterManifest._(); final FlutterManifest manifest = FlutterManifest._(logger);
manifest._descriptor = const <String, dynamic>{}; manifest._descriptor = const <String, dynamic>{};
manifest._flutterDescriptor = const <String, dynamic>{}; manifest._flutterDescriptor = const <String, dynamic>{};
return manifest; return manifest;
} }
/// Returns null on invalid manifest. Returns empty manifest on missing file. /// Returns null on invalid manifest. Returns empty manifest on missing file.
static FlutterManifest createFromPath(String path) { static FlutterManifest createFromPath(String path, {
if (path == null || !globals.fs.isFileSync(path)) { @required FileSystem fileSystem,
return _createFromYaml(null); @required Logger logger,
}) {
if (path == null || !fileSystem.isFileSync(path)) {
return _createFromYaml(null, logger);
} }
final String manifest = globals.fs.file(path).readAsStringSync(); final String manifest = fileSystem.file(path).readAsStringSync();
return createFromString(manifest); return FlutterManifest.createFromString(manifest, logger: logger);
} }
/// Returns null on missing or invalid manifest /// Returns null on missing or invalid manifest
@visibleForTesting @visibleForTesting
static FlutterManifest createFromString(String manifest) { static FlutterManifest createFromString(String manifest, { @required Logger logger }) {
return _createFromYaml(loadYaml(manifest) as YamlMap); return _createFromYaml(loadYaml(manifest) as YamlMap, logger);
} }
static FlutterManifest _createFromYaml(YamlMap yamlDocument) { static FlutterManifest _createFromYaml(YamlMap yamlDocument, Logger logger) {
final FlutterManifest pubspec = FlutterManifest._(); final FlutterManifest pubspec = FlutterManifest._(logger);
if (yamlDocument != null && !_validate(yamlDocument)) { if (yamlDocument != null && !_validate(yamlDocument, logger)) {
return null; return null;
} }
...@@ -63,6 +66,8 @@ class FlutterManifest { ...@@ -63,6 +66,8 @@ class FlutterManifest {
return pubspec; return pubspec;
} }
final Logger _logger;
/// A map representation of the entire `pubspec.yaml` file. /// A map representation of the entire `pubspec.yaml` file.
Map<String, dynamic> _descriptor; Map<String, dynamic> _descriptor;
...@@ -91,7 +96,7 @@ class FlutterManifest { ...@@ -91,7 +96,7 @@ class FlutterManifest {
version = Version.parse(verStr); version = Version.parse(verStr);
} on Exception { } on Exception {
if (!_hasShowInvalidVersionMsg) { if (!_hasShowInvalidVersionMsg) {
globals.printStatus(userMessages.invalidVersionSettingHintMessage(verStr), emphasis: true); _logger.printStatus(userMessages.invalidVersionSettingHintMessage(verStr), emphasis: true);
_hasShowInvalidVersionMsg = true; _hasShowInvalidVersionMsg = true;
} }
} }
...@@ -203,14 +208,14 @@ class FlutterManifest { ...@@ -203,14 +208,14 @@ class FlutterManifest {
final List<Uri> results = <Uri>[]; final List<Uri> results = <Uri>[];
for (final Object asset in assets) { for (final Object asset in assets) {
if (asset is! String || asset == null || asset == '') { if (asset is! String || asset == null || asset == '') {
globals.printError('Asset manifest contains a null or empty uri.'); _logger.printError('Asset manifest contains a null or empty uri.');
continue; continue;
} }
final String stringAsset = asset as String; final String stringAsset = asset as String;
try { try {
results.add(Uri(pathSegments: stringAsset.split('/'))); results.add(Uri(pathSegments: stringAsset.split('/')));
} on FormatException { } on FormatException {
globals.printError('Asset manifest contains invalid uri: $asset.'); _logger.printError('Asset manifest contains invalid uri: $asset.');
} }
} }
return results; return results;
...@@ -233,11 +238,11 @@ class FlutterManifest { ...@@ -233,11 +238,11 @@ class FlutterManifest {
final YamlList fontFiles = fontFamily['fonts'] as YamlList; final YamlList fontFiles = fontFamily['fonts'] as YamlList;
final String familyName = fontFamily['family'] as String; final String familyName = fontFamily['family'] as String;
if (familyName == null) { if (familyName == null) {
globals.printError('Warning: Missing family name for font.', emphasis: true); _logger.printError('Warning: Missing family name for font.', emphasis: true);
continue; continue;
} }
if (fontFiles == null) { if (fontFiles == null) {
globals.printError('Warning: No fonts specified for font $familyName', emphasis: true); _logger.printError('Warning: No fonts specified for font $familyName', emphasis: true);
continue; continue;
} }
...@@ -245,7 +250,7 @@ class FlutterManifest { ...@@ -245,7 +250,7 @@ class FlutterManifest {
for (final Map<dynamic, dynamic> fontFile in fontFiles.cast<Map<dynamic, dynamic>>()) { for (final Map<dynamic, dynamic> fontFile in fontFiles.cast<Map<dynamic, dynamic>>()) {
final String asset = fontFile['asset'] as String; final String asset = fontFile['asset'] as String;
if (asset == null) { if (asset == null) {
globals.printError('Warning: Missing asset in fonts for $familyName', emphasis: true); _logger.printError('Warning: Missing asset in fonts for $familyName', emphasis: true);
continue; continue;
} }
...@@ -310,16 +315,16 @@ class FontAsset { ...@@ -310,16 +315,16 @@ class FontAsset {
} }
@visibleForTesting @visibleForTesting
String buildSchemaDir(FileSystem fs) { String buildSchemaDir(FileSystem fileSystem) {
return globals.fs.path.join( return fileSystem.path.join(
globals.fs.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema', fileSystem.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema',
); );
} }
@visibleForTesting @visibleForTesting
String buildSchemaPath(FileSystem fs) { String buildSchemaPath(FileSystem fileSystem) {
return globals.fs.path.join( return fileSystem.path.join(
buildSchemaDir(fs), buildSchemaDir(fileSystem),
'pubspec_yaml.json', 'pubspec_yaml.json',
); );
} }
...@@ -327,7 +332,7 @@ String buildSchemaPath(FileSystem fs) { ...@@ -327,7 +332,7 @@ String buildSchemaPath(FileSystem fs) {
/// This method should be kept in sync with the schema in /// This method should be kept in sync with the schema in
/// `$FLUTTER_ROOT/packages/flutter_tools/schema/pubspec_yaml.json`, /// `$FLUTTER_ROOT/packages/flutter_tools/schema/pubspec_yaml.json`,
/// but avoid introducing dependencies on packages for simple validation. /// but avoid introducing dependencies on packages for simple validation.
bool _validate(YamlMap manifest) { bool _validate(YamlMap manifest, Logger logger) {
final List<String> errors = <String>[]; final List<String> errors = <String>[];
for (final MapEntry<dynamic, dynamic> kvp in manifest.entries) { for (final MapEntry<dynamic, dynamic> kvp in manifest.entries) {
if (kvp.key is! String) { if (kvp.key is! String) {
...@@ -357,8 +362,8 @@ bool _validate(YamlMap manifest) { ...@@ -357,8 +362,8 @@ bool _validate(YamlMap manifest) {
} }
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
globals.printStatus('Error detected in pubspec.yaml:', emphasis: true); logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
globals.printError(errors.join('\n')); logger.printError(errors.join('\n'));
return false; return false;
} }
......
...@@ -43,7 +43,10 @@ Logger get logger => context.get<Logger>(); ...@@ -43,7 +43,10 @@ Logger get logger => context.get<Logger>();
OperatingSystemUtils get os => context.get<OperatingSystemUtils>(); OperatingSystemUtils get os => context.get<OperatingSystemUtils>();
PersistentToolState get persistentToolState => PersistentToolState.instance; PersistentToolState get persistentToolState => PersistentToolState.instance;
Usage get flutterUsage => context.get<Usage>(); Usage get flutterUsage => context.get<Usage>();
FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? FlutterProjectFactory(); FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? FlutterProjectFactory(
logger: logger,
fileSystem: fs,
);
const FileSystem _kLocalFs = LocalFileSystem(); const FileSystem _kLocalFs = LocalFileSystem();
......
...@@ -12,6 +12,7 @@ import 'android/gradle_utils.dart' as gradle; ...@@ -12,6 +12,7 @@ import 'android/gradle_utils.dart' as gradle;
import 'artifacts.dart'; import 'artifacts.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/logger.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'bundle.dart' as bundle; import 'bundle.dart' as bundle;
import 'features.dart'; import 'features.dart';
...@@ -24,7 +25,14 @@ import 'plugins.dart'; ...@@ -24,7 +25,14 @@ import 'plugins.dart';
import 'template.dart'; import 'template.dart';
class FlutterProjectFactory { class FlutterProjectFactory {
FlutterProjectFactory(); FlutterProjectFactory({
@required Logger logger,
@required FileSystem fileSystem,
}) : _logger = logger,
_fileSystem = fileSystem;
final Logger _logger;
final FileSystem _fileSystem;
@visibleForTesting @visibleForTesting
final Map<String, FlutterProject> projects = final Map<String, FlutterProject> projects =
...@@ -34,14 +42,18 @@ class FlutterProjectFactory { ...@@ -34,14 +42,18 @@ class FlutterProjectFactory {
/// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
FlutterProject fromDirectory(Directory directory) { FlutterProject fromDirectory(Directory directory) {
assert(directory != null); assert(directory != null);
return projects.putIfAbsent(directory.path, /* ifAbsent */ () { return projects.putIfAbsent(directory.path, () {
final FlutterManifest manifest = FlutterProject._readManifest( final FlutterManifest manifest = FlutterProject._readManifest(
directory.childFile(bundle.defaultManifestPath).path, directory.childFile(bundle.defaultManifestPath).path,
logger: _logger,
fileSystem: _fileSystem,
); );
final FlutterManifest exampleManifest = FlutterProject._readManifest( final FlutterManifest exampleManifest = FlutterProject._readManifest(
FlutterProject._exampleDirectory(directory) FlutterProject._exampleDirectory(directory)
.childFile(bundle.defaultManifestPath) .childFile(bundle.defaultManifestPath)
.path, .path,
logger: _logger,
fileSystem: _fileSystem,
); );
return FlutterProject(directory, manifest, exampleManifest); return FlutterProject(directory, manifest, exampleManifest);
}); });
...@@ -167,7 +179,7 @@ class FlutterProject { ...@@ -167,7 +179,7 @@ class FlutterProject {
FlutterProject get example => FlutterProject( FlutterProject get example => FlutterProject(
_exampleDirectory(directory), _exampleDirectory(directory),
_exampleManifest, _exampleManifest,
FlutterManifest.empty(), FlutterManifest.empty(logger: globals.logger),
); );
/// True if this project is a Flutter module project. /// True if this project is a Flutter module project.
...@@ -187,13 +199,20 @@ class FlutterProject { ...@@ -187,13 +199,20 @@ class FlutterProject {
/// ///
/// Completes with an empty [FlutterManifest], if the file does not exist. /// Completes with an empty [FlutterManifest], if the file does not exist.
/// Completes with a ToolExit on validation error. /// Completes with a ToolExit on validation error.
static FlutterManifest _readManifest(String path) { static FlutterManifest _readManifest(String path, {
@required Logger logger,
@required FileSystem fileSystem,
}) {
FlutterManifest manifest; FlutterManifest manifest;
try { try {
manifest = FlutterManifest.createFromPath(path); manifest = FlutterManifest.createFromPath(
path,
logger: logger,
fileSystem: fileSystem,
);
} on YamlException catch (e) { } on YamlException catch (e) {
globals.printStatus('Error detected in pubspec.yaml:', emphasis: true); logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
globals.printError('$e'); logger.printError('$e');
} }
if (manifest == null) { if (manifest == null) {
throwToolExit('Please correct the pubspec.yaml file at $path'); throwToolExit('Please correct the pubspec.yaml file at $path');
......
...@@ -2,27 +2,28 @@ ...@@ -2,27 +2,28 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.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/cache.dart';
import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_manifest.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/pubspec_schema.dart';
void main() { void main() {
setUpAll(() { setUpAll(() {
Cache.flutterRoot = getFlutterRoot(); Cache.flutterRoot = getFlutterRoot();
}); });
group('FlutterManifest', () { testWithoutContext('FlutterManifest is empty when the pubspec.yaml file is empty', () async {
testUsingContext('is empty when the pubspec.yaml file is empty', () async { final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(''); final FlutterManifest flutterManifest = FlutterManifest.createFromString(
'',
logger: logger,
);
expect(flutterManifest.isEmpty, true); expect(flutterManifest.isEmpty, true);
expect(flutterManifest.appName, ''); expect(flutterManifest.appName, '');
expect(flutterManifest.usesMaterialDesign, false); expect(flutterManifest.usesMaterialDesign, false);
...@@ -31,14 +32,19 @@ void main() { ...@@ -31,14 +32,19 @@ void main() {
expect(flutterManifest.assets, isEmpty); expect(flutterManifest.assets, isEmpty);
}); });
test('has no fonts or assets when the "flutter" section is empty', () async { testWithoutContext('FlutterManifest has no fonts or assets when the "flutter" section is empty', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, isNotNull); expect(flutterManifest, isNotNull);
expect(flutterManifest.isEmpty, false); expect(flutterManifest.isEmpty, false);
expect(flutterManifest.appName, 'test'); expect(flutterManifest.appName, 'test');
...@@ -48,7 +54,7 @@ dependencies: ...@@ -48,7 +54,7 @@ dependencies:
expect(flutterManifest.assets, isEmpty); expect(flutterManifest.assets, isEmpty);
}); });
test('knows if material design is used', () async { testWithoutContext('FlutterManifest knows if material design is used', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -57,11 +63,16 @@ dependencies: ...@@ -57,11 +63,16 @@ dependencies:
flutter: flutter:
uses-material-design: true uses-material-design: true
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.usesMaterialDesign, true); expect(flutterManifest.usesMaterialDesign, true);
}); });
test('has two assets', () async { testWithoutContext('FlutterManifest has two assets', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -73,13 +84,19 @@ flutter: ...@@ -73,13 +84,19 @@ flutter:
- a/foo - a/foo
- a/bar - a/bar
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
expect(flutterManifest.assets.length, 2); final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expect(flutterManifest.assets[0], Uri.parse('a/foo')); manifest,
expect(flutterManifest.assets[1], Uri.parse('a/bar')); logger: logger,
);
expect(flutterManifest.assets, <Uri>[
Uri.parse('a/foo'),
Uri.parse('a/bar'),
]);
}); });
test('has one font family with one asset', () async { testWithoutContext('FlutterManifest has one font family with one asset', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -92,24 +109,29 @@ flutter: ...@@ -92,24 +109,29 @@ flutter:
fonts: fonts:
- asset: a/bar - asset: a/bar
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor); manifest,
final List<Font> fonts = flutterManifest.fonts; logger: logger,
expect(fonts.length, 1); );
final Font font = fonts[0];
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}]}; // ignore: always_specify_types expect(flutterManifest.fonts, hasLength(1));
expect(font.descriptor, fooFontDescriptor); expect(flutterManifest.fonts.single, matchesFont(
expect(font.familyName, 'foo'); familyName: 'foo',
final List<FontAsset> assets = font.fontAssets; descriptor: <String, Object>{
expect(assets.length, 1); 'family': 'foo',
final FontAsset fontAsset = assets[0]; 'fonts': <Object>[
expect(fontAsset.assetUri.path, 'a/bar'); <String, Object>{'asset': 'a/bar'},
expect(fontAsset.weight, isNull); ],
expect(fontAsset.style, isNull); },
fontAssets: <Matcher>[
matchesFontAsset(assetUri: Uri.parse('a/bar')),
],
));
}); });
test('has one font family with a simple asset and one with weight', () async { testWithoutContext('FlutterManifest has one font family with a simple asset '
'and one with weight', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -124,28 +146,31 @@ flutter: ...@@ -124,28 +146,31 @@ flutter:
- asset: a/bar - asset: a/bar
weight: 400 weight: 400
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor); manifest,
final List<Font> fonts = flutterManifest.fonts; logger: logger,
expect(fonts.length, 1); );
final Font font = fonts[0];
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'asset': 'a/bar'}]}; // ignore: always_specify_types expect(flutterManifest.fonts, hasLength(1));
expect(font.descriptor, fooFontDescriptor); expect(flutterManifest.fonts.single, matchesFont(
expect(font.familyName, 'foo'); familyName: 'foo',
final List<FontAsset> assets = font.fontAssets; descriptor: <String, Object>{
expect(assets.length, 2); 'family': 'foo',
final FontAsset fontAsset0 = assets[0]; 'fonts': <Object>[
expect(fontAsset0.assetUri.path, 'a/bar'); <String, Object>{'asset': 'a/bar'},
expect(fontAsset0.weight, isNull); <String, Object>{'weight': 400, 'asset': 'a/bar'},
expect(fontAsset0.style, isNull); ],
final FontAsset fontAsset1 = assets[1]; },
expect(fontAsset1.assetUri.path, 'a/bar'); fontAssets: <Matcher>[
expect(fontAsset1.weight, 400); matchesFontAsset(assetUri: Uri.parse('a/bar')),
expect(fontAsset1.style, isNull); matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400),
}); ])
);
test('has one font family with a simple asset and one with weight and style', () async { });
testWithoutContext('FlutterManifest has one font family with a simple asset '
'and one with weight and style', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -161,29 +186,31 @@ flutter: ...@@ -161,29 +186,31 @@ flutter:
weight: 400 weight: 400
style: italic style: italic
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor); expect(flutterManifest.fonts, hasLength(1));
final List<Font> fonts = flutterManifest.fonts; expect(flutterManifest.fonts.single, matchesFont(
expect(fonts.length, 1); familyName: 'foo',
final Font font = fonts[0]; descriptor: <String, Object>{
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types 'family': 'foo',
expect(font.descriptor, fooFontDescriptor); 'fonts': <Object>[
expect(font.familyName, 'foo'); <String, Object>{'asset': 'a/bar'},
final List<FontAsset> assets = font.fontAssets; <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
expect(assets.length, 2); ],
final FontAsset fontAsset0 = assets[0]; },
expect(fontAsset0.assetUri.path, 'a/bar'); fontAssets: <Matcher>[
expect(fontAsset0.weight, isNull); matchesFontAsset(assetUri: Uri.parse('a/bar')),
expect(fontAsset0.style, isNull); matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
final FontAsset fontAsset1 = assets[1]; ],
expect(fontAsset1.assetUri.path, 'a/bar'); ));
expect(fontAsset1.weight, 400);
expect(fontAsset1.style, 'italic');
}); });
test('has two font families, each with one simple asset and one with weight and style', () async { testWithoutContext('FlutterManifest has two font families, each with one '
'simple asset and one with weight and style', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -205,47 +232,94 @@ flutter: ...@@ -205,47 +232,94 @@ flutter:
asset: a/baz asset: a/baz
style: italic style: italic
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final dynamic expectedFontsDescriptor = <dynamic>[ final FlutterManifest flutterManifest = FlutterManifest.createFromString(
{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}, // ignore: always_specify_types manifest,
{'fonts': [{'asset': 'a/baz'}, {'style': 'italic', 'weight': 400, 'asset': 'a/baz'}], 'family': 'bar'}, // ignore: always_specify_types logger: logger,
]; );
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts; expect(flutterManifest.fonts, hasLength(2));
expect(fonts.length, 2); expect(flutterManifest.fonts, containsAll(<Matcher>[
matchesFont(
final Font fooFont = fonts[0]; familyName: 'foo',
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types descriptor: <String, Object>{
expect(fooFont.descriptor, fooFontDescriptor); 'family': 'foo',
expect(fooFont.familyName, 'foo'); 'fonts': <Object>[
final List<FontAsset> fooAassets = fooFont.fontAssets; <String, Object>{'asset': 'a/bar'},
expect(fooAassets.length, 2); <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
final FontAsset fooFontAsset0 = fooAassets[0]; ],
expect(fooFontAsset0.assetUri.path, 'a/bar'); },
expect(fooFontAsset0.weight, isNull); fontAssets: <Matcher>[
expect(fooFontAsset0.style, isNull); matchesFontAsset(assetUri: Uri.parse('a/bar')),
final FontAsset fooFontAsset1 = fooAassets[1]; matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
expect(fooFontAsset1.assetUri.path, 'a/bar'); ],
expect(fooFontAsset1.weight, 400); ),
expect(fooFontAsset1.style, 'italic'); matchesFont(
familyName: 'bar',
final Font barFont = fonts[1]; descriptor: <String, Object>{
const String fontDescriptor = '{family: bar, fonts: [{asset: a/baz}, {weight: 400, style: italic, asset: a/baz}]}'; // ignore: always_specify_types 'family': 'bar',
expect(barFont.descriptor.toString(), fontDescriptor); 'fonts': <Object>[
expect(barFont.familyName, 'bar'); <String, Object>{'asset': 'a/baz'},
final List<FontAsset> barAssets = barFont.fontAssets; <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/baz'},
expect(barAssets.length, 2); ],
final FontAsset barFontAsset0 = barAssets[0]; },
expect(barFontAsset0.assetUri.path, 'a/baz'); fontAssets: <Matcher>[
expect(barFontAsset0.weight, isNull); matchesFontAsset(assetUri: Uri.parse('a/baz')),
expect(barFontAsset0.style, isNull); matchesFontAsset(assetUri: Uri.parse('a/baz'), weight: 400, style: 'italic'),
final FontAsset barFontAsset1 = barAssets[1]; ],
expect(barFontAsset1.assetUri.path, 'a/baz'); ),
expect(barFontAsset1.weight, 400); ]));
expect(barFontAsset1.style, 'italic'); });
});
testWithoutContext('FlutterManifest.fontsDescriptor combines descriptors from '
testUsingContext('has only one of two font families when one declaration is missing the "family" option', () async { 'individual fonts', () {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
fonts:
- family: foo
fonts:
- asset: a/bar
- asset: a/bar
weight: 400
style: italic
- family: bar
fonts:
- asset: a/baz
- weight: 400
asset: a/baz
style: italic
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.fontsDescriptor, <Object>[
<String, Object>{
'family': 'foo',
'fonts': <Object>[
<String, Object>{'asset': 'a/bar'},
<String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
],
},
<String, Object>{
'family': 'bar',
'fonts': <Object>[
<String, Object>{'asset': 'a/baz'},
<String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/baz'},
],
},
]);
});
testWithoutContext('FlutterManifest has only one of two font families when '
'one declaration is missing the "family" option', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -266,29 +340,33 @@ flutter: ...@@ -266,29 +340,33 @@ flutter:
weight: 400 weight: 400
style: italic style: italic
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types expect(flutterManifest.fonts, hasLength(1));
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor); expect(flutterManifest.fonts, containsAll(<Matcher>[
final List<Font> fonts = flutterManifest.fonts; matchesFont(
expect(fonts.length, 1); familyName: 'foo',
final Font fooFont = fonts[0]; descriptor: <String, Object>{
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types 'family': 'foo',
expect(fooFont.descriptor, fooFontDescriptor); 'fonts': <Object>[
expect(fooFont.familyName, 'foo'); <String, Object>{'asset': 'a/bar'},
final List<FontAsset> fooAassets = fooFont.fontAssets; <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
expect(fooAassets.length, 2); ],
final FontAsset fooFontAsset0 = fooAassets[0]; },
expect(fooFontAsset0.assetUri.path, 'a/bar'); fontAssets: <Matcher>[
expect(fooFontAsset0.weight, isNull); matchesFontAsset(assetUri: Uri.parse('a/bar')),
expect(fooFontAsset0.style, isNull); matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
final FontAsset fooFontAsset1 = fooAassets[1]; ],
expect(fooFontAsset1.assetUri.path, 'a/bar'); ),
expect(fooFontAsset1.weight, 400); ]));
expect(fooFontAsset1.style, 'italic');
}); });
testUsingContext('has only one of two font families when one declaration is missing the "fonts" option', () async { testWithoutContext('FlutterManifest has only one of two font families when '
'one declaration is missing the "fonts" option', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -305,28 +383,33 @@ flutter: ...@@ -305,28 +383,33 @@ flutter:
style: italic style: italic
- family: bar - family: bar
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor); manifest,
final List<Font> fonts = flutterManifest.fonts; logger: logger,
expect(fonts.length, 1); );
final Font fooFont = fonts[0];
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types expect(flutterManifest.fonts, hasLength(1));
expect(fooFont.descriptor, fooFontDescriptor); expect(flutterManifest.fonts, containsAll(<Matcher>[
expect(fooFont.familyName, 'foo'); matchesFont(
final List<FontAsset> fooAassets = fooFont.fontAssets; familyName: 'foo',
expect(fooAassets.length, 2); descriptor: <String, Object>{
final FontAsset fooFontAsset0 = fooAassets[0]; 'family': 'foo',
expect(fooFontAsset0.assetUri.path, 'a/bar'); 'fonts': <Object>[
expect(fooFontAsset0.weight, isNull); <String, Object>{'asset': 'a/bar'},
expect(fooFontAsset0.style, isNull); <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
final FontAsset fooFontAsset1 = fooAassets[1]; ],
expect(fooFontAsset1.assetUri.path, 'a/bar'); },
expect(fooFontAsset1.weight, 400); fontAssets: <Matcher>[
expect(fooFontAsset1.style, 'italic'); matchesFontAsset(assetUri: Uri.parse('a/bar')),
}); matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
],
testUsingContext('has no font family when declaration is missing the "asset" option', () async { ),
]));
});
testWithoutContext('FlutterManifest has no font family when declaration is '
'missing the "asset" option', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -339,14 +422,17 @@ flutter: ...@@ -339,14 +422,17 @@ flutter:
fonts: fonts:
- weight: 400 - weight: 400
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.fontsDescriptor, <dynamic>[]); expect(flutterManifest.fontsDescriptor, isEmpty);
final List<Font> fonts = flutterManifest.fonts; expect(flutterManifest.fonts, isEmpty);
expect(fonts.length, 0);
}); });
test('allows a blank flutter section', () async { testWithoutContext('FlutterManifest allows a blank flutter section', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -354,7 +440,12 @@ dependencies: ...@@ -354,7 +440,12 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.isEmpty, false); expect(flutterManifest.isEmpty, false);
expect(flutterManifest.isModule, false); expect(flutterManifest.isModule, false);
expect(flutterManifest.isPlugin, false); expect(flutterManifest.isPlugin, false);
...@@ -362,7 +453,7 @@ flutter: ...@@ -362,7 +453,7 @@ flutter:
expect(flutterManifest.usesAndroidX, false); expect(flutterManifest.usesAndroidX, false);
}); });
test('allows a module declaration', () async { testWithoutContext('FlutterManifest allows a module declaration', () {
const String manifest = ''' const String manifest = '''
name: test name: test
flutter: flutter:
...@@ -370,25 +461,36 @@ flutter: ...@@ -370,25 +461,36 @@ flutter:
androidPackage: com.example androidPackage: com.example
androidX: true androidX: true
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.isModule, true); expect(flutterManifest.isModule, true);
expect(flutterManifest.androidPackage, 'com.example'); expect(flutterManifest.androidPackage, 'com.example');
expect(flutterManifest.usesAndroidX, true); expect(flutterManifest.usesAndroidX, true);
}); });
test('allows a legacy plugin declaration', () async { testWithoutContext('FlutterManifest allows a legacy plugin declaration', () {
const String manifest = ''' const String manifest = '''
name: test name: test
flutter: flutter:
plugin: plugin:
androidPackage: com.example androidPackage: com.example
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.isPlugin, true); expect(flutterManifest.isPlugin, true);
expect(flutterManifest.androidPackage, 'com.example'); expect(flutterManifest.androidPackage, 'com.example');
}); });
test('allows a multi-plat plugin declaration with android only', () async { testWithoutContext('FlutterManifest allows a multi-plat plugin declaration '
'with android only', () {
const String manifest = ''' const String manifest = '''
name: test name: test
flutter: flutter:
...@@ -398,12 +500,18 @@ flutter: ...@@ -398,12 +500,18 @@ flutter:
package: com.example package: com.example
pluginClass: TestPlugin pluginClass: TestPlugin
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.isPlugin, true); expect(flutterManifest.isPlugin, true);
expect(flutterManifest.androidPackage, 'com.example'); expect(flutterManifest.androidPackage, 'com.example');
}); });
test('allows a multi-plat plugin declaration with ios only', () async { testWithoutContext('FlutterManifest allows a multi-plat plugin declaration '
'with ios only', () {
const String manifest = ''' const String manifest = '''
name: test name: test
flutter: flutter:
...@@ -412,36 +520,34 @@ flutter: ...@@ -412,36 +520,34 @@ flutter:
ios: ios:
pluginClass: HelloPlugin pluginClass: HelloPlugin
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.isPlugin, true); expect(flutterManifest.isPlugin, true);
expect(flutterManifest.androidPackage, isNull); expect(flutterManifest.androidPackage, isNull);
}); });
testUsingContext('handles an invalid plugin declaration', () async { testUsingContext('FlutterManifest handles an invalid plugin declaration', () {
const String manifest = ''' const String manifest = '''
name: test name: test
flutter: flutter:
plugin: plugin:
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null); expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "plugin" to be an object, but got null')); expect(logger.errorText,
contains('Expected "plugin" to be an object, but got null'));
}); });
testWithoutContext('FlutterManifest parses major.minor.patch+build version clause 1', () {
Future<void> checkManifestVersion({
String manifest,
String expectedAppVersion,
String expectedBuildName,
String expectedBuildNumber,
}) async {
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.appVersion, expectedAppVersion);
expect(flutterManifest.buildName, expectedBuildName);
expect(flutterManifest.buildNumber, expectedBuildNumber);
}
test('parses major.minor.patch+build version clause 1', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
version: 1.0.0+2 version: 1.0.0+2
...@@ -450,15 +556,20 @@ dependencies: ...@@ -450,15 +556,20 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
await checkManifestVersion( final BufferLogger logger = BufferLogger.test();
manifest: manifest, final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expectedAppVersion: '1.0.0+2', manifest,
expectedBuildName: '1.0.0', logger: logger,
expectedBuildNumber: '2', );
expect(flutterManifest, matchesManifest(
appVersion: '1.0.0+2',
buildName: '1.0.0',
buildNumber: '2'),
); );
}); });
test('parses major.minor.patch with no build version', () async { testWithoutContext('FlutterManifest parses major.minor.patch with no build version', () {
const String manifest = ''' const String manifest = '''
name: test name: test
version: 0.0.1 version: 0.0.1
...@@ -467,15 +578,20 @@ dependencies: ...@@ -467,15 +578,20 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
await checkManifestVersion( final BufferLogger logger = BufferLogger.test();
manifest: manifest, final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expectedAppVersion: '0.0.1', manifest,
expectedBuildName: '0.0.1', logger: logger,
expectedBuildNumber: null, );
expect(flutterManifest, matchesManifest(
appVersion: '0.0.1',
buildName: '0.0.1',
buildNumber: null),
); );
}); });
test('parses major.minor.patch+build version clause 2', () async { testWithoutContext('FlutterManifest parses major.minor.patch+build version clause 2', () {
const String manifest = ''' const String manifest = '''
name: test name: test
version: 1.0.0-beta+exp.sha.5114f85 version: 1.0.0-beta+exp.sha.5114f85
...@@ -484,15 +600,20 @@ dependencies: ...@@ -484,15 +600,20 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
await checkManifestVersion( final BufferLogger logger = BufferLogger.test();
manifest: manifest, final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expectedAppVersion: '1.0.0-beta+exp.sha.5114f85', manifest,
expectedBuildName: '1.0.0-beta', logger: logger,
expectedBuildNumber: 'exp.sha.5114f85', );
expect(flutterManifest, matchesManifest(
appVersion: '1.0.0-beta+exp.sha.5114f85',
buildName: '1.0.0-beta',
buildNumber: 'exp.sha.5114f85'),
); );
}); });
test('parses major.minor+build version clause', () async { testWithoutContext('FlutterManifest parses major.minor+build version clause', () {
const String manifest = ''' const String manifest = '''
name: test name: test
version: 1.0+2 version: 1.0+2
...@@ -501,15 +622,20 @@ dependencies: ...@@ -501,15 +622,20 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
await checkManifestVersion( final BufferLogger logger = BufferLogger.test();
manifest: manifest, final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expectedAppVersion: '1.0+2', manifest,
expectedBuildName: '1.0', logger: logger,
expectedBuildNumber: '2', );
expect(flutterManifest, matchesManifest(
appVersion: '1.0+2',
buildName: '1.0',
buildNumber: '2'),
); );
}); });
test('parses empty version clause', () async { testWithoutContext('FlutterManifest parses empty version clause', () {
const String manifest = ''' const String manifest = '''
name: test name: test
version: version:
...@@ -518,15 +644,20 @@ dependencies: ...@@ -518,15 +644,20 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
await checkManifestVersion( final BufferLogger logger = BufferLogger.test();
manifest: manifest, final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expectedAppVersion: null, manifest,
expectedBuildName: null, logger: logger,
expectedBuildNumber: null, );
expect(flutterManifest, matchesManifest(
appVersion: null,
buildName: null,
buildNumber: null),
); );
}); });
test('parses no version clause', () async { testWithoutContext('FlutterManifest parses no version clause', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -534,16 +665,21 @@ dependencies: ...@@ -534,16 +665,21 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
await checkManifestVersion( final BufferLogger logger = BufferLogger.test();
manifest: manifest, final FlutterManifest flutterManifest = FlutterManifest.createFromString(
expectedAppVersion: null, manifest,
expectedBuildName: null, logger: logger,
expectedBuildNumber: null, );
expect(flutterManifest, matchesManifest(
appVersion: null,
buildName: null,
buildNumber: null),
); );
}); });
// Regression test for https://github.com/flutter/flutter/issues/31764 // Regression test for https://github.com/flutter/flutter/issues/31764
testUsingContext('Returns proper error when font detail is malformed', () async { testWithoutContext('FlutterManifest returns proper error when font detail is malformed', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -555,13 +691,19 @@ flutter: ...@@ -555,13 +691,19 @@ flutter:
fonts: fonts:
-asset: a/bar -asset: a/bar
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null); expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "fonts" to either be null or a list.')); expect(logger.errorText,
contains('Expected "fonts" to either be null or a list.'));
}); });
testUsingContext('Returns proper error when font detail is not a list of maps', () async { testWithoutContext('FlutterManifest returns proper error when font detail is '
'not a list of maps', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -573,13 +715,19 @@ flutter: ...@@ -573,13 +715,19 @@ flutter:
fonts: fonts:
- asset - asset
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null); expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "fonts" to be a list of maps.')); expect(logger.errorText,
contains('Expected "fonts" to be a list of maps.'));
}); });
testUsingContext('Returns proper error when font is a map instead of a list', () async { testWithoutContext('FlutterManifest returns proper error when font is a map '
'instead of a list', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -591,13 +739,18 @@ flutter: ...@@ -591,13 +739,18 @@ flutter:
fonts: fonts:
-asset: a/bar -asset: a/bar
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null); expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "fonts" to be a list')); expect(logger.errorText, contains('Expected "fonts" to be a list'));
}); });
testUsingContext('Returns proper error when second font family is invalid', () async { testWithoutContext('FlutterManifest returns proper error when second font '
'family is invalid', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -611,12 +764,17 @@ flutter: ...@@ -611,12 +764,17 @@ flutter:
- asset: a/bar - asset: a/bar
- string - string
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null); expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected a map.')); expect(logger.errorText, contains('Expected a map.'));
}); });
testUsingContext('Does not crash on empty entry', () async { testWithoutContext('FlutterManifest does not crash on empty entry', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -628,14 +786,18 @@ flutter: ...@@ -628,14 +786,18 @@ flutter:
- lib/gallery/example_code.dart - lib/gallery/example_code.dart
- -
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
final List<Uri> assets = flutterManifest.assets; final List<Uri> assets = flutterManifest.assets;
expect(testLogger.errorText, contains('Asset manifest contains a null or empty uri.')); expect(logger.errorText, contains('Asset manifest contains a null or empty uri.'));
expect(assets.length, 1); expect(assets, hasLength(1));
}); });
testUsingContext('Special characters in asset URIs', () async { testWithoutContext('FlutterManifest handles special characters in asset URIs', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -648,16 +810,23 @@ flutter: ...@@ -648,16 +810,23 @@ flutter:
- lib/gallery/abc?xyz - lib/gallery/abc?xyz
- lib/gallery/aaa bbb - lib/gallery/aaa bbb
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
final List<Uri> assets = flutterManifest.assets; final List<Uri> assets = flutterManifest.assets;
expect(assets.length, 3); expect(assets, hasLength(3));
expect(assets[0].path, 'lib/gallery/abc%23xyz'); expect(assets, <Uri>[
expect(assets[1].path, 'lib/gallery/abc%3Fxyz'); Uri.parse('lib/gallery/abc%23xyz'),
expect(assets[2].path, 'lib/gallery/aaa%20bbb'); Uri.parse('lib/gallery/abc%3Fxyz'),
Uri.parse('lib/gallery/aaa%20bbb'),
]);
}); });
testUsingContext('Returns proper error when flutter is a list instead of a map', () async { testWithoutContext('FlutterManifest returns proper error when flutter is a '
'list instead of a map', () {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -666,15 +835,22 @@ dependencies: ...@@ -666,15 +835,22 @@ dependencies:
flutter: flutter:
- uses-material-design: true - uses-material-design: true
'''; ''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest, null); expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "flutter" section to be an object or null, but got [{uses-material-design: true}].')); expect(logger.errorText,
}); contains(
'Expected "flutter" section to be an object or null, but got '
'[{uses-material-design: true}].',
),
);
}); });
group('FlutterManifest with MemoryFileSystem', () { testWithoutContext('FlutterManifest can parse manifest on posix filesystem', () {
Future<void> assertSchemaIsReadable() async {
const String manifest = ''' const String manifest = '''
name: test name: test
dependencies: dependencies:
...@@ -682,48 +858,69 @@ dependencies: ...@@ -682,48 +858,69 @@ dependencies:
sdk: flutter sdk: flutter
flutter: flutter:
'''; ''';
final FileSystem fileSystem = MemoryFileSystem.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest); fileSystem.file('pubspec.yaml').writeAsStringSync(manifest);
expect(flutterManifest.isEmpty, false); final BufferLogger logger = BufferLogger.test();
} final FlutterManifest flutterManifest = FlutterManifest.createFromPath(
'pubspec.yaml',
void testUsingContextAndFs( fileSystem: fileSystem,
String description, logger: logger,
FileSystem filesystem,
dynamic testMethod(),
) {
testUsingContext(
description,
() async {
writeEmptySchemaFile(filesystem);
testMethod();
},
overrides: <Type, Generator>{
FileSystem: () => filesystem,
ProcessManager: () => FakeProcessManager.any(),
},
); );
}
testUsingContext('Validate manifest on original fs', () { expect(flutterManifest.isEmpty, false);
assertSchemaIsReadable();
}); });
testUsingContextAndFs( testWithoutContext('FlutterManifest can parse manifest on windows filesystem', () {
'Validate manifest on Posix FS', const String manifest = '''
MemoryFileSystem(style: FileSystemStyle.posix), name: test
() { dependencies:
assertSchemaIsReadable(); flutter:
}, sdk: flutter
); flutter:
''';
testUsingContextAndFs( final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
'Validate manifest on Windows FS', fileSystem.file('pubspec.yaml').writeAsStringSync(manifest);
MemoryFileSystem(style: FileSystemStyle.windows), final BufferLogger logger = BufferLogger.test();
() { final FlutterManifest flutterManifest = FlutterManifest.createFromPath(
assertSchemaIsReadable(); 'pubspec.yaml',
}, fileSystem: fileSystem,
logger: logger,
); );
expect(flutterManifest.isEmpty, false);
}); });
} }
Matcher matchesManifest({
String appVersion,
String buildName,
String buildNumber,
}) {
return isA<FlutterManifest>()
.having((FlutterManifest manifest) => manifest.appVersion, 'appVersion', appVersion)
.having((FlutterManifest manifest) => manifest.buildName, 'buildName', buildName)
.having((FlutterManifest manifest) => manifest.buildNumber, 'buildNumber', buildNumber);
}
Matcher matchesFontAsset({
Uri assetUri,
int weight,
String style,
}) {
return isA<FontAsset>()
.having((FontAsset fontAsset) => fontAsset.assetUri, 'assetUri', assetUri)
.having((FontAsset fontAsset) => fontAsset.weight, 'weight', weight)
.having((FontAsset fontAsset) => fontAsset.style, 'style', style);
}
Matcher matchesFont({
Map<String, Object> descriptor,
String familyName,
List<Matcher> fontAssets,
}) {
return isA<Font>()
.having((Font font) => font.descriptor, 'descriptor', descriptor)
.having((Font font) => font.familyName, 'familyName', familyName)
.having((Font font) => font.fontAssets, 'fontAssets', containsAll(fontAssets));
}
...@@ -160,7 +160,10 @@ void main() { ...@@ -160,7 +160,10 @@ void main() {
fileSystem: fs, fileSystem: fs,
logger: logger, logger: logger,
client: SuccessShortenURLFakeHttpClient(), client: SuccessShortenURLFakeHttpClient(),
flutterProjectFactory: FlutterProjectFactory(), flutterProjectFactory: FlutterProjectFactory(
fileSystem: fs,
logger: logger,
),
); );
expect( expect(
await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText), await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText),
...@@ -176,7 +179,10 @@ void main() { ...@@ -176,7 +179,10 @@ void main() {
fileSystem: fs, fileSystem: fs,
logger: logger, logger: logger,
client: FakeHttpClient(), client: FakeHttpClient(),
flutterProjectFactory: FlutterProjectFactory(), flutterProjectFactory: FlutterProjectFactory(
fileSystem: fs,
logger: logger,
),
); );
expect( expect(
await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText), await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText),
...@@ -199,7 +205,10 @@ void main() { ...@@ -199,7 +205,10 @@ void main() {
fileSystem: fs, fileSystem: fs,
logger: logger, logger: logger,
client: FakeHttpClient(), client: FakeHttpClient(),
flutterProjectFactory: FlutterProjectFactory(), flutterProjectFactory: FlutterProjectFactory(
fileSystem: fs,
logger: logger,
),
); );
final Directory projectDirectory = fs.currentDirectory; final Directory projectDirectory = fs.currentDirectory;
......
...@@ -8,6 +8,7 @@ import 'package:file/file.dart'; ...@@ -8,6 +8,7 @@ import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.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/cache.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_manifest.dart';
...@@ -23,6 +24,10 @@ import '../src/context.dart'; ...@@ -23,6 +24,10 @@ import '../src/context.dart';
import '../src/testbed.dart'; import '../src/testbed.dart';
void main() { void main() {
// TODO(jonahwilliams): remove once FlutterProject is fully refactored.
// this is safe since no tests have expectations on the test logger.
final BufferLogger logger = BufferLogger.test();
group('Project', () { group('Project', () {
group('construction', () { group('construction', () {
testInMemory('fails on null directory', () async { testInMemory('fails on null directory', () async {
...@@ -154,8 +159,8 @@ void main() { ...@@ -154,8 +159,8 @@ void main() {
testInMemory('does nothing, if project is not created', () async { testInMemory('does nothing, if project is not created', () async {
final FlutterProject project = FlutterProject( final FlutterProject project = FlutterProject(
globals.fs.directory('not_created'), globals.fs.directory('not_created'),
FlutterManifest.empty(), FlutterManifest.empty(logger: logger),
FlutterManifest.empty(), FlutterManifest.empty(logger: logger),
); );
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectNotExists(project.directory); expectNotExists(project.directory);
...@@ -197,7 +202,10 @@ void main() { ...@@ -197,7 +202,10 @@ void main() {
FileSystem: () => MemoryFileSystem(), FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(), FlutterProjectFactory: () => FlutterProjectFactory(
logger: logger,
fileSystem: globals.fs,
),
}); });
testUsingContext('generates Xcode configuration for macOS', () async { testUsingContext('generates Xcode configuration for macOS', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
...@@ -208,7 +216,10 @@ void main() { ...@@ -208,7 +216,10 @@ void main() {
FileSystem: () => MemoryFileSystem(), FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(), FlutterProjectFactory: () => FlutterProjectFactory(
logger: logger,
fileSystem: globals.fs,
),
}); });
testUsingContext('injects plugins for Linux', () async { testUsingContext('injects plugins for Linux', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
...@@ -220,7 +231,10 @@ void main() { ...@@ -220,7 +231,10 @@ void main() {
FileSystem: () => MemoryFileSystem(), FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(), FlutterProjectFactory: () => FlutterProjectFactory(
logger: logger,
fileSystem: globals.fs,
),
}); });
testUsingContext('injects plugins for Windows', () async { testUsingContext('injects plugins for Windows', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
...@@ -246,7 +260,10 @@ EndGlobal'''); ...@@ -246,7 +260,10 @@ EndGlobal''');
FileSystem: () => MemoryFileSystem(), FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(), FlutterProjectFactory: () => FlutterProjectFactory(
logger: logger,
fileSystem: globals.fs,
),
}); });
testInMemory('creates Android library in module', () async { testInMemory('creates Android library in module', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await aModuleProject();
...@@ -312,7 +329,10 @@ EndGlobal'''); ...@@ -312,7 +329,10 @@ EndGlobal''');
setUp(() { setUp(() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
flutterProjectFactory = FlutterProjectFactory(); flutterProjectFactory = FlutterProjectFactory(
logger: logger,
fileSystem: fs,
);
}); });
testInMemory('default host app language', () async { testInMemory('default host app language', () async {
...@@ -348,7 +368,10 @@ apply plugin: 'kotlin-android' ...@@ -348,7 +368,10 @@ apply plugin: 'kotlin-android'
fs = MemoryFileSystem(); fs = MemoryFileSystem();
mockPlistUtils = MockPlistUtils(); mockPlistUtils = MockPlistUtils();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
flutterProjectFactory = FlutterProjectFactory(); flutterProjectFactory = FlutterProjectFactory(
fileSystem: fs,
logger: logger,
);
}); });
void testWithMocks(String description, Future<void> testMethod()) { void testWithMocks(String description, Future<void> testMethod()) {
...@@ -521,8 +544,12 @@ apply plugin: 'kotlin-android' ...@@ -521,8 +544,12 @@ apply plugin: 'kotlin-android'
FlutterProjectFactory flutterProjectFactory; FlutterProjectFactory flutterProjectFactory;
setUp(() { setUp(() {
testbed = Testbed(); testbed = Testbed(setup: () {
flutterProjectFactory = FlutterProjectFactory(); flutterProjectFactory = FlutterProjectFactory(
fileSystem: globals.fs,
logger: globals.logger,
);
});
}); });
test('Handles asking for builders from an invalid pubspec', () => testbed.run(() { test('Handles asking for builders from an invalid pubspec', () => testbed.run(() {
...@@ -646,7 +673,10 @@ void testInMemory(String description, Future<void> testMethod()) { ...@@ -646,7 +673,10 @@ void testInMemory(String description, Future<void> testMethod()) {
packagesFile.createSync(recursive: true); packagesFile.createSync(recursive: true);
packagesFile.writeAsStringSync('flutter_template_images:${dummyTemplateImagesDirectory.uri}'); packagesFile.writeAsStringSync('flutter_template_images:${dummyTemplateImagesDirectory.uri}');
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(
fileSystem: testFileSystem,
logger: globals.logger ?? BufferLogger.test(),
);
testUsingContext( testUsingContext(
description, description,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/android/android_builder.dart'; import 'package:flutter_tools/src/android/android_builder.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
...@@ -39,7 +40,11 @@ class FakeAndroidBuilder implements AndroidBuilder { ...@@ -39,7 +40,11 @@ class FakeAndroidBuilder implements AndroidBuilder {
/// within [directoryOverride]. /// within [directoryOverride].
class FakeFlutterProjectFactory extends FlutterProjectFactory { class FakeFlutterProjectFactory extends FlutterProjectFactory {
FakeFlutterProjectFactory(this.directoryOverride) : FakeFlutterProjectFactory(this.directoryOverride) :
assert(directoryOverride != null); assert(directoryOverride != null),
super(
fileSystem: globals.fs,
logger: globals.logger,
);
final Directory directoryOverride; final Directory directoryOverride;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment