// 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>> _assetBundleMap; Map loadCallCount = {}; @override Future 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 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 assetBundleMap = >>{}; final 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 mainAssetPath = 'assets/normalFolder/normalFile.png'; const variantPath = 'assets/normalFolder/3.0x/normalFile.png'; final assetBundleMap = >>{}; final mainAssetVariantManifestEntry = {}; mainAssetVariantManifestEntry['asset'] = variantPath; mainAssetVariantManifestEntry['dpr'] = 3.0; assetBundleMap[mainAssetPath] = >[mainAssetVariantManifestEntry]; final testAssetBundle = TestAssetBundle(assetBundleMap); final 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 mainAssetPath = 'assets/normalFolder/normalFile.png'; final assetBundleMap = >>{}; assetBundleMap[mainAssetPath] = >[]; final testAssetBundle = TestAssetBundle(assetBundleMap); final 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 mainAssetPath = 'assets/normalFolder/normalFile.png'; const variantPath = 'assets/normalFolder/3.0x/normalFile.png'; void buildBundleAndTestVariantLogic( double deviceRatio, double chosenAssetRatio, String expectedAssetPath, ) { const assetManifest = >>{ 'assets/normalFolder/normalFile.png': >[ {'asset': 'assets/normalFolder/normalFile.png'}, {'asset': 'assets/normalFolder/3.0x/normalFile.png', 'dpr': 3.0}, ], }; final testAssetBundle = TestAssetBundle(assetManifest); final 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); }); }, ); }