// 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 '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 { Map loadCallCount = {}; @override Future load(String key) async { loadCallCount[key] = (loadCallCount[key] ?? 0) + 1; if (key == 'AssetManifest.json') { return ByteData.sublistView(utf8.encode('{"one": ["one"]}')); } if (key == 'AssetManifest.bin') { return const StandardMessageCodec().encodeMessage({'one': []})!; } if (key == 'AssetManifest.bin.json') { // Encode the manifest data that will be used by the app final ByteData data = const StandardMessageCodec().encodeMessage({ 'one': [], })!; // 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), ), ), ), ); } if (key == 'counter') { return ByteData.sublistView(utf8.encode(loadCallCount[key]!.toString())); } if (key == 'one') { return ByteData(1)..setInt8(0, 49); } throw FlutterError('key not found'); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('Caching asset bundle test', () async { final bundle = TestAssetBundle(); final ByteData assetData = await bundle.load('one'); expect(assetData.getInt8(0), equals(49)); expect(bundle.loadCallCount['one'], 1); final String assetString = await bundle.loadString('one'); expect(assetString, equals('1')); expect(bundle.loadCallCount['one'], 2); late Object loadException; try { await bundle.loadString('foo'); } catch (e) { loadException = e; } expect(loadException, isFlutterError); }); group('CachingAssetBundle caching behavior', () { test( 'caches results for loadString, loadStructuredData, and loadBinaryStructuredData', () async { final bundle = TestAssetBundle(); final String firstLoadStringResult = await bundle.loadString('counter'); final String secondLoadStringResult = await bundle.loadString('counter'); expect(firstLoadStringResult, '1'); expect(secondLoadStringResult, '1'); final String firstLoadStructuredDataResult = await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.value('one'), ); final String secondLoadStructuredDataResult = await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.value('two'), ); expect(firstLoadStructuredDataResult, 'one'); expect(secondLoadStructuredDataResult, 'one'); final String firstLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.value('one'), ); final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.value('two'), ); expect(firstLoadStructuredBinaryDataResult, 'one'); expect(secondLoadStructuredBinaryDataResult, 'one'); }, ); test("clear clears all cached values'", () async { final bundle = TestAssetBundle(); await bundle.loadString('counter'); bundle.clear(); final String secondLoadStringResult = await bundle.loadString('counter'); expect(secondLoadStringResult, '2'); await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.value('one'), ); bundle.clear(); final String secondLoadStructuredDataResult = await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.value('two'), ); expect(secondLoadStructuredDataResult, 'two'); await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.value('one'), ); bundle.clear(); final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.value('two'), ); expect(secondLoadStructuredBinaryDataResult, 'two'); }); test('evict evicts a particular key from the cache', () async { final bundle = TestAssetBundle(); await bundle.loadString('counter'); bundle.evict('counter'); final String secondLoadStringResult = await bundle.loadString('counter'); expect(secondLoadStringResult, '2'); await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.value('one'), ); bundle.evict('AssetManifest.json'); final String secondLoadStructuredDataResult = await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.value('two'), ); expect(secondLoadStructuredDataResult, 'two'); await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.value('one'), ); bundle.evict('AssetManifest.bin'); final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.value('two'), ); expect(secondLoadStructuredBinaryDataResult, 'two'); }); test( 'for a given key, subsequent loadStructuredData calls are synchronous after the first call resolves', () async { final bundle = TestAssetBundle(); await bundle.loadStructuredData('one', (String data) => SynchronousFuture(1)); final Future data = bundle.loadStructuredData( 'one', (String data) => SynchronousFuture(2), ); expect(data, isA>()); expect(await data, 1); }, ); test( 'for a given key, subsequent loadStructuredBinaryData calls are synchronous after the first call resolves', () async { final bundle = TestAssetBundle(); await bundle.loadStructuredBinaryData('one', (ByteData data) => 1); final Future data = bundle.loadStructuredBinaryData('one', (ByteData data) => 2); expect(data, isA>()); expect(await data, 1); }, ); testWidgets('loadStructuredData handles exceptions correctly', (WidgetTester tester) async { final bundle = TestAssetBundle(); try { await bundle.loadStructuredData( 'AssetManifest.json', (String value) => Future.error('what do they say?'), ); fail('expected exception did not happen'); } catch (e) { expect(e.toString(), contains('what do they say?')); } }); testWidgets('loadStructuredBinaryData handles exceptions correctly', ( WidgetTester tester, ) async { final bundle = TestAssetBundle(); try { await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData value) => Future.error('buy more crystals'), ); fail('expected exception did not happen'); } catch (e) { expect(e.toString(), contains('buy more crystals')); } }); }); test('AssetImage.obtainKey succeeds with ImageConfiguration.empty', () async { // This is a regression test for https://github.com/flutter/flutter/issues/12392 final assetImage = AssetImage('one', bundle: TestAssetBundle()); final AssetBundleImageKey key = await assetImage.obtainKey(ImageConfiguration.empty); expect(key.name, 'one'); expect(key.scale, 1.0); }); test('NetworkAssetBundle control test', () async { final uri = Uri.http('example.org', '/path'); final bundle = NetworkAssetBundle(uri); late FlutterError error; try { await bundle.load('key'); } on FlutterError catch (e) { error = e; } expect(error, isNotNull); expect(error.diagnostics.length, 2); expect(error.diagnostics.last, isA()); expect( error.toStringDeep(), 'FlutterError\n' ' Unable to load asset: "key".\n' ' HTTP status code: 400\n', ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998 test('toString works as intended', () { final uri = Uri.http('example.org', '/path'); final bundle = NetworkAssetBundle(uri); expect(bundle.toString(), 'NetworkAssetBundle#${shortHash(bundle)}($uri)'); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998 test('Throws expected exceptions when loading not exists asset', () async { late final FlutterError error; try { await rootBundle.load('not-exists'); } on FlutterError catch (e) { error = e; } expect( error.message, equals( 'Unable to load asset: "not-exists".\n' 'The asset does not exist or has empty data.', ), ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314 test('loadStructuredBinaryData correctly loads ByteData', () async { final bundle = TestAssetBundle(); final Map assetManifest = await bundle.loadStructuredBinaryData( 'AssetManifest.bin', (ByteData data) => const StandardMessageCodec().decodeMessage(data) as Map, ); expect(assetManifest.keys.toList(), equals(['one'])); expect(assetManifest['one'], []); }); }