1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:convert'; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | import 'package:flutter/painting.dart'; |
9 | import 'package:flutter/services.dart'; |
10 | import 'package:flutter_test/flutter_test.dart'; |
11 | |
12 | class TestAssetBundle extends CachingAssetBundle { |
13 | Map<String, int> loadCallCount = <String, int>{}; |
14 | |
15 | @override |
16 | Future<ByteData> load(String key) async { |
17 | loadCallCount[key] = (loadCallCount[key] ?? 0) + 1; |
18 | if (key == 'AssetManifest.json' ) { |
19 | return ByteData.sublistView(utf8.encode('{"one": ["one"]}' )); |
20 | } |
21 | |
22 | if (key == 'AssetManifest.bin' ) { |
23 | return const StandardMessageCodec().encodeMessage(<String, Object>{'one' : <Object>[]})!; |
24 | } |
25 | |
26 | if (key == 'AssetManifest.bin.json' ) { |
27 | // Encode the manifest data that will be used by the app |
28 | final ByteData data = |
29 | const StandardMessageCodec().encodeMessage(<String, Object>{'one' : <Object>[]})!; |
30 | // Simulate the behavior of NetworkAssetBundle.load here, for web tests |
31 | return ByteData.sublistView( |
32 | utf8.encode( |
33 | json.encode( |
34 | base64.encode( |
35 | // Encode only the actual bytes of the buffer, and no more... |
36 | data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes), |
37 | ), |
38 | ), |
39 | ), |
40 | ); |
41 | } |
42 | |
43 | if (key == 'counter' ) { |
44 | return ByteData.sublistView(utf8.encode(loadCallCount[key]!.toString())); |
45 | } |
46 | |
47 | if (key == 'one' ) { |
48 | return ByteData(1)..setInt8(0, 49); |
49 | } |
50 | |
51 | throw FlutterError('key not found' ); |
52 | } |
53 | } |
54 | |
55 | void main() { |
56 | TestWidgetsFlutterBinding.ensureInitialized(); |
57 | |
58 | test('Caching asset bundle test' , () async { |
59 | final TestAssetBundle bundle = TestAssetBundle(); |
60 | |
61 | final ByteData assetData = await bundle.load('one' ); |
62 | expect(assetData.getInt8(0), equals(49)); |
63 | |
64 | expect(bundle.loadCallCount['one' ], 1); |
65 | |
66 | final String assetString = await bundle.loadString('one' ); |
67 | expect(assetString, equals('1' )); |
68 | |
69 | expect(bundle.loadCallCount['one' ], 2); |
70 | |
71 | late Object loadException; |
72 | try { |
73 | await bundle.loadString('foo' ); |
74 | } catch (e) { |
75 | loadException = e; |
76 | } |
77 | expect(loadException, isFlutterError); |
78 | }); |
79 | |
80 | group('CachingAssetBundle caching behavior' , () { |
81 | test( |
82 | 'caches results for loadString, loadStructuredData, and loadBinaryStructuredData' , |
83 | () async { |
84 | final TestAssetBundle bundle = TestAssetBundle(); |
85 | |
86 | final String firstLoadStringResult = await bundle.loadString('counter' ); |
87 | final String secondLoadStringResult = await bundle.loadString('counter' ); |
88 | expect(firstLoadStringResult, '1' ); |
89 | expect(secondLoadStringResult, '1' ); |
90 | |
91 | final String firstLoadStructuredDataResult = await bundle.loadStructuredData( |
92 | 'AssetManifest.json' , |
93 | (String value) => Future<String>.value('one' ), |
94 | ); |
95 | final String secondLoadStructuredDataResult = await bundle.loadStructuredData( |
96 | 'AssetManifest.json' , |
97 | (String value) => Future<String>.value('two' ), |
98 | ); |
99 | expect(firstLoadStructuredDataResult, 'one' ); |
100 | expect(secondLoadStructuredDataResult, 'one' ); |
101 | |
102 | final String firstLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( |
103 | 'AssetManifest.bin' , |
104 | (ByteData value) => Future<String>.value('one' ), |
105 | ); |
106 | final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( |
107 | 'AssetManifest.bin' , |
108 | (ByteData value) => Future<String>.value('two' ), |
109 | ); |
110 | expect(firstLoadStructuredBinaryDataResult, 'one' ); |
111 | expect(secondLoadStructuredBinaryDataResult, 'one' ); |
112 | }, |
113 | ); |
114 | |
115 | test("clear clears all cached values'" , () async { |
116 | final TestAssetBundle bundle = TestAssetBundle(); |
117 | |
118 | await bundle.loadString('counter' ); |
119 | bundle.clear(); |
120 | final String secondLoadStringResult = await bundle.loadString('counter' ); |
121 | expect(secondLoadStringResult, '2' ); |
122 | |
123 | await bundle.loadStructuredData( |
124 | 'AssetManifest.json' , |
125 | (String value) => Future<String>.value('one' ), |
126 | ); |
127 | bundle.clear(); |
128 | final String secondLoadStructuredDataResult = await bundle.loadStructuredData( |
129 | 'AssetManifest.json' , |
130 | (String value) => Future<String>.value('two' ), |
131 | ); |
132 | expect(secondLoadStructuredDataResult, 'two' ); |
133 | |
134 | await bundle.loadStructuredBinaryData( |
135 | 'AssetManifest.bin' , |
136 | (ByteData value) => Future<String>.value('one' ), |
137 | ); |
138 | bundle.clear(); |
139 | final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( |
140 | 'AssetManifest.bin' , |
141 | (ByteData value) => Future<String>.value('two' ), |
142 | ); |
143 | expect(secondLoadStructuredBinaryDataResult, 'two' ); |
144 | }); |
145 | |
146 | test('evict evicts a particular key from the cache' , () async { |
147 | final TestAssetBundle bundle = TestAssetBundle(); |
148 | |
149 | await bundle.loadString('counter' ); |
150 | bundle.evict('counter' ); |
151 | final String secondLoadStringResult = await bundle.loadString('counter' ); |
152 | expect(secondLoadStringResult, '2' ); |
153 | |
154 | await bundle.loadStructuredData( |
155 | 'AssetManifest.json' , |
156 | (String value) => Future<String>.value('one' ), |
157 | ); |
158 | bundle.evict('AssetManifest.json' ); |
159 | final String secondLoadStructuredDataResult = await bundle.loadStructuredData( |
160 | 'AssetManifest.json' , |
161 | (String value) => Future<String>.value('two' ), |
162 | ); |
163 | expect(secondLoadStructuredDataResult, 'two' ); |
164 | |
165 | await bundle.loadStructuredBinaryData( |
166 | 'AssetManifest.bin' , |
167 | (ByteData value) => Future<String>.value('one' ), |
168 | ); |
169 | bundle.evict('AssetManifest.bin' ); |
170 | final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData( |
171 | 'AssetManifest.bin' , |
172 | (ByteData value) => Future<String>.value('two' ), |
173 | ); |
174 | expect(secondLoadStructuredBinaryDataResult, 'two' ); |
175 | }); |
176 | |
177 | test( |
178 | 'for a given key, subsequent loadStructuredData calls are synchronous after the first call resolves' , |
179 | () async { |
180 | final TestAssetBundle bundle = TestAssetBundle(); |
181 | await bundle.loadStructuredData('one' , (String data) => SynchronousFuture<int>(1)); |
182 | final Future<int> data = bundle.loadStructuredData( |
183 | 'one' , |
184 | (String data) => SynchronousFuture<int>(2), |
185 | ); |
186 | expect(data, isA<SynchronousFuture<int>>()); |
187 | expect(await data, 1); |
188 | }, |
189 | ); |
190 | |
191 | test( |
192 | 'for a given key, subsequent loadStructuredBinaryData calls are synchronous after the first call resolves' , |
193 | () async { |
194 | final TestAssetBundle bundle = TestAssetBundle(); |
195 | await bundle.loadStructuredBinaryData('one' , (ByteData data) => 1); |
196 | final Future<int> data = bundle.loadStructuredBinaryData('one' , (ByteData data) => 2); |
197 | expect(data, isA<SynchronousFuture<int>>()); |
198 | expect(await data, 1); |
199 | }, |
200 | ); |
201 | |
202 | testWidgets('loadStructuredData handles exceptions correctly' , (WidgetTester tester) async { |
203 | final TestAssetBundle bundle = TestAssetBundle(); |
204 | try { |
205 | await bundle.loadStructuredData( |
206 | 'AssetManifest.json' , |
207 | (String value) => Future<String>.error('what do they say?' ), |
208 | ); |
209 | fail('expected exception did not happen' ); |
210 | } catch (e) { |
211 | expect(e.toString(), contains('what do they say?' )); |
212 | } |
213 | }); |
214 | |
215 | testWidgets('loadStructuredBinaryData handles exceptions correctly' , ( |
216 | WidgetTester tester, |
217 | ) async { |
218 | final TestAssetBundle bundle = TestAssetBundle(); |
219 | try { |
220 | await bundle.loadStructuredBinaryData( |
221 | 'AssetManifest.bin' , |
222 | (ByteData value) => Future<String>.error('buy more crystals' ), |
223 | ); |
224 | fail('expected exception did not happen' ); |
225 | } catch (e) { |
226 | expect(e.toString(), contains('buy more crystals' )); |
227 | } |
228 | }); |
229 | }); |
230 | |
231 | test('AssetImage.obtainKey succeeds with ImageConfiguration.empty' , () async { |
232 | // This is a regression test for https://github.com/flutter/flutter/issues/12392 |
233 | final AssetImage assetImage = AssetImage('one' , bundle: TestAssetBundle()); |
234 | final AssetBundleImageKey key = await assetImage.obtainKey(ImageConfiguration.empty); |
235 | expect(key.name, 'one' ); |
236 | expect(key.scale, 1.0); |
237 | }); |
238 | |
239 | test('NetworkAssetBundle control test' , () async { |
240 | final Uri uri = Uri.http('example.org' , '/path' ); |
241 | final NetworkAssetBundle bundle = NetworkAssetBundle(uri); |
242 | late FlutterError error; |
243 | try { |
244 | await bundle.load('key' ); |
245 | } on FlutterError catch (e) { |
246 | error = e; |
247 | } |
248 | expect(error, isNotNull); |
249 | expect(error.diagnostics.length, 2); |
250 | expect(error.diagnostics.last, isA<IntProperty>()); |
251 | expect( |
252 | error.toStringDeep(), |
253 | 'FlutterError\n' |
254 | ' Unable to load asset: "key".\n' |
255 | ' HTTP status code: 400\n' , |
256 | ); |
257 | }, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998 |
258 | |
259 | test('toString works as intended' , () { |
260 | final Uri uri = Uri.http('example.org' , '/path' ); |
261 | final NetworkAssetBundle bundle = NetworkAssetBundle(uri); |
262 | |
263 | expect(bundle.toString(), 'NetworkAssetBundle# ${shortHash(bundle)}( $uri)' ); |
264 | }, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998 |
265 | |
266 | test('Throws expected exceptions when loading not exists asset' , () async { |
267 | late final FlutterError error; |
268 | try { |
269 | await rootBundle.load('not-exists' ); |
270 | } on FlutterError catch (e) { |
271 | error = e; |
272 | } |
273 | expect( |
274 | error.message, |
275 | equals( |
276 | 'Unable to load asset: "not-exists".\n' |
277 | 'The asset does not exist or has empty data.' , |
278 | ), |
279 | ); |
280 | }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314 |
281 | |
282 | test('loadStructuredBinaryData correctly loads ByteData' , () async { |
283 | final TestAssetBundle bundle = TestAssetBundle(); |
284 | final Map<Object?, Object?> assetManifest = await bundle.loadStructuredBinaryData( |
285 | 'AssetManifest.bin' , |
286 | (ByteData data) => const StandardMessageCodec().decodeMessage(data) as Map<Object?, Object?>, |
287 | ); |
288 | expect(assetManifest.keys.toList(), equals(<String>['one' ])); |
289 | expect(assetManifest['one' ], <Object>[]); |
290 | }); |
291 | } |
292 | |