// 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/file.dart'; import 'package:file/memory.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/flutter_manifest.dart'; import '../src/common.dart'; import '../src/context.dart'; void main() { setUpAll(() { Cache.flutterRoot = getFlutterRoot(); }); testWithoutContext('FlutterManifest is empty when the pubspec.yaml file is empty', () async { final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( '', logger: logger, ); expect(flutterManifest.isEmpty, true); expect(flutterManifest.appName, ''); expect(flutterManifest.usesMaterialDesign, false); expect(flutterManifest.fontsDescriptor, isEmpty); expect(flutterManifest.fonts, isEmpty); expect(flutterManifest.assets, isEmpty); }); testWithoutContext('FlutterManifest is null when the pubspec.yaml file is not a map', () async { final BufferLogger logger = BufferLogger.test(); expect(FlutterManifest.createFromString( 'Not a map', logger: logger, ), isNull); expect(logger.errorText, contains('Expected YAML map')); }); testWithoutContext('FlutterManifest has no fonts or assets when the "flutter" section is empty', () async { const String manifest = ''' name: test dependencies: flutter: sdk: flutter '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); 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); }); testWithoutContext('FlutterManifest knows if material design is used', () async { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: uses-material-design: true '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.usesMaterialDesign, true); }); testWithoutContext('FlutterManifest has two assets', () async { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: uses-material-design: true assets: - a/foo - a/bar '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.assets, [ Uri.parse('a/foo'), Uri.parse('a/bar'), ]); }); testWithoutContext('FlutterManifest 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.fonts, hasLength(1)); expect(flutterManifest.fonts.single, matchesFont( familyName: 'foo', descriptor: { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/bar')), ], )); }); testWithoutContext('FlutterManifest 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.fonts, hasLength(1)); expect(flutterManifest.fonts.single, matchesFont( familyName: 'foo', descriptor: { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, {'weight': 400, 'asset': 'a/bar'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/bar')), matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400), ]) ); }); testWithoutContext('FlutterManifest has one font family with a simple asset ' 'and one with weight and style', () { 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.fonts, hasLength(1)); expect(flutterManifest.fonts.single, matchesFont( familyName: 'foo', descriptor: { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/bar')), matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'), ], )); }); testWithoutContext('FlutterManifest has two font families, each with one ' 'simple asset and one with weight and style', () { 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.fonts, hasLength(2)); expect(flutterManifest.fonts, containsAll([ matchesFont( familyName: 'foo', descriptor: { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/bar')), matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'), ], ), matchesFont( familyName: 'bar', descriptor: { 'family': 'bar', 'fonts': [ {'asset': 'a/baz'}, {'weight': 400, 'style': 'italic', 'asset': 'a/baz'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/baz')), matchesFontAsset(assetUri: Uri.parse('a/baz'), weight: 400, style: 'italic'), ], ), ])); }); testWithoutContext('FlutterManifest.fontsDescriptor combines descriptors from ' '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, [ { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}, ], }, { 'family': 'bar', 'fonts': [ {'asset': 'a/baz'}, {'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 = ''' 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.fonts, hasLength(1)); expect(flutterManifest.fonts, containsAll([ matchesFont( familyName: 'foo', descriptor: { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/bar')), matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'), ], ), ])); }); testWithoutContext('FlutterManifest 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.fonts, hasLength(1)); expect(flutterManifest.fonts, containsAll([ matchesFont( familyName: 'foo', descriptor: { 'family': 'foo', 'fonts': [ {'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}, ], }, fontAssets: [ matchesFontAsset(assetUri: Uri.parse('a/bar')), matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'), ], ), ])); }); testWithoutContext('FlutterManifest 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.fontsDescriptor, isEmpty); expect(flutterManifest.fonts, isEmpty); }); testWithoutContext('FlutterManifest allows a blank flutter section', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.isEmpty, false); expect(flutterManifest.isModule, false); expect(flutterManifest.isPlugin, false); expect(flutterManifest.androidPackage, null); expect(flutterManifest.usesAndroidX, false); }); testWithoutContext('FlutterManifest allows a module declaration', () { const String manifest = ''' name: test flutter: module: androidPackage: com.example androidX: true '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.isModule, true); expect(flutterManifest.androidPackage, 'com.example'); expect(flutterManifest.usesAndroidX, true); }); testWithoutContext('FlutterManifest allows a legacy plugin declaration', () { const String manifest = ''' name: test flutter: plugin: androidPackage: com.example '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.isPlugin, true); expect(flutterManifest.androidPackage, 'com.example'); }); testWithoutContext('FlutterManifest allows a multi-plat plugin declaration ' 'with android only', () { const String manifest = ''' name: test flutter: plugin: platforms: android: package: com.example pluginClass: TestPlugin '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.isPlugin, true); expect(flutterManifest.androidPackage, 'com.example'); }); testWithoutContext('FlutterManifest allows a multi-plat plugin declaration ' 'with ios only', () { const String manifest = ''' name: test flutter: plugin: platforms: ios: pluginClass: HelloPlugin '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest.isPlugin, true); expect(flutterManifest.androidPackage, isNull); }); testUsingContext('FlutterManifest handles an invalid plugin declaration', () { const String manifest = ''' name: test flutter: plugin: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, null); expect(logger.errorText, contains('Expected "plugin" to be an object, but got null')); }); testWithoutContext('FlutterManifest parses major.minor.patch+build version clause 1', () { const String manifest = ''' name: test version: 1.0.0+2 dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, matchesManifest( appVersion: '1.0.0+2', buildName: '1.0.0', buildNumber: '2'), ); }); testWithoutContext('FlutterManifest parses major.minor.patch with no build version', () { const String manifest = ''' name: test version: 0.0.1 dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, matchesManifest( appVersion: '0.0.1', buildName: '0.0.1', buildNumber: null), ); }); testWithoutContext('FlutterManifest parses major.minor.patch+build version clause 2', () { const String manifest = ''' name: test version: 1.0.0-beta+exp.sha.5114f85 dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, matchesManifest( appVersion: '1.0.0-beta+exp.sha.5114f85', buildName: '1.0.0-beta', buildNumber: 'exp.sha.5114f85'), ); }); testWithoutContext('FlutterManifest parses major.minor+build version clause', () { const String manifest = ''' name: test version: 1.0+2 dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, matchesManifest( appVersion: '1.0+2', buildName: '1.0', buildNumber: '2'), ); }); testWithoutContext('FlutterManifest parses empty version clause', () { const String manifest = ''' name: test version: dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, matchesManifest( appVersion: null, buildName: null, buildNumber: null), ); }); testWithoutContext('FlutterManifest parses no version clause', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, matchesManifest( appVersion: null, buildName: null, buildNumber: null), ); }); // Regression test for https://github.com/flutter/flutter/issues/31764 testWithoutContext('FlutterManifest returns proper error when font detail is malformed', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: fonts: - family: foo fonts: -asset: a/bar '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, null); expect(logger.errorText, contains('Expected "fonts" to either be null or a list.')); }); testWithoutContext('FlutterManifest returns proper error when font detail is ' 'not a list of maps', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: fonts: - family: foo fonts: - asset '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, null); expect(logger.errorText, contains('Expected "fonts" to be a list of maps.')); }); testWithoutContext('FlutterManifest returns proper error when font is a map ' 'instead of a list', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: fonts: family: foo fonts: -asset: a/bar '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, null); expect(logger.errorText, contains('Expected "fonts" to be a list')); }); testWithoutContext('FlutterManifest returns proper error when second font ' 'family is invalid', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: uses-material-design: true fonts: - family: foo fonts: - asset: a/bar - string '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, null); expect(logger.errorText, contains('Expected a map.')); }); testWithoutContext('FlutterManifest does not crash on empty entry', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: uses-material-design: true assets: - lib/gallery/example_code.dart - '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); final List assets = flutterManifest.assets; expect(logger.errorText, contains('Asset manifest contains a null or empty uri.')); expect(assets, hasLength(1)); }); testWithoutContext('FlutterManifest handles special characters in asset URIs', () { 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 BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); final List assets = flutterManifest.assets; expect(assets, hasLength(3)); expect(assets, [ Uri.parse('lib/gallery/abc%23xyz'), Uri.parse('lib/gallery/abc%3Fxyz'), Uri.parse('lib/gallery/aaa%20bbb'), ]); }); testWithoutContext('FlutterManifest returns proper error when flutter is a ' 'list instead of a map', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: - uses-material-design: true '''; final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromString( manifest, logger: logger, ); expect(flutterManifest, null); expect(logger.errorText, contains( 'Expected "flutter" section to be an object or null, but got ' '[{uses-material-design: true}].', ), ); }); testWithoutContext('FlutterManifest can parse manifest on posix filesystem', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: '''; final FileSystem fileSystem = MemoryFileSystem.test(); fileSystem.file('pubspec.yaml').writeAsStringSync(manifest); final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromPath( 'pubspec.yaml', fileSystem: fileSystem, logger: logger, ); expect(flutterManifest.isEmpty, false); }); testWithoutContext('FlutterManifest can parse manifest on windows filesystem', () { const String manifest = ''' name: test dependencies: flutter: sdk: flutter flutter: '''; final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); fileSystem.file('pubspec.yaml').writeAsStringSync(manifest); final BufferLogger logger = BufferLogger.test(); final FlutterManifest flutterManifest = FlutterManifest.createFromPath( 'pubspec.yaml', fileSystem: fileSystem, logger: logger, ); expect(flutterManifest.isEmpty, false); }); } Matcher matchesManifest({ String appVersion, String buildName, String buildNumber, }) { return isA() .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() .having((FontAsset fontAsset) => fontAsset.assetUri, 'assetUri', assetUri) .having((FontAsset fontAsset) => fontAsset.weight, 'weight', weight) .having((FontAsset fontAsset) => fontAsset.style, 'style', style); } Matcher matchesFont({ Map descriptor, String familyName, List fontAssets, }) { return isA() .having((Font font) => font.descriptor, 'descriptor', descriptor) .having((Font font) => font.familyName, 'familyName', familyName) .having((Font font) => font.fontAssets, 'fontAssets', containsAll(fontAssets)); }