// 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:convert'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; class TestAssetBundle extends CachingAssetBundle { TestAssetBundle(this._assetBundleMap); final Map<String, List<Map<Object?, Object?>>> _assetBundleMap; Map<String, int> loadCallCount = <String, int>{}; @override Future<ByteData> load(String key) async { if (key == 'AssetManifest.bin') { return const StandardMessageCodec().encodeMessage(_assetBundleMap)!; } if (key == 'AssetManifest.bin.json') { // Encode the manifest data that will be used by the app final ByteData data = const StandardMessageCodec().encodeMessage(_assetBundleMap)!; // Simulate the behavior of NetworkAssetBundle.load here, for web tests return ByteData.sublistView( utf8.encode( json.encode( base64.encode( // Encode only the actual bytes of the buffer, and no more... data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes) ) ) ) ); } loadCallCount[key] = loadCallCount[key] ?? 0 + 1; if (key == 'one') { return ByteData(1) ..setInt8(0, 49); } throw FlutterError('key not found'); } @override Future<ui.ImmutableBuffer> loadBuffer(String key) async { final ByteData data = await load(key); return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List()); } } void main() { group('1.0 scale device tests', () { void buildAndTestWithOneAsset(String mainAssetPath) { final Map<String, List<Map<Object?, Object?>>> assetBundleMap = <String, List<Map<Object?, Object?>>>{}; final AssetImage assetImage = AssetImage( mainAssetPath, bundle: TestAssetBundle(assetBundleMap), ); const ImageConfiguration configuration = ImageConfiguration.empty; assetImage.obtainKey(configuration) .then(expectAsync1((AssetBundleImageKey bundleKey) { expect(bundleKey.name, mainAssetPath); expect(bundleKey.scale, 1.0); })); } test('When asset is main variant check scale is 1.0', () { buildAndTestWithOneAsset('assets/normalFolder/normalFile.png'); }); test('When asset path and key are the same string even though it could be took as a 3.0x variant', () async { buildAndTestWithOneAsset('assets/parentFolder/3.0x/normalFile.png'); }); test('When asset path contains variant identifier as part of parent folder name scale is 1.0', () { buildAndTestWithOneAsset('assets/parentFolder/__3.0x__/leafFolder/normalFile.png'); }); test('When asset path contains variant identifier as part of leaf folder name scale is 1.0', () { buildAndTestWithOneAsset('assets/parentFolder/__3.0x_leaf_folder_/normalFile.png'); }); test('When asset path contains variant identifier as part of parent folder name scale is 1.0', () { buildAndTestWithOneAsset('assets/parentFolder/__3.0x__/leafFolder/normalFile.png'); }); test('When asset path contains variant identifier in parent folder scale is 1.0', () { buildAndTestWithOneAsset('assets/parentFolder/3.0x/leafFolder/normalFile.png'); }); }); group('High-res device behavior tests', () { test('When asset is not main variant check scale is not 1.0', () { const String mainAssetPath = 'assets/normalFolder/normalFile.png'; const String variantPath = 'assets/normalFolder/3.0x/normalFile.png'; final Map<String, List<Map<Object?, Object?>>> assetBundleMap = <String, List<Map<Object?, Object?>>>{}; final Map<Object?, Object?> mainAssetVariantManifestEntry = <Object?, Object?>{}; mainAssetVariantManifestEntry['asset'] = variantPath; mainAssetVariantManifestEntry['dpr'] = 3.0; assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[mainAssetVariantManifestEntry]; final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap); final AssetImage assetImage = AssetImage( mainAssetPath, bundle: testAssetBundle, ); assetImage.obtainKey(ImageConfiguration.empty) .then(expectAsync1((AssetBundleImageKey bundleKey) { expect(bundleKey.name, mainAssetPath); expect(bundleKey.scale, 1.0); })); assetImage.obtainKey(ImageConfiguration( bundle: testAssetBundle, devicePixelRatio: 3.0, )).then(expectAsync1((AssetBundleImageKey bundleKey) { expect(bundleKey.name, variantPath); expect(bundleKey.scale, 3.0); })); }); test('When high-res device and high-res asset not present in bundle then return main variant', () { const String mainAssetPath = 'assets/normalFolder/normalFile.png'; final Map<String, List<Map<Object?, Object?>>> assetBundleMap = <String, List<Map<Object?, Object?>>>{}; assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[]; final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap); final AssetImage assetImage = AssetImage( mainAssetPath, bundle: TestAssetBundle(assetBundleMap), ); assetImage.obtainKey(ImageConfiguration.empty) .then(expectAsync1((AssetBundleImageKey bundleKey) { expect(bundleKey.name, mainAssetPath); expect(bundleKey.scale, 1.0); })); assetImage.obtainKey(ImageConfiguration( bundle: testAssetBundle, devicePixelRatio: 3.0, )).then(expectAsync1((AssetBundleImageKey bundleKey) { expect(bundleKey.name, mainAssetPath); expect(bundleKey.scale, 1.0); })); }); }); group('Regression - When assets available are 1.0 and 3.0 check devices with a range of scales', () { const String mainAssetPath = 'assets/normalFolder/normalFile.png'; const String variantPath = 'assets/normalFolder/3.0x/normalFile.png'; void buildBundleAndTestVariantLogic( double deviceRatio, double chosenAssetRatio, String expectedAssetPath, ) { const Map<String, List<Map<Object?, Object?>>> assetManifest = <String, List<Map<Object?, Object?>>>{ 'assets/normalFolder/normalFile.png': <Map<Object?, Object?>>[ <Object?, Object?>{'asset': 'assets/normalFolder/normalFile.png'}, <Object?, Object?>{ 'asset': 'assets/normalFolder/3.0x/normalFile.png', 'dpr': 3.0 }, ] }; final TestAssetBundle testAssetBundle = TestAssetBundle(assetManifest); final AssetImage assetImage = AssetImage( mainAssetPath, bundle: testAssetBundle, ); // we have 1.0 and 3.0, asking for 1.5 should give assetImage.obtainKey(ImageConfiguration( bundle: testAssetBundle, devicePixelRatio: deviceRatio, )).then(expectAsync1((AssetBundleImageKey bundleKey) { expect(bundleKey.name, expectedAssetPath); expect(bundleKey.scale, chosenAssetRatio); })); } test('Obvious case 1.0 - we have exact asset', () { buildBundleAndTestVariantLogic(1.0, 1.0, mainAssetPath); }); test('Obvious case 3.0 - we have exact asset', () { buildBundleAndTestVariantLogic(3.0, 3.0, variantPath); }); test('Typical case 2.0', () { buildBundleAndTestVariantLogic(2.0, 1.0, mainAssetPath); }); test('Borderline case 2.01', () { buildBundleAndTestVariantLogic(2.01, 3.0, variantPath); }); test('Borderline case 2.9', () { buildBundleAndTestVariantLogic(2.9, 3.0, variantPath); }); test('Typical case 4.0', () { buildBundleAndTestVariantLogic(4.0, 3.0, variantPath); }); }); }