blob: 81f98300c38e428fdb444545ff2a150e0d5bdeb2 [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:async';
import 'package:file/file.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/cache.dart';
import 'package:flutter_tools/src/flutter_manifest.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/pubspec_schema.dart';
void main() {
setUpAll(() {
Cache.flutterRoot = getFlutterRoot();
});
group('FlutterManifest', () {
testUsingContext('is empty when the pubspec.yaml file is empty', () async {
final FlutterManifest flutterManifest = FlutterManifest.createFromString('');
expect(flutterManifest.isEmpty, true);
expect(flutterManifest.appName, '');
expect(flutterManifest.usesMaterialDesign, false);
expect(flutterManifest.fontsDescriptor, isEmpty);
expect(flutterManifest.fonts, isEmpty);
expect(flutterManifest.assets, isEmpty);
});
test('has no fonts or assets when the "flutter" section is empty', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, isNotNull);
expect(flutterManifest.isEmpty, false);
expect(flutterManifest.appName, 'test');
expect(flutterManifest.usesMaterialDesign, false);
expect(flutterManifest.fontsDescriptor, isEmpty);
expect(flutterManifest.fonts, isEmpty);
expect(flutterManifest.assets, isEmpty);
});
test('knows if material design is used', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.usesMaterialDesign, true);
});
test('has two assets', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
assets:
- a/foo
- a/bar
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.assets.length, 2);
expect(flutterManifest.assets[0], Uri.parse('a/foo'));
expect(flutterManifest.assets[1], Uri.parse('a/bar'));
});
test('has one font family with one asset', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
fonts:
- family: foo
fonts:
- asset: a/bar
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts;
expect(fonts.length, 1);
final Font font = fonts[0];
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}]}; // ignore: always_specify_types
expect(font.descriptor, fooFontDescriptor);
expect(font.familyName, 'foo');
final List<FontAsset> assets = font.fontAssets;
expect(assets.length, 1);
final FontAsset fontAsset = assets[0];
expect(fontAsset.assetUri.path, 'a/bar');
expect(fontAsset.weight, isNull);
expect(fontAsset.style, isNull);
});
test('has one font family with a simple asset and one with weight', () async {
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
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts;
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(font.descriptor, fooFontDescriptor);
expect(font.familyName, 'foo');
final List<FontAsset> assets = font.fontAssets;
expect(assets.length, 2);
final FontAsset fontAsset0 = assets[0];
expect(fontAsset0.assetUri.path, 'a/bar');
expect(fontAsset0.weight, isNull);
expect(fontAsset0.style, isNull);
final FontAsset fontAsset1 = assets[1];
expect(fontAsset1.assetUri.path, 'a/bar');
expect(fontAsset1.weight, 400);
expect(fontAsset1.style, isNull);
});
test('has one font family with a simple asset and one with weight and style', () async {
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
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts;
expect(fonts.length, 1);
final Font font = fonts[0];
final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types
expect(font.descriptor, fooFontDescriptor);
expect(font.familyName, 'foo');
final List<FontAsset> assets = font.fontAssets;
expect(assets.length, 2);
final FontAsset fontAsset0 = assets[0];
expect(fontAsset0.assetUri.path, 'a/bar');
expect(fontAsset0.weight, isNull);
expect(fontAsset0.style, isNull);
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 {
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 FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final dynamic expectedFontsDescriptor = <dynamic>[
{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}, // ignore: always_specify_types
{'fonts': [{'asset': 'a/baz'}, {'style': 'italic', 'weight': 400, 'asset': 'a/baz'}], 'family': 'bar'}, // ignore: always_specify_types
];
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts;
expect(fonts.length, 2);
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(fooFont.descriptor, fooFontDescriptor);
expect(fooFont.familyName, 'foo');
final List<FontAsset> fooAassets = fooFont.fontAssets;
expect(fooAassets.length, 2);
final FontAsset fooFontAsset0 = fooAassets[0];
expect(fooFontAsset0.assetUri.path, 'a/bar');
expect(fooFontAsset0.weight, isNull);
expect(fooFontAsset0.style, isNull);
final FontAsset fooFontAsset1 = fooAassets[1];
expect(fooFontAsset1.assetUri.path, 'a/bar');
expect(fooFontAsset1.weight, 400);
expect(fooFontAsset1.style, 'italic');
final Font barFont = fonts[1];
const String fontDescriptor = '{family: bar, fonts: [{asset: a/baz}, {weight: 400, style: italic, asset: a/baz}]}'; // ignore: always_specify_types
expect(barFont.descriptor.toString(), fontDescriptor);
expect(barFont.familyName, 'bar');
final List<FontAsset> barAssets = barFont.fontAssets;
expect(barAssets.length, 2);
final FontAsset barFontAsset0 = barAssets[0];
expect(barFontAsset0.assetUri.path, 'a/baz');
expect(barFontAsset0.weight, isNull);
expect(barFontAsset0.style, isNull);
final FontAsset barFontAsset1 = barAssets[1];
expect(barFontAsset1.assetUri.path, 'a/baz');
expect(barFontAsset1.weight, 400);
expect(barFontAsset1.style, 'italic');
});
testUsingContext('has only one of two font families when one declaration is missing the "family" option', () async {
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
- fonts:
- asset: a/baz
- asset: a/baz
weight: 400
style: italic
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts;
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(fooFont.descriptor, fooFontDescriptor);
expect(fooFont.familyName, 'foo');
final List<FontAsset> fooAassets = fooFont.fontAssets;
expect(fooAassets.length, 2);
final FontAsset fooFontAsset0 = fooAassets[0];
expect(fooFontAsset0.assetUri.path, 'a/bar');
expect(fooFontAsset0.weight, isNull);
expect(fooFontAsset0.style, isNull);
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 {
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
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
final List<Font> fonts = flutterManifest.fonts;
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(fooFont.descriptor, fooFontDescriptor);
expect(fooFont.familyName, 'foo');
final List<FontAsset> fooAassets = fooFont.fontAssets;
expect(fooAassets.length, 2);
final FontAsset fooFontAsset0 = fooAassets[0];
expect(fooFontAsset0.assetUri.path, 'a/bar');
expect(fooFontAsset0.weight, isNull);
expect(fooFontAsset0.style, isNull);
final FontAsset fooFontAsset1 = fooAassets[1];
expect(fooFontAsset1.assetUri.path, 'a/bar');
expect(fooFontAsset1.weight, 400);
expect(fooFontAsset1.style, 'italic');
});
testUsingContext('has no font family when declaration is missing the "asset" option', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
fonts:
- family: foo
fonts:
- weight: 400
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.fontsDescriptor, <dynamic>[]);
final List<Font> fonts = flutterManifest.fonts;
expect(fonts.length, 0);
});
test('allows a blank flutter section', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.isEmpty, false);
expect(flutterManifest.isModule, false);
expect(flutterManifest.isPlugin, false);
expect(flutterManifest.androidPackage, null);
expect(flutterManifest.usesAndroidX, false);
});
test('allows a module declaration', () async {
const String manifest = '''
name: test
flutter:
module:
androidPackage: com.example
androidX: true
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.isModule, true);
expect(flutterManifest.androidPackage, 'com.example');
expect(flutterManifest.usesAndroidX, true);
});
test('allows a legacy plugin declaration', () async {
const String manifest = '''
name: test
flutter:
plugin:
androidPackage: com.example
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.isPlugin, true);
expect(flutterManifest.androidPackage, 'com.example');
});
test('allows a multi-plat plugin declaration with android only', () async {
const String manifest = '''
name: test
flutter:
plugin:
platforms:
android:
package: com.example
pluginClass: TestPlugin
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.isPlugin, true);
expect(flutterManifest.androidPackage, 'com.example');
});
test('allows a multi-plat plugin declaration with ios only', () async {
const String manifest = '''
name: test
flutter:
plugin:
platforms:
ios:
pluginClass: HelloPlugin
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.isPlugin, true);
expect(flutterManifest.androidPackage, isNull);
});
testUsingContext('handles an invalid plugin declaration', () async {
const String manifest = '''
name: test
flutter:
plugin:
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "plugin" to be an object, but got null'));
});
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 = '''
name: test
version: 1.0.0+2
dependencies:
flutter:
sdk: flutter
flutter:
''';
await checkManifestVersion(
manifest: manifest,
expectedAppVersion: '1.0.0+2',
expectedBuildName: '1.0.0',
expectedBuildNumber: '2',
);
});
test('parses major.minor.patch with no build version', () async {
const String manifest = '''
name: test
version: 0.0.1
dependencies:
flutter:
sdk: flutter
flutter:
''';
await checkManifestVersion(
manifest: manifest,
expectedAppVersion: '0.0.1',
expectedBuildName: '0.0.1',
expectedBuildNumber: null,
);
});
test('parses major.minor.patch+build version clause 2', () async {
const String manifest = '''
name: test
version: 1.0.0-beta+exp.sha.5114f85
dependencies:
flutter:
sdk: flutter
flutter:
''';
await checkManifestVersion(
manifest: manifest,
expectedAppVersion: '1.0.0-beta+exp.sha.5114f85',
expectedBuildName: '1.0.0-beta',
expectedBuildNumber: 'exp.sha.5114f85',
);
});
test('parses major.minor+build version clause', () async {
const String manifest = '''
name: test
version: 1.0+2
dependencies:
flutter:
sdk: flutter
flutter:
''';
await checkManifestVersion(
manifest: manifest,
expectedAppVersion: '1.0+2',
expectedBuildName: '1.0',
expectedBuildNumber: '2',
);
});
test('parses empty version clause', () async {
const String manifest = '''
name: test
version:
dependencies:
flutter:
sdk: flutter
flutter:
''';
await checkManifestVersion(
manifest: manifest,
expectedAppVersion: null,
expectedBuildName: null,
expectedBuildNumber: null,
);
});
test('parses no version clause', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
''';
await checkManifestVersion(
manifest: manifest,
expectedAppVersion: null,
expectedBuildName: null,
expectedBuildNumber: null,
);
});
// Regression test for https://github.com/flutter/flutter/issues/31764
testUsingContext('Returns proper error when font detail is malformed', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
fonts:
- family: foo
fonts:
-asset: a/bar
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, null);
expect(testLogger.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 {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
fonts:
- family: foo
fonts:
- asset
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "fonts" to be a list of maps.'));
});
testUsingContext('Returns proper error when font is a map instead of a list', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
fonts:
family: foo
fonts:
-asset: a/bar
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "fonts" to be a list'));
});
testUsingContext('Returns proper error when second font family is invalid', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
fonts:
- family: foo
fonts:
- asset: a/bar
- string
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected a map.'));
});
testUsingContext('Does not crash on empty entry', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
assets:
- lib/gallery/example_code.dart
-
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final List<Uri> assets = flutterManifest.assets;
expect(testLogger.errorText, contains('Asset manifest contains a null or empty uri.'));
expect(assets.length, 1);
});
testUsingContext('Special characters in asset URIs', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
uses-material-design: true
assets:
- lib/gallery/abc#xyz
- lib/gallery/abc?xyz
- lib/gallery/aaa bbb
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
final List<Uri> assets = flutterManifest.assets;
expect(assets.length, 3);
expect(assets[0].path, 'lib/gallery/abc%23xyz');
expect(assets[1].path, 'lib/gallery/abc%3Fxyz');
expect(assets[2].path, 'lib/gallery/aaa%20bbb');
});
testUsingContext('Returns proper error when flutter is a list instead of a map', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
- uses-material-design: true
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest, null);
expect(testLogger.errorText, contains('Expected "flutter" section to be an object or null, but got [{uses-material-design: true}].'));
});
});
group('FlutterManifest with MemoryFileSystem', () {
Future<void> assertSchemaIsReadable() async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
''';
final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
expect(flutterManifest.isEmpty, false);
}
void testUsingContextAndFs(
String description,
FileSystem filesystem,
dynamic testMethod(),
) {
testUsingContext(
description,
() async {
writeEmptySchemaFile(filesystem);
testMethod();
},
overrides: <Type, Generator>{
FileSystem: () => filesystem,
ProcessManager: () => FakeProcessManager.any(),
},
);
}
testUsingContext('Validate manifest on original fs', () {
assertSchemaIsReadable();
});
testUsingContextAndFs(
'Validate manifest on Posix FS',
MemoryFileSystem(style: FileSystemStyle.posix),
() {
assertSchemaIsReadable();
},
);
testUsingContextAndFs(
'Validate manifest on Windows FS',
MemoryFileSystem(style: FileSystemStyle.windows),
() {
assertSchemaIsReadable();
},
);
});
}