Bundle assets used in packages (#11751)
diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart
index ac48364..c29b52a 100644
--- a/packages/flutter/lib/src/services/image_provider.dart
+++ b/packages/flutter/lib/src/services/image_provider.dart
@@ -589,27 +589,94 @@
/// Fetches an image from an [AssetBundle], associating it with the given scale.
///
-/// This implementation requires an explicit final [name] and [scale] on
+/// This implementation requires an explicit final [assetName] and [scale] on
/// construction, and ignores the device pixel ratio and size in the
/// configuration passed into [resolve]. For a resolution-aware variant that
/// uses the configuration to pick an appropriate image based on the device
/// pixel ratio and size, see [AssetImage].
+///
+/// ## Fetching assets
+///
+/// When fetching an image provided by the app itself, use the [assetName]
+/// argument to name the asset to choose. For instance, consider a directory
+/// `icons` with an image `heart.png`. First, the [pubspec.yaml] of the project
+/// should specify its assets in the `flutter` section:
+///
+/// ```yaml
+/// flutter:
+/// assets:
+/// - icons/heart.png
+/// ```
+///
+/// Then, to fetch the image and associate it with scale `1.5`, use
+///
+/// ```dart
+/// new AssetImage('icons/heart.png', scale: 1.5)
+/// ```
+///
+///## Assets in packages
+///
+/// To fetch an asset from a package, the [package] argument must be provided.
+/// For instance, suppose the structure above is inside a package called
+/// `my_icons`. Then to fetch the image, use:
+///
+/// ```dart
+/// new AssetImage('icons/heart.png', scale: 1.5, package: 'my_icons')
+/// ```
+///
+/// Assets used by the package itself should also be fetched using the [package]
+/// argument as above.
+///
+/// If the desired asset is specified in the [pubspec.yaml] of the package, it
+/// is bundled automatically with the app. In particular, assets used by the
+/// package itself must be specified in its [pubspec.yaml].
+///
+/// A package can also choose to have assets in its 'lib/' folder that are not
+/// specified in its [pubspec.yaml]. In this case for those images to be
+/// bundled, the app has to specify which ones to include. For instance a
+/// package named `fancy_backgrounds` could have:
+///
+/// ```
+/// lib/backgrounds/background1.png
+/// lib/backgrounds/background2.png
+/// lib/backgrounds/background3.png
+///```
+///
+/// To include, say the first image, the [pubspec.yaml] of the app should specify
+/// it in the `assets` section:
+///
+/// ```yaml
+/// assets:
+/// - packages/fancy_backgrounds/backgrounds/background1.png
+/// ```
+///
+/// Note that the `lib/` is implied, so it should not be included in the asset
+/// path.
+///
class ExactAssetImage extends AssetBundleImageProvider {
/// Creates an object that fetches the given image from an asset bundle.
///
- /// The [name] and [scale] arguments must not be null. The [scale] arguments
+ /// The [assetName] and [scale] arguments must not be null. The [scale] arguments
/// defaults to 1.0. The [bundle] argument may be null, in which case the
/// bundle provided in the [ImageConfiguration] passed to the [resolve] call
/// will be used instead.
- const ExactAssetImage(this.name, {
+ ///
+ /// The [package] argument must be non-null when fetching an asset that is
+ /// included in a package. See the documentation for the [ExactAssetImage] class
+ /// itself for details.
+ const ExactAssetImage(this.assetName, {
this.scale: 1.0,
- this.bundle
- }) : assert(name != null),
+ this.bundle,
+ this.package,
+ }) : assert(assetName != null),
assert(scale != null);
+ /// The name of the asset.
+ final String assetName;
+
/// The key to use to obtain the resource from the [bundle]. This is the
/// argument passed to [AssetBundle.load].
- final String name;
+ String get keyName => package == null ? assetName : 'packages/$package/$assetName';
/// The scale to place in the [ImageInfo] object of the image.
final double scale;
@@ -621,14 +688,18 @@
/// that is also null, the [rootBundle] is used.
///
/// The image is obtained by calling [AssetBundle.load] on the given [bundle]
- /// using the key given by [name].
+ /// using the key given by [keyName].
final AssetBundle bundle;
+ /// The name of the package from which the image is included. See the
+ /// documentation for the [ExactAssetImage] class itself for details.
+ final String package;
+
@override
Future<AssetBundleImageKey> obtainKey(ImageConfiguration configuration) {
return new SynchronousFuture<AssetBundleImageKey>(new AssetBundleImageKey(
bundle: bundle ?? configuration.bundle ?? rootBundle,
- name: name,
+ name: keyName,
scale: scale
));
}
@@ -638,14 +709,14 @@
if (other.runtimeType != runtimeType)
return false;
final ExactAssetImage typedOther = other;
- return name == typedOther.name
+ return keyName == typedOther.keyName
&& scale == typedOther.scale
&& bundle == typedOther.bundle;
}
@override
- int get hashCode => hashValues(name, scale, bundle);
+ int get hashCode => hashValues(keyName, scale, bundle);
@override
- String toString() => '$runtimeType(name: "$name", scale: $scale, bundle: $bundle)';
+ String toString() => '$runtimeType(name: "$keyName", scale: $scale, bundle: $bundle)';
}
diff --git a/packages/flutter/lib/src/services/image_resolution.dart b/packages/flutter/lib/src/services/image_resolution.dart
index 78764c3..0f6b1e8 100644
--- a/packages/flutter/lib/src/services/image_resolution.dart
+++ b/packages/flutter/lib/src/services/image_resolution.dart
@@ -55,16 +55,84 @@
/// icons/1.5x/heart.png
/// icons/2.0x/heart.png
/// ```
+///
+/// ## Fetching assets
+///
+/// When fetching an image provided by the app itself, use the [assetName]
+/// argument to name the asset to choose. For instance, consider the structure
+/// above. First, the [pubspec.yaml] of the project should specify its assets in
+/// the `flutter` section:
+///
+/// ```yaml
+/// flutter:
+/// assets:
+/// - icons/heart.png
+/// ```
+///
+/// Then, to fetch the image, use
+/// ```dart
+/// new AssetImage('icons/heart.png')
+/// ```
+///
+/// ## Assets in packages
+///
+/// To fetch an asset from a package, the [package] argument must be provided.
+/// For instance, suppose the structure above is inside a package called
+/// `my_icons`. Then to fetch the image, use:
+///
+/// ```dart
+/// new AssetImage('icons/heart.png', package: 'my_icons')
+/// ```
+///
+/// Assets used by the package itself should also be fetched using the [package]
+/// argument as above.
+///
+/// If the desired asset is specified in the [pubspec.yaml] of the package, it
+/// is bundled automatically with the app. In particular, assets used by the
+/// package itself must be specified in its [pubspec.yaml].
+///
+/// A package can also choose to have assets in its 'lib/' folder that are not
+/// specified in its [pubspec.yaml]. In this case for those images to be
+/// bundled, the app has to specify which ones to include. For instance a
+/// package named `fancy_backgrounds` could have:
+///
+/// ```
+/// lib/backgrounds/background1.png
+/// lib/backgrounds/background2.png
+/// lib/backgrounds/background3.png
+///```
+///
+/// To include, say the first image, the [pubspec.yaml] of the app should specify
+/// it in the `assets` section:
+///
+/// ```yaml
+/// assets:
+/// - packages/fancy_backgrounds/backgrounds/background1.png
+/// ```
+///
+/// Note that the `lib/` is implied, so it should not be included in the asset
+/// path.
+///
class AssetImage extends AssetBundleImageProvider {
/// Creates an object that fetches an image from an asset bundle.
///
- /// The [name] argument must not be null. It should name the main asset from
- /// the set of images to chose from.
- const AssetImage(this.name, { this.bundle }) : assert(name != null);
-
- /// The name of the main asset from the set of images to chose from. See the
+ /// The [assetName] argument must not be null. It should name the main asset
+ /// from the set of images to choose from. The [package] argument must be
+ /// non-null when fetching an asset that is included in package. See the
/// documentation for the [AssetImage] class itself for details.
- final String name;
+ const AssetImage(this.assetName, {
+ this.bundle,
+ this.package,
+ }) : assert(assetName != null);
+
+ /// The name of the main asset from the set of images to choose from. See the
+ /// documentation for the [AssetImage] class itself for details.
+ final String assetName;
+
+ /// The name used to generate the key to obtain the asset. For local assets
+ /// this is [assetName], and for assets from packages the [assetName] is
+ /// prefixed 'packages/<package_name>/'.
+ String get keyName => package == null ? assetName : 'packages/$package/$assetName';
/// The bundle from which the image will be obtained.
///
@@ -73,9 +141,15 @@
/// that is also null, the [rootBundle] is used.
///
/// The image is obtained by calling [AssetBundle.load] on the given [bundle]
- /// using the key given by [name].
+ /// using the key given by [keyName].
final AssetBundle bundle;
+ /// The name of the package from which the image is included. See the
+ /// documentation for the [AssetImage] class itself for details.
+ final String package;
+
+
+
// We assume the main asset is designed for a device pixel ratio of 1.0
static const double _naturalResolution = 1.0;
@@ -93,9 +167,9 @@
chosenBundle.loadStructuredData<Map<String, List<String>>>(_kAssetManifestFileName, _manifestParser).then<Null>(
(Map<String, List<String>> manifest) {
final String chosenName = _chooseVariant(
- name,
+ keyName,
configuration,
- manifest == null ? null : manifest[name]
+ manifest == null ? null : manifest[keyName]
);
final double chosenScale = _parseScale(chosenName);
final AssetBundleImageKey key = new AssetBundleImageKey(
@@ -185,13 +259,13 @@
if (other.runtimeType != runtimeType)
return false;
final AssetImage typedOther = other;
- return name == typedOther.name
+ return keyName == typedOther.keyName
&& bundle == typedOther.bundle;
}
@override
- int get hashCode => hashValues(name, bundle);
+ int get hashCode => hashValues(keyName, bundle);
@override
- String toString() => '$runtimeType(bundle: $bundle, name: "$name")';
+ String toString() => '$runtimeType(bundle: $bundle, name: "$keyName")';
}
diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart
index c531e20..d8c5f97 100644
--- a/packages/flutter/lib/src/widgets/image.dart
+++ b/packages/flutter/lib/src/widgets/image.dart
@@ -113,7 +113,8 @@
this.alignment,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
- this.gaplessPlayback: false
+ this.gaplessPlayback: false,
+ this.package,
}) : assert(image != null),
super(key: key);
@@ -131,7 +132,8 @@
this.alignment,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
- this.gaplessPlayback: false
+ this.gaplessPlayback: false,
+ this.package,
}) : image = new NetworkImage(src, scale: scale),
super(key: key);
@@ -152,13 +154,18 @@
this.alignment,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
- this.gaplessPlayback: false
+ this.gaplessPlayback: false,
+ this.package,
}) : image = new FileImage(file, scale: scale),
super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from an asset
/// bundle. The key for the image is given by the `name` argument.
///
+ /// The `package` argument must be non-null when displaying an image from a
+ /// package and null otherwise. See the `Assets in packages` section for
+ /// details.
+ ///
/// If the `bundle` argument is omitted or null, then the
/// [DefaultAssetBundle] will be used.
///
@@ -210,6 +217,49 @@
/// be present in the manifest). If it is omitted, then on a device with a 1.0
/// device pixel ratio, the `images/2x/cat.png` image would be used instead.
///
+ ///
+ /// ## Assets in packages
+ ///
+ /// To create the widget with an asset from a package, the [package] argument
+ /// must be provided. For instance, suppose a package called `my_icons` has
+ /// `icons/heart.png` .
+ ///
+ /// Then to display the image, use:
+ ///
+ /// ```dart
+ /// new Image.asset('icons/heart.png', package: 'my_icons')
+ /// ```
+ ///
+ /// Assets used by the package itself should also be displayed using the
+ /// [package] argument as above.
+ ///
+ /// If the desired asset is specified in the [pubspec.yaml] of the package, it
+ /// is bundled automatically with the app. In particular, assets used by the
+ /// package itself must be specified in its [pubspec.yaml].
+ ///
+ /// A package can also choose to have assets in its 'lib/' folder that are not
+ /// specified in its [pubspec.yaml]. In this case for those images to be
+ /// bundled, the app has to specify which ones to include. For instance a
+ /// package named `fancy_backgrounds` could have:
+ ///
+ /// ```
+ /// lib/backgrounds/background1.png
+ /// lib/backgrounds/background2.png
+ /// lib/backgrounds/background3.png
+ ///```
+ ///
+ /// To include, say the first image, the [pubspec.yaml] of the app should
+ /// specify it in the assets section:
+ ///
+ /// ```yaml
+ /// assets:
+ /// - packages/fancy_backgrounds/backgrounds/background1.png
+ /// ```
+ ///
+ /// Note that the `lib/` is implied, so it should not be included in the asset
+ /// path.
+ ///
+ ///
/// See also:
///
/// * [AssetImage], which is used to implement the behavior when the scale is
@@ -230,10 +280,12 @@
this.alignment,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
- this.gaplessPlayback: false
- }) : image = scale != null ? new ExactAssetImage(name, bundle: bundle, scale: scale)
- : new AssetImage(name, bundle: bundle),
- super(key: key);
+ this.gaplessPlayback: false,
+ this.package,
+ }) : image = scale != null
+ ? new ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
+ : new AssetImage(name, bundle: bundle, package: package),
+ super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
///
@@ -249,7 +301,8 @@
this.alignment,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
- this.gaplessPlayback: false
+ this.gaplessPlayback: false,
+ this.package,
}) : image = new MemoryImage(bytes, scale: scale),
super(key: key);
@@ -310,6 +363,10 @@
/// (false), when the image provider changes.
final bool gaplessPlayback;
+ /// The name of the package from which the image is included. See the
+ /// documentation for the [Image.asset] constructor for details.
+ final String package;
+
@override
_ImageState createState() => new _ImageState();
diff --git a/packages/flutter/test/widgets/image_package_asset_test.dart b/packages/flutter/test/widgets/image_package_asset_test.dart
new file mode 100644
index 0000000..e27ba92
--- /dev/null
+++ b/packages/flutter/test/widgets/image_package_asset_test.dart
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium 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:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ test('AssetImage from package', () {
+ final AssetImage image = const AssetImage(
+ 'assets/image.png',
+ package: 'test_package',
+ );
+ expect(image.keyName, 'packages/test_package/assets/image.png');
+ });
+
+ test('ExactAssetImage from package', () {
+ final ExactAssetImage image = const ExactAssetImage(
+ 'assets/image.png',
+ scale: 1.5,
+ package: 'test_package',
+ );
+ expect(image.keyName, 'packages/test_package/assets/image.png');
+ });
+
+ test('Image.asset from package', () {
+ final Image imageWidget = new Image.asset(
+ 'assets/image.png',
+ package: 'test_package',
+ );
+ assert(imageWidget.image is AssetImage);
+ final AssetImage assetImage = imageWidget.image;
+ expect(assetImage.keyName, 'packages/test_package/assets/image.png');
+ });
+
+ test('Image.asset from package', () {
+ final Image imageWidget = new Image.asset(
+ 'assets/image.png',
+ scale: 1.5,
+ package: 'test_package',
+ );
+ assert(imageWidget.image is ExactAssetImage);
+ final ExactAssetImage asssetImage = imageWidget.image;
+ expect(asssetImage.keyName, 'packages/test_package/assets/image.png');
+ });
+}
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index aef0882..24f94d9 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -86,11 +86,12 @@
return 0;
}
if (manifest != null) {
- final int result = await _validateFlutterManifest(manifest);
- if (result != 0)
- return result;
+ final int result = await _validateFlutterManifest(manifest);
+ if (result != 0)
+ return result;
}
Map<String, dynamic> manifestDescriptor = manifest;
+ final String appName = manifestDescriptor['name'];
manifestDescriptor = manifestDescriptor['flutter'] ?? <String, dynamic>{};
final String assetBasePath = fs.path.dirname(fs.path.absolute(manifestPath));
@@ -116,6 +117,33 @@
manifestDescriptor.containsKey('uses-material-design') &&
manifestDescriptor['uses-material-design'];
+ // Add assets from packages.
+ for (String packageName in packageMap.map.keys) {
+ final Uri package = packageMap.map[packageName];
+ if (package != null && package.scheme == 'file') {
+ final String packageManifestPath = package.resolve('../pubspec.yaml').path;
+ final Object packageManifest = _loadFlutterManifest(packageManifestPath);
+ if (packageManifest == null)
+ continue;
+ final int result = await _validateFlutterManifest(packageManifest);
+ if (result == 0) {
+ final Map<String, dynamic> packageManifestDescriptor = packageManifest;
+ // Skip the app itself.
+ if (packageManifestDescriptor['name'] == appName)
+ continue;
+ if (packageManifestDescriptor.containsKey('flutter')) {
+ final String packageBasePath = fs.path.dirname(packageManifestPath);
+ assetVariants.addAll(_parseAssets(
+ packageMap,
+ packageManifestDescriptor['flutter'],
+ packageBasePath,
+ packageKey: packageName,
+ ));
+ }
+ }
+ }
+ }
+
// Save the contents of each image, image variant, and font
// asset in entries.
for (_Asset asset in assetVariants.keys) {
@@ -203,6 +231,27 @@
@override
String toString() => 'asset: $assetEntry';
+
+ @override
+ bool operator ==(dynamic other) {
+ if (identical(other, this))
+ return true;
+ if (other.runtimeType != runtimeType)
+ return false;
+ final _Asset otherAsset = other;
+ return otherAsset.base == base
+ && otherAsset.assetEntry == assetEntry
+ && otherAsset.relativePath == relativePath
+ && otherAsset.source == source;
+ }
+
+ @override
+ int get hashCode {
+ return base.hashCode
+ ^assetEntry.hashCode
+ ^relativePath.hashCode
+ ^ source.hashCode;
+ }
}
Map<String, dynamic> _readMaterialFontsManifest() {
@@ -312,8 +361,8 @@
for (_Asset main in assetVariants.keys) {
final List<String> variants = <String>[];
for (_Asset variant in assetVariants[main])
- variants.add(variant.relativePath);
- json[main.relativePath] = variants;
+ variants.add(variant.assetEntry);
+ json[main.assetEntry] = variants;
}
return new DevFSStringContent(JSON.encode(json));
}
@@ -384,7 +433,8 @@
PackageMap packageMap,
Map<String, dynamic> manifestDescriptor,
String assetBase, {
- List<String> excludeDirs: const <String>[]
+ List<String> excludeDirs: const <String>[],
+ String packageKey
}) {
final Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
@@ -394,7 +444,9 @@
if (manifestDescriptor.containsKey('assets')) {
final _AssetDirectoryCache cache = new _AssetDirectoryCache(excludeDirs);
for (String assetName in manifestDescriptor['assets']) {
- final _Asset asset = _resolveAsset(packageMap, assetBase, assetName);
+ final _Asset asset = packageKey != null
+ ? _resolvePackageAsset(assetBase, packageKey, assetName)
+ : _resolveAsset(packageMap, assetBase, assetName);
final List<_Asset> variants = <_Asset>[];
for (String path in cache.variantsFor(asset.assetFile.path)) {
@@ -435,10 +487,22 @@
return result;
}
+_Asset _resolvePackageAsset(
+ String assetBase,
+ String packageName,
+ String asset,
+) {
+ return new _Asset(
+ base: assetBase,
+ assetEntry: 'packages/$packageName/$asset',
+ relativePath: asset,
+ );
+}
+
_Asset _resolveAsset(
PackageMap packageMap,
String assetBase,
- String asset
+ String asset,
) {
if (asset.startsWith('packages/') && !fs.isFileSync(fs.path.join(assetBase, asset))) {
// Convert packages/flutter_gallery_assets/clouds-0.png to clouds-0.png.
diff --git a/packages/flutter_tools/test/asset_bundle_package_test.dart b/packages/flutter_tools/test/asset_bundle_package_test.dart
new file mode 100644
index 0000000..a072610
--- /dev/null
+++ b/packages/flutter_tools/test/asset_bundle_package_test.dart
@@ -0,0 +1,225 @@
+// Copyright 2017 The Chromium 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 'dart:convert';
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+
+import 'package:flutter_tools/src/asset.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
+
+import 'package:test/test.dart';
+
+import 'src/common.dart';
+import 'src/context.dart';
+
+void main() {
+ void writePubspecFile(String path, String name, {List<String> assets}) {
+ String assetsSection;
+ if (assets == null) {
+ assetsSection = '';
+ } else {
+ final StringBuffer buffer = new StringBuffer();
+ buffer.write('''
+flutter:
+ assets:
+''');
+
+ for (String asset in assets) {
+ buffer.write('''
+ - $asset
+''');
+ }
+ assetsSection = buffer.toString();
+ }
+
+ fs.file(path)
+ ..createSync(recursive: true)
+ ..writeAsStringSync('''
+name: $name
+dependencies:
+ flutter:
+ sdk: flutter
+$assetsSection
+''');
+ }
+
+ void establishFlutterRoot() {
+ // Setting flutterRoot here so that it picks up the MemoryFileSystem's
+ // path separator.
+ Cache.flutterRoot = getFlutterRoot();
+ }
+
+ void writePackagesFile(String packages) {
+ fs.file(".packages")
+ ..createSync()
+ ..writeAsStringSync(packages);
+ }
+
+ Future<Null> buildAndVerifyAssets(
+ List<String> assets,
+ List<String> packages,
+ String expectedAssetManifest,
+ ) async {
+ final AssetBundle bundle = new AssetBundle();
+ await bundle.build(manifestPath: 'pubspec.yaml');
+
+ for (String packageName in packages) {
+ for (String asset in assets) {
+ final String entryKey = 'packages/$packageName/$asset';
+ expect(bundle.entries.containsKey(entryKey), true);
+ expect(
+ UTF8.decode(await bundle.entries[entryKey].contentsAsBytes()),
+ asset,
+ );
+ }
+ }
+
+ expect(
+ UTF8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
+ expectedAssetManifest,
+ );
+ }
+
+ void writeAssets(String path, List<String> assets) {
+ for (String asset in assets) {
+ fs.file('$path$asset')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(asset);
+ }
+ }
+
+ group('AssetBundle assets from package', () {
+ testUsingContext('One package with no assets', () async {
+ establishFlutterRoot();
+
+ writePubspecFile('pubspec.yaml', 'test');
+ writePackagesFile('test_package:p/p/lib/');
+ writePubspecFile('p/p/pubspec.yaml', 'test_package');
+
+ final AssetBundle bundle = new AssetBundle();
+ await bundle.build(manifestPath: 'pubspec.yaml');
+ expect(bundle.entries.length, 2); // LICENSE, AssetManifest
+ }, overrides: <Type, Generator>{
+ FileSystem: () => new MemoryFileSystem(),
+ });
+
+ testUsingContext('One package with one asset', () async {
+ establishFlutterRoot();
+
+ writePubspecFile('pubspec.yaml', 'test');
+ writePackagesFile('test_package:p/p/lib/');
+
+ final List<String> assets = <String>['a/foo'];
+ writePubspecFile(
+ 'p/p/pubspec.yaml',
+ 'test_package',
+ assets: assets,
+ );
+
+ writeAssets('p/p/', assets);
+
+ final String expectedAssetManifest = '{"packages/test_package/a/foo":'
+ '["packages/test_package/a/foo"]}';
+ await buildAndVerifyAssets(
+ assets,
+ <String>['test_package'],
+ expectedAssetManifest,
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => new MemoryFileSystem(),
+ });
+
+ testUsingContext('One package with asset variants', () async {
+ establishFlutterRoot();
+
+ writePubspecFile('pubspec.yaml', 'test');
+ writePackagesFile('test_package:p/p/lib/');
+ writePubspecFile(
+ 'p/p/pubspec.yaml',
+ 'test_package',
+ assets: <String>['a/foo'],
+ );
+
+ final List<String> assets = <String>['a/foo', 'a/v/foo'];
+ writeAssets('p/p/', assets);
+
+ final String expectedManifest = '{"packages/test_package/a/foo":'
+ '["packages/test_package/a/foo","packages/test_package/a/v/foo"]}';
+
+ await buildAndVerifyAssets(
+ assets,
+ <String>['test_package'],
+ expectedManifest,
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => new MemoryFileSystem(),
+ });
+
+ testUsingContext('One package with two assets', () async {
+ establishFlutterRoot();
+
+ writePubspecFile('pubspec.yaml', 'test');
+ writePackagesFile('test_package:p/p/lib/');
+
+ final List<String> assets = <String>['a/foo', 'a/bar'];
+ writePubspecFile(
+ 'p/p/pubspec.yaml',
+ 'test_package',
+ assets: assets,
+ );
+
+ writeAssets('p/p/', assets);
+ final String expectedAssetManifest =
+ '{"packages/test_package/a/foo":["packages/test_package/a/foo"],'
+ '"packages/test_package/a/bar":["packages/test_package/a/bar"]}';
+
+ await buildAndVerifyAssets(
+ assets,
+ <String>['test_package'],
+ expectedAssetManifest,
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => new MemoryFileSystem(),
+ });
+
+ testUsingContext('Two packages with assets', () async {
+ establishFlutterRoot();
+
+ writePubspecFile('pubspec.yaml', 'test');
+ writePackagesFile('test_package:p/p/lib/\ntest_package2:p2/p/lib/');
+ writePubspecFile(
+ 'p/p/pubspec.yaml',
+ 'test_package',
+ assets: <String>['a/foo'],
+ );
+ writePubspecFile(
+ 'p2/p/pubspec.yaml',
+ 'test_package2',
+ assets: <String>['a/foo'],
+ );
+
+ final List<String> assets = <String>['a/foo', 'a/v/foo'];
+ writeAssets('p/p/', assets);
+ writeAssets('p2/p/', assets);
+
+ final String expectedAssetManifest =
+ '{"packages/test_package/a/foo":'
+ '["packages/test_package/a/foo","packages/test_package/a/v/foo"],'
+ '"packages/test_package2/a/foo":'
+ '["packages/test_package2/a/foo","packages/test_package2/a/v/foo"]}';
+
+ await buildAndVerifyAssets(
+ assets,
+ <String>['test_package', 'test_package2'],
+ expectedAssetManifest,
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => new MemoryFileSystem(),
+ });
+ });
+}