Fix silent test failure in image cache tests (#56492)
diff --git a/packages/flutter/lib/src/painting/image_cache.dart b/packages/flutter/lib/src/painting/image_cache.dart
index 24d894f..dbdf7b0 100644
--- a/packages/flutter/lib/src/painting/image_cache.dart
+++ b/packages/flutter/lib/src/painting/image_cache.dart
@@ -286,10 +286,9 @@
}
}
- void _trackLiveImage(Object key, _LiveImage image, { bool debugPutOk = true }) {
+ void _trackLiveImage(Object key, _LiveImage image) {
// Avoid adding unnecessary callbacks to the completer.
_liveImages.putIfAbsent(key, () {
- assert(debugPutOk);
// Even if no callers to ImageProvider.resolve have listened to the stream,
// the cache is listening to the stream and will remove itself once the
// image completes to move it from pending to keepAlive.
@@ -400,10 +399,6 @@
imageSize,
() => _liveImages.remove(key),
),
- // This should result in a put if `loader()` above executed
- // synchronously, in which case syncCall is true and we arrived here
- // before we got a chance to track the image otherwise.
- debugPutOk: syncCall,
);
final _PendingImage pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart
index e75a582..c6ffb7f 100644
--- a/packages/flutter/lib/src/painting/image_provider.dart
+++ b/packages/flutter/lib/src/painting/image_provider.dart
@@ -347,10 +347,10 @@
stack: stack,
context: ErrorDescription('while resolving an image'),
silent: true, // could be a network error or whatnot
- informationCollector: collector
- );
- },
- );
+ informationCollector: collector,
+ );
+ },
+ );
return stream;
}
diff --git a/packages/flutter/test/painting/image_cache_clearing_test.dart b/packages/flutter/test/painting/image_cache_clearing_test.dart
new file mode 100644
index 0000000..beb913b
--- /dev/null
+++ b/packages/flutter/test/painting/image_cache_clearing_test.dart
@@ -0,0 +1,31 @@
+// 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:async';
+import 'dart:typed_data';
+
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+
+void main() {
+ TestRenderingFlutterBinding();
+
+ test('Clearing images while they\'re pending does not crash', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage memoryImage = MemoryImage(bytes);
+ final ImageStream stream = memoryImage.resolve(ImageConfiguration.empty);
+ final Completer<void> completer = Completer<void>();
+ FlutterError.onError = (FlutterErrorDetails error) { completer.completeError(error.exception, error.stack); };
+ stream.addListener(ImageStreamListener(
+ (ImageInfo image, bool synchronousCall) {
+ completer.complete();
+ }
+ ));
+ imageCache.clearLiveImages();
+ await completer.future;
+ });
+}
diff --git a/packages/flutter/test/painting/image_cache_resize_test.dart b/packages/flutter/test/painting/image_cache_resize_test.dart
index eab9291..867ff4c 100644
--- a/packages/flutter/test/painting/image_cache_resize_test.dart
+++ b/packages/flutter/test/painting/image_cache_resize_test.dart
@@ -9,71 +9,70 @@
import 'mocks_for_image_cache.dart';
void main() {
- TestRenderingFlutterBinding(); // initializes the imageCache
- group(ImageCache, () {
- tearDown(() {
- imageCache.clear();
- imageCache.maximumSize = 1000;
- imageCache.maximumSizeBytes = 10485760;
- });
+ TestRenderingFlutterBinding();
- test('Image cache resizing based on count', () async {
- imageCache.maximumSize = 2;
+ tearDown(() {
+ imageCache.clear();
+ imageCache.maximumSize = 1000;
+ imageCache.maximumSizeBytes = 10485760;
+ });
- final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
- final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
- final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
- final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(a.value, equals(1));
- expect(b.value, equals(2));
- expect(c.value, equals(3));
- expect(d.value, equals(4));
+ test('Image cache resizing based on count', () async {
+ imageCache.maximumSize = 2;
- imageCache.maximumSize = 0;
+ final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(a.value, equals(1));
+ expect(b.value, equals(2));
+ expect(c.value, equals(3));
+ expect(d.value, equals(4));
- final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(e.value, equals(5));
+ imageCache.maximumSize = 0;
- final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(f.value, equals(6));
+ final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(e.value, equals(5));
- imageCache.maximumSize = 3;
+ final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(f.value, equals(6));
- final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(g.value, equals(7));
+ imageCache.maximumSize = 3;
- final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(h.value, equals(7));
- });
+ final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(g.value, equals(7));
- test('Image cache resizing based on size', () async {
- const TestImage testImage = TestImage(width: 8, height: 8); // 256 B.
- imageCache.maximumSizeBytes = 256 * 2;
+ final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(h.value, equals(7));
+ });
- final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(a.value, equals(1));
- expect(b.value, equals(2));
- expect(c.value, equals(3));
- expect(d.value, equals(4));
+ test('Image cache resizing based on size', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8); // 256 B.
+ imageCache.maximumSizeBytes = 256 * 2;
- imageCache.maximumSizeBytes = 0;
+ final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(a.value, equals(1));
+ expect(b.value, equals(2));
+ expect(c.value, equals(3));
+ expect(d.value, equals(4));
- final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(e.value, equals(5));
+ imageCache.maximumSizeBytes = 0;
- final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(f.value, equals(6));
+ final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(e.value, equals(5));
- imageCache.maximumSizeBytes = 256 * 3;
+ final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(f.value, equals(6));
- final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(g.value, equals(7));
+ imageCache.maximumSizeBytes = 256 * 3;
- final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(h.value, equals(7));
- });
+ final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(g.value, equals(7));
+
+ final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(h.value, equals(7));
});
}
diff --git a/packages/flutter/test/painting/image_cache_test.dart b/packages/flutter/test/painting/image_cache_test.dart
index ad72ade..1ddba9b 100644
--- a/packages/flutter/test/painting/image_cache_test.dart
+++ b/packages/flutter/test/painting/image_cache_test.dart
@@ -9,475 +9,471 @@
import 'mocks_for_image_cache.dart';
void main() {
- group('ImageCache', () {
- setUpAll(() {
- TestRenderingFlutterBinding(); // initializes the imageCache
+ TestRenderingFlutterBinding();
+
+ tearDown(() {
+ imageCache.clear();
+ imageCache.clearLiveImages();
+ imageCache.maximumSize = 1000;
+ imageCache.maximumSizeBytes = 10485760;
+ });
+
+ test('maintains cache size', () async {
+ imageCache.maximumSize = 3;
+
+ final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(a.value, equals(1));
+ final TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(b.value, equals(1));
+ final TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(c.value, equals(1));
+ final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(d.value, equals(1));
+ final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(e.value, equals(1));
+ final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(f.value, equals(1));
+
+ expect(f, equals(a));
+
+ // cache still only has one entry in it: 1(1)
+
+ final TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(g.value, equals(7));
+
+ // cache has two entries in it: 1(1), 2(7)
+
+ final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(h.value, equals(1));
+
+ // cache still has two entries in it: 2(7), 1(1)
+
+ final TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(i.value, equals(9));
+
+ // cache has three entries in it: 2(7), 1(1), 3(9)
+
+ final TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(j.value, equals(1));
+
+ // cache still has three entries in it: 2(7), 3(9), 1(1)
+
+ final TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(k.value, equals(11));
+
+ // cache has three entries: 3(9), 1(1), 4(11)
+
+ final TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(l.value, equals(1));
+
+ // cache has three entries: 3(9), 4(11), 1(1)
+
+ final TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(m.value, equals(13));
+
+ // cache has three entries: 4(11), 1(1), 2(13)
+
+ final TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(n.value, equals(14));
+
+ // cache has three entries: 1(1), 2(13), 3(14)
+
+ final TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(o.value, equals(15));
+
+ // cache has three entries: 2(13), 3(14), 4(15)
+
+ final TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty)) as TestImageInfo;
+ expect(p.value, equals(16));
+
+ // cache has three entries: 3(14), 4(15), 1(16)
+ });
+
+ test('clear removes all images and resets cache size', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
+
+ expect(imageCache.currentSize, 0);
+ expect(imageCache.currentSizeBytes, 0);
+
+ await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
+ await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+
+ expect(imageCache.currentSize, 2);
+ expect(imageCache.currentSizeBytes, 256 * 2);
+
+ imageCache.clear();
+
+ expect(imageCache.currentSize, 0);
+ expect(imageCache.currentSizeBytes, 0);
+ });
+
+ test('evicts individual images', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
+ await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
+ await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+
+ expect(imageCache.currentSize, 2);
+ expect(imageCache.currentSizeBytes, 256 * 2);
+ expect(imageCache.evict(1), true);
+ expect(imageCache.currentSize, 1);
+ expect(imageCache.currentSizeBytes, 256);
+ });
+
+ test('Do not cache large images', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
+
+ imageCache.maximumSizeBytes = 1;
+ await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
+ expect(imageCache.currentSize, 0);
+ expect(imageCache.currentSizeBytes, 0);
+ expect(imageCache.maximumSizeBytes, 1);
+ });
+
+ test('Returns null if an error is caught resolving an image', () {
+ final ErrorImageProvider errorImage = ErrorImageProvider();
+ expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null)), throwsA(isA<Error>()));
+ bool caughtError = false;
+ final ImageStreamCompleter result = imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null), onError: (dynamic error, StackTrace stackTrace) {
+ caughtError = true;
});
+ expect(result, null);
+ expect(caughtError, true);
+ });
- tearDown(() {
- imageCache.clear();
- imageCache.clearLiveImages();
- imageCache.maximumSize = 1000;
- imageCache.maximumSizeBytes = 10485760;
- });
+ test('already pending image is returned when it is put into the cache again', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- test('maintains cache size', () async {
- imageCache.maximumSize = 3;
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
+ final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
- final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(a.value, equals(1));
- final TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(b.value, equals(1));
- final TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(c.value, equals(1));
- final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(d.value, equals(1));
- final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(e.value, equals(1));
- final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(f.value, equals(1));
+ final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+ return completer1;
+ }) as TestImageStreamCompleter;
+ final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
+ return completer2;
+ }) as TestImageStreamCompleter;
- expect(f, equals(a));
+ expect(resultingCompleter1, completer1);
+ expect(resultingCompleter2, completer1);
+ });
- // cache still only has one entry in it: 1(1)
+ test('pending image is removed when cache is cleared', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(g.value, equals(7));
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
+ final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
- // cache has two entries in it: 1(1), 2(7)
+ final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+ return completer1;
+ }) as TestImageStreamCompleter;
- final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(h.value, equals(1));
+ expect(imageCache.statusForKey(testImage).pending, true);
+ expect(imageCache.statusForKey(testImage).live, true);
+ imageCache.clear();
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ imageCache.clearLiveImages();
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, false);
- // cache still has two entries in it: 2(7), 1(1)
+ final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
+ return completer2;
+ }) as TestImageStreamCompleter;
- final TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(i.value, equals(9));
+ expect(resultingCompleter1, completer1);
+ expect(resultingCompleter2, completer2);
+ });
- // cache has three entries in it: 2(7), 1(1), 3(9)
+ test('pending image is removed when image is evicted', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(j.value, equals(1));
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
+ final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
- // cache still has three entries in it: 2(7), 3(9), 1(1)
+ final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+ return completer1;
+ }) as TestImageStreamCompleter;
- final TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(k.value, equals(11));
+ imageCache.evict(testImage);
- // cache has three entries: 3(9), 1(1), 4(11)
+ final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
+ return completer2;
+ }) as TestImageStreamCompleter;
- final TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(l.value, equals(1));
+ expect(resultingCompleter1, completer1);
+ expect(resultingCompleter2, completer2);
+ });
- // cache has three entries: 3(9), 4(11), 1(1)
+ test("failed image can successfully be removed from the cache's pending images", () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(m.value, equals(13));
+ const FailingTestImageProvider(1, 1, image: testImage)
+ .resolve(ImageConfiguration.empty)
+ .addListener(ImageStreamListener(
+ (ImageInfo image, bool synchronousCall) { },
+ onError: (dynamic exception, StackTrace stackTrace) {
+ final bool evicationResult = imageCache.evict(1);
+ expect(evicationResult, isTrue);
+ },
+ ));
+ });
- // cache has three entries: 4(11), 1(1), 2(13)
+ test('containsKey - pending', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(n.value, equals(14));
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- // cache has three entries: 1(1), 2(13), 3(14)
+ final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+ return completer1;
+ }) as TestImageStreamCompleter;
- final TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(o.value, equals(15));
+ expect(resultingCompleter1, completer1);
+ expect(imageCache.containsKey(testImage), true);
+ });
- // cache has three entries: 2(13), 3(14), 4(15)
+ test('containsKey - completed', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty)) as TestImageInfo;
- expect(p.value, equals(16));
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- // cache has three entries: 3(14), 4(15), 1(16)
- });
+ final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+ return completer1;
+ }) as TestImageStreamCompleter;
- test('clear removes all images and resets cache size', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ // Mark as complete
+ completer1.testSetImage(testImage);
- expect(imageCache.currentSize, 0);
- expect(imageCache.currentSizeBytes, 0);
+ expect(resultingCompleter1, completer1);
+ expect(imageCache.containsKey(testImage), true);
+ });
- await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
- await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+ test('putIfAbsent updates LRU properties of a live image', () async {
+ imageCache.maximumSize = 1;
+ const TestImage testImage = TestImage(width: 8, height: 8);
+ const TestImage testImage2 = TestImage(width: 10, height: 10);
- expect(imageCache.currentSize, 2);
- expect(imageCache.currentSizeBytes, 256 * 2);
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter()..testSetImage(testImage);
+ final TestImageStreamCompleter completer2 = TestImageStreamCompleter()..testSetImage(testImage2);
- imageCache.clear();
+ completer1.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
- expect(imageCache.currentSize, 0);
- expect(imageCache.currentSizeBytes, 0);
- });
+ final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+ return completer1;
+ }) as TestImageStreamCompleter;
- test('evicts individual images', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
- await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
- await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage2).untracked, true);
+ final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage2, () {
+ return completer2;
+ }) as TestImageStreamCompleter;
- expect(imageCache.currentSize, 2);
- expect(imageCache.currentSizeBytes, 256 * 2);
- expect(imageCache.evict(1), true);
- expect(imageCache.currentSize, 1);
- expect(imageCache.currentSizeBytes, 256);
- });
- test('Do not cache large images', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).keepAlive, false); // evicted
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage2).pending, false);
+ expect(imageCache.statusForKey(testImage2).keepAlive, true); // took the LRU spot.
+ expect(imageCache.statusForKey(testImage2).live, false); // no listeners
- imageCache.maximumSizeBytes = 1;
- await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
- expect(imageCache.currentSize, 0);
- expect(imageCache.currentSizeBytes, 0);
- expect(imageCache.maximumSizeBytes, 1);
- });
+ expect(resultingCompleter1, completer1);
+ expect(resultingCompleter2, completer2);
+ });
- test('Returns null if an error is caught resolving an image', () {
- final ErrorImageProvider errorImage = ErrorImageProvider();
- expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null)), throwsA(isA<Error>()));
- bool caughtError = false;
- final ImageStreamCompleter result = imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null), onError: (dynamic error, StackTrace stackTrace) {
- caughtError = true;
- });
- expect(result, null);
- expect(caughtError, true);
- });
+ test('Live image cache avoids leaks of unlistened streams', () async {
+ imageCache.maximumSize = 3;
- test('already pending image is returned when it is put into the cache again', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
+ const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
+ const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
+ const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
+ const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
+ const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
+ // wait an event loop to let image resolution process.
+ await null;
- final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
- return completer1;
- }) as TestImageStreamCompleter;
- final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
- return completer2;
- }) as TestImageStreamCompleter;
+ expect(imageCache.currentSize, 3);
+ expect(imageCache.liveImageCount, 0);
+ });
- expect(resultingCompleter1, completer1);
- expect(resultingCompleter2, completer1);
- });
+ test('Disabled image cache does not leak live images', () async {
+ imageCache.maximumSize = 0;
- test('pending image is removed when cache is cleared', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
+ const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
+ const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
+ const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
+ const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
+ const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
+ // wait an event loop to let image resolution process.
+ await null;
- final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
- return completer1;
- }) as TestImageStreamCompleter;
+ expect(imageCache.currentSize, 0);
+ expect(imageCache.liveImageCount, 0);
+ });
- expect(imageCache.statusForKey(testImage).pending, true);
- expect(imageCache.statusForKey(testImage).live, true);
- imageCache.clear();
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- imageCache.clearLiveImages();
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, false);
+ test('Evicting a pending image clears the live image by default', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
- return completer2;
- }) as TestImageStreamCompleter;
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- expect(resultingCompleter1, completer1);
- expect(resultingCompleter2, completer2);
- });
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, true);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
- test('pending image is removed when image is evicted', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ imageCache.evict(testImage);
+ expect(imageCache.statusForKey(testImage).untracked, true);
+ });
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
+ test('Evicting a pending image does clear the live image when includeLive is false and only cache listening', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
- return completer1;
- }) as TestImageStreamCompleter;
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- imageCache.evict(testImage);
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, true);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
- final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
- return completer2;
- }) as TestImageStreamCompleter;
+ imageCache.evict(testImage, includeLive: false);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, false);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
+ });
- expect(resultingCompleter1, completer1);
- expect(resultingCompleter2, completer2);
- });
+ test('Evicting a pending image does clear the live image when includeLive is false and some other listener', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- test("failed image can successfully be removed from the cache's pending images", () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
- const FailingTestImageProvider(1, 1, image: testImage)
- .resolve(ImageConfiguration.empty)
- .addListener(ImageStreamListener(
- (ImageInfo image, bool synchronousCall) { },
- onError: (dynamic exception, StackTrace stackTrace) {
- final bool evicationResult = imageCache.evict(1);
- expect(evicationResult, isTrue);
- },
- ));
- });
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, true);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
- test('containsKey - pending', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
-
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
- final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
- return completer1;
- }) as TestImageStreamCompleter;
-
- expect(resultingCompleter1, completer1);
- expect(imageCache.containsKey(testImage), true);
- });
-
- test('containsKey - completed', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
-
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
- final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
- return completer1;
- }) as TestImageStreamCompleter;
-
- // Mark as complete
- completer1.testSetImage(testImage);
-
- expect(resultingCompleter1, completer1);
- expect(imageCache.containsKey(testImage), true);
- });
-
- test('putIfAbsent updates LRU properties of a live image', () async {
- imageCache.maximumSize = 1;
- const TestImage testImage = TestImage(width: 8, height: 8);
- const TestImage testImage2 = TestImage(width: 10, height: 10);
-
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter()..testSetImage(testImage);
- final TestImageStreamCompleter completer2 = TestImageStreamCompleter()..testSetImage(testImage2);
-
- completer1.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
-
- final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
- return completer1;
- }) as TestImageStreamCompleter;
-
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage2).untracked, true);
- final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage2, () {
- return completer2;
- }) as TestImageStreamCompleter;
-
-
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).keepAlive, false); // evicted
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage2).pending, false);
- expect(imageCache.statusForKey(testImage2).keepAlive, true); // took the LRU spot.
- expect(imageCache.statusForKey(testImage2).live, false); // no listeners
-
- expect(resultingCompleter1, completer1);
- expect(resultingCompleter2, completer2);
- });
-
- test('Live image cache avoids leaks of unlistened streams', () async {
- imageCache.maximumSize = 3;
-
- const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
- const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
- const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
- const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
- const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
- const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
-
- // wait an event loop to let image resolution process.
- await null;
-
- expect(imageCache.currentSize, 3);
- expect(imageCache.liveImageCount, 0);
- });
-
- test('Disabled image cache does not leak live images', () async {
- imageCache.maximumSize = 0;
-
- const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
- const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
- const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
- const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
- const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
- const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
-
- // wait an event loop to let image resolution process.
- await null;
-
- expect(imageCache.currentSize, 0);
- expect(imageCache.liveImageCount, 0);
- });
-
- test('Evicting a pending image clears the live image by default', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
-
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, true);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
-
- imageCache.evict(testImage);
- expect(imageCache.statusForKey(testImage).untracked, true);
- });
-
- test('Evicting a pending image does clear the live image when includeLive is false and only cache listening', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
-
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, true);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
-
- imageCache.evict(testImage, includeLive: false);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, false);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
- });
-
- test('Evicting a pending image does clear the live image when includeLive is false and some other listener', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
-
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, true);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
-
- completer1.addListener(ImageStreamListener((_, __) {}));
- imageCache.evict(testImage, includeLive: false);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
- });
+ completer1.addListener(ImageStreamListener((_, __) {}));
+ imageCache.evict(testImage, includeLive: false);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
+ });
- test('Evicting a completed image does clear the live image by default', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ test('Evicting a completed image does clear the live image by default', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
- ..testSetImage(testImage)
- ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+ ..testSetImage(testImage)
+ ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
- imageCache.evict(testImage);
- expect(imageCache.statusForKey(testImage).untracked, true);
- });
+ imageCache.evict(testImage);
+ expect(imageCache.statusForKey(testImage).untracked, true);
+ });
- test('Evicting a completed image does not clear the live image when includeLive is set to false', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ test('Evicting a completed image does not clear the live image when includeLive is set to false', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
- ..testSetImage(testImage)
- ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+ ..testSetImage(testImage)
+ ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
- imageCache.evict(testImage, includeLive: false);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
- });
+ imageCache.evict(testImage, includeLive: false);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
+ });
- test('Clearing liveImages removes callbacks', () async {
- const TestImage testImage = TestImage(width: 8, height: 8);
+ test('Clearing liveImages removes callbacks', () async {
+ const TestImage testImage = TestImage(width: 8, height: 8);
- final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
+ final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
- ..testSetImage(testImage)
- ..addListener(listener);
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+ ..testSetImage(testImage)
+ ..addListener(listener);
- final TestImageStreamCompleter completer2 = TestImageStreamCompleter()
- ..testSetImage(testImage)
- ..addListener(listener);
+ final TestImageStreamCompleter completer2 = TestImageStreamCompleter()
+ ..testSetImage(testImage)
+ ..addListener(listener);
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
- imageCache.clear();
- imageCache.clearLiveImages();
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, false);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
+ imageCache.clear();
+ imageCache.clearLiveImages();
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, false);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
- imageCache.putIfAbsent(testImage, () => completer2);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
+ imageCache.putIfAbsent(testImage, () => completer2);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
- completer1.removeListener(listener);
+ completer1.removeListener(listener);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
- });
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
+ });
- test('Live image gets size updated', () async {
- // Add an image to the cache in pending state
- // Complete it once it is in there as live
- // Evict it but leave the live one.
- // Add it again.
- // If the live image did not track the size properly, the last line of
- // this test will fail.
+ test('Live image gets size updated', () async {
+ // Add an image to the cache in pending state
+ // Complete it once it is in there as live
+ // Evict it but leave the live one.
+ // Add it again.
+ // If the live image did not track the size properly, the last line of
+ // this test will fail.
- const TestImage testImage = TestImage(width: 8, height: 8);
- const int testImageSize = 8 * 8 * 4;
+ const TestImage testImage = TestImage(width: 8, height: 8);
+ const int testImageSize = 8 * 8 * 4;
- final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
+ final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
- final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
- ..addListener(listener);
+ final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+ ..addListener(listener);
- imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, true);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
- expect(imageCache.currentSizeBytes, 0);
+ imageCache.putIfAbsent(testImage, () => completer1);
+ expect(imageCache.statusForKey(testImage).pending, true);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
+ expect(imageCache.currentSizeBytes, 0);
- completer1.testSetImage(testImage);
+ completer1.testSetImage(testImage);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
- expect(imageCache.currentSizeBytes, testImageSize);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
+ expect(imageCache.currentSizeBytes, testImageSize);
- imageCache.evict(testImage, includeLive: false);
+ imageCache.evict(testImage, includeLive: false);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, false);
- expect(imageCache.currentSizeBytes, 0);
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, false);
+ expect(imageCache.currentSizeBytes, 0);
- imageCache.putIfAbsent(testImage, () => completer1);
+ imageCache.putIfAbsent(testImage, () => completer1);
- expect(imageCache.statusForKey(testImage).pending, false);
- expect(imageCache.statusForKey(testImage).live, true);
- expect(imageCache.statusForKey(testImage).keepAlive, true);
- expect(imageCache.currentSizeBytes, testImageSize);
- });
+ expect(imageCache.statusForKey(testImage).pending, false);
+ expect(imageCache.statusForKey(testImage).live, true);
+ expect(imageCache.statusForKey(testImage).keepAlive, true);
+ expect(imageCache.currentSizeBytes, testImageSize);
});
}
diff --git a/packages/flutter/test/painting/image_provider_and_image_cache_test.dart b/packages/flutter/test/painting/image_provider_and_image_cache_test.dart
new file mode 100644
index 0000000..53724c6
--- /dev/null
+++ b/packages/flutter/test/painting/image_provider_and_image_cache_test.dart
@@ -0,0 +1,142 @@
+// 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:async';
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+import 'mocks_for_image_cache.dart';
+
+void main() {
+ TestRenderingFlutterBinding();
+
+ final DecoderCallback _basicDecoder = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
+ return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+ };
+
+ FlutterExceptionHandler oldError;
+ setUp(() {
+ oldError = FlutterError.onError;
+ });
+
+ tearDown(() {
+ FlutterError.onError = oldError;
+ PaintingBinding.instance.imageCache.clear();
+ PaintingBinding.instance.imageCache.clearLiveImages();
+ });
+
+ tearDown(() {
+ imageCache.clear();
+ });
+
+ test('AssetImageProvider - evicts on failure to load', () async {
+ final Completer<FlutterError> error = Completer<FlutterError>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ error.complete(details.exception as FlutterError);
+ };
+
+ const ImageProvider provider = ExactAssetImage('does-not-exist');
+ final Object key = await provider.obtainKey(ImageConfiguration.empty);
+ expect(imageCache.statusForKey(provider).untracked, true);
+ expect(imageCache.pendingImageCount, 0);
+
+ provider.resolve(ImageConfiguration.empty);
+
+ expect(imageCache.statusForKey(key).pending, true);
+ expect(imageCache.pendingImageCount, 1);
+
+ await error.future;
+
+ expect(imageCache.statusForKey(provider).untracked, true);
+ expect(imageCache.pendingImageCount, 0);
+ }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314
+
+ test('AssetImageProvider - evicts on null load', () async {
+ final Completer<StateError> error = Completer<StateError>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ error.complete(details.exception as StateError);
+ };
+
+ final ImageProvider provider = ExactAssetImage('does-not-exist', bundle: _TestAssetBundle());
+ final Object key = await provider.obtainKey(ImageConfiguration.empty);
+ expect(imageCache.statusForKey(provider).untracked, true);
+ expect(imageCache.pendingImageCount, 0);
+
+ provider.resolve(ImageConfiguration.empty);
+
+ expect(imageCache.statusForKey(key).pending, true);
+ expect(imageCache.pendingImageCount, 1);
+
+ await error.future;
+
+ expect(imageCache.statusForKey(provider).untracked, true);
+ expect(imageCache.pendingImageCount, 0);
+ });
+
+ test('ImageProvider can evict images', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage imageProvider = MemoryImage(bytes);
+ final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+ final Completer<void> completer = Completer<void>();
+ stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) => completer.complete()));
+ await completer.future;
+
+ expect(imageCache.currentSize, 1);
+ expect(await MemoryImage(bytes).evict(), true);
+ expect(imageCache.currentSize, 0);
+ });
+
+ test('ImageProvider.evict respects the provided ImageCache', () async {
+ final ImageCache otherCache = ImageCache();
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage imageProvider = MemoryImage(bytes);
+ final ImageStreamCompleter cacheStream = otherCache.putIfAbsent(
+ imageProvider, () => imageProvider.load(imageProvider, _basicDecoder),
+ );
+ final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+ final Completer<void> completer = Completer<void>();
+ final Completer<void> cacheCompleter = Completer<void>();
+ stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ completer.complete();
+ }));
+ cacheStream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ cacheCompleter.complete();
+ }));
+ await Future.wait(<Future<void>>[completer.future, cacheCompleter.future]);
+
+ expect(otherCache.currentSize, 1);
+ expect(imageCache.currentSize, 1);
+ expect(await imageProvider.evict(cache: otherCache), true);
+ expect(otherCache.currentSize, 0);
+ expect(imageCache.currentSize, 1);
+ });
+
+ test('ImageProvider errors can always be caught', () async {
+ final ErrorImageProvider imageProvider = ErrorImageProvider();
+ final Completer<bool> caughtError = Completer<bool>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ caughtError.complete(false);
+ };
+ final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+ stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ caughtError.complete(false);
+ }, onError: (dynamic error, StackTrace stackTrace) {
+ caughtError.complete(true);
+ }));
+ expect(await caughtError.future, true);
+ });
+}
+
+class _TestAssetBundle extends CachingAssetBundle {
+ @override
+ Future<ByteData> load(String key) async {
+ return null;
+ }
+}
diff --git a/packages/flutter/test/painting/image_provider_network_image_test.dart b/packages/flutter/test/painting/image_provider_network_image_test.dart
new file mode 100644
index 0000000..3fda11b
--- /dev/null
+++ b/packages/flutter/test/painting/image_provider_network_image_test.dart
@@ -0,0 +1,224 @@
+// 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:async';
+import 'dart:io';
+import 'dart:math' as math;
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+
+void main() {
+ TestRenderingFlutterBinding();
+
+ final DecoderCallback _basicDecoder = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
+ return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+ };
+
+ _MockHttpClient httpClient;
+
+ setUp(() {
+ httpClient = _MockHttpClient();
+ debugNetworkImageHttpClientProvider = () => httpClient;
+ });
+
+ tearDown(() {
+ debugNetworkImageHttpClientProvider = null;
+ PaintingBinding.instance.imageCache.clear();
+ PaintingBinding.instance.imageCache.clearLiveImages();
+ });
+
+ test('Expect thrown exception with statusCode - evicts from cache', () async {
+ final int errorStatusCode = HttpStatus.notFound;
+ const String requestUrl = 'foo-url';
+
+ final _MockHttpClientRequest request = _MockHttpClientRequest();
+ final _MockHttpClientResponse response = _MockHttpClientResponse();
+ when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
+ when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
+ when(response.statusCode).thenReturn(errorStatusCode);
+
+ final Completer<dynamic> caughtError = Completer<dynamic>();
+
+ final ImageProvider imageProvider = NetworkImage(nonconst(requestUrl));
+ expect(imageCache.pendingImageCount, 0);
+ expect(imageCache.statusForKey(imageProvider).untracked, true);
+
+ final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+
+ expect(imageCache.pendingImageCount, 1);
+ expect(imageCache.statusForKey(imageProvider).pending, true);
+
+ result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ }, onError: (dynamic error, StackTrace stackTrace) {
+ caughtError.complete(error);
+ }));
+
+ final dynamic err = await caughtError.future;
+
+ expect(imageCache.pendingImageCount, 0);
+ expect(imageCache.statusForKey(imageProvider).untracked, true);
+
+ expect(
+ err,
+ isA<NetworkImageLoadException>()
+ .having((NetworkImageLoadException e) => e.statusCode, 'statusCode', errorStatusCode)
+ .having((NetworkImageLoadException e) => e.uri, 'uri', Uri.base.resolve(requestUrl)),
+ );
+ }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
+
+ test('Disallows null urls', () {
+ expect(() {
+ NetworkImage(nonconst(null));
+ }, throwsAssertionError);
+ });
+
+ test('Uses the HttpClient provided by debugNetworkImageHttpClientProvider if set', () async {
+ when(httpClient.getUrl(any)).thenThrow('client1');
+ final List<dynamic> capturedErrors = <dynamic>[];
+
+ Future<void> loadNetworkImage() async {
+ final NetworkImage networkImage = NetworkImage(nonconst('foo'));
+ final ImageStreamCompleter completer = networkImage.load(networkImage, _basicDecoder);
+ completer.addListener(ImageStreamListener(
+ (ImageInfo image, bool synchronousCall) { },
+ onError: (dynamic error, StackTrace stackTrace) {
+ capturedErrors.add(error);
+ },
+ ));
+ await Future<void>.value();
+ }
+
+ await loadNetworkImage();
+ expect(capturedErrors, <dynamic>['client1']);
+ final _MockHttpClient client2 = _MockHttpClient();
+ when(client2.getUrl(any)).thenThrow('client2');
+ debugNetworkImageHttpClientProvider = () => client2;
+ await loadNetworkImage();
+ expect(capturedErrors, <dynamic>['client1', 'client2']);
+ }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
+
+ test('Propagates http client errors during resolve()', () async {
+ when(httpClient.getUrl(any)).thenThrow(Error());
+ bool uncaught = false;
+
+ final FlutterExceptionHandler oldError = FlutterError.onError;
+ await runZoned(() async {
+ const ImageProvider imageProvider = NetworkImage('asdasdasdas');
+ final Completer<bool> caughtError = Completer<bool>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ throw Error();
+ };
+ final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+ result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ }, onError: (dynamic error, StackTrace stackTrace) {
+ caughtError.complete(true);
+ }));
+ expect(await caughtError.future, true);
+ }, zoneSpecification: ZoneSpecification(
+ handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
+ uncaught = true;
+ },
+ ));
+ expect(uncaught, false);
+ FlutterError.onError = oldError;
+ });
+
+ test('Notifies listeners of chunk events', () async {
+ const int chunkSize = 8;
+ final List<Uint8List> chunks = <Uint8List>[
+ for (int offset = 0; offset < kTransparentImage.length; offset += chunkSize)
+ Uint8List.fromList(kTransparentImage.skip(offset).take(chunkSize).toList()),
+ ];
+ final Completer<void> imageAvailable = Completer<void>();
+ final _MockHttpClientRequest request = _MockHttpClientRequest();
+ final _MockHttpClientResponse response = _MockHttpClientResponse();
+ when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
+ when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
+ when(response.statusCode).thenReturn(HttpStatus.ok);
+ when(response.contentLength).thenReturn(kTransparentImage.length);
+ when(response.listen(
+ any,
+ onDone: anyNamed('onDone'),
+ onError: anyNamed('onError'),
+ cancelOnError: anyNamed('cancelOnError'),
+ )).thenAnswer((Invocation invocation) {
+ final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>);
+ final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object);
+ final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback;
+ final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
+
+ return Stream<Uint8List>.fromIterable(chunks).listen(
+ onData,
+ onDone: onDone,
+ onError: onError,
+ cancelOnError: cancelOnError,
+ );
+ });
+
+ final ImageProvider imageProvider = NetworkImage(nonconst('foo'));
+ final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+ final List<ImageChunkEvent> events = <ImageChunkEvent>[];
+ result.addListener(ImageStreamListener(
+ (ImageInfo image, bool synchronousCall) {
+ imageAvailable.complete();
+ },
+ onChunk: (ImageChunkEvent event) {
+ events.add(event);
+ },
+ onError: (dynamic error, StackTrace stackTrace) {
+ imageAvailable.completeError(error, stackTrace);
+ },
+ ));
+ await imageAvailable.future;
+ expect(events.length, chunks.length);
+ for (int i = 0; i < events.length; i++) {
+ expect(events[i].cumulativeBytesLoaded, math.min((i + 1) * chunkSize, kTransparentImage.length));
+ expect(events[i].expectedTotalBytes, kTransparentImage.length);
+ }
+ }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56317
+
+ test('NetworkImage is evicted from cache on SocketException', () async {
+ final _MockHttpClient mockHttpClient = _MockHttpClient();
+ when(mockHttpClient.getUrl(any)).thenAnswer((_) => throw const SocketException('test exception'));
+ debugNetworkImageHttpClientProvider = () => mockHttpClient;
+
+
+ final ImageProvider imageProvider = NetworkImage(nonconst('testing.url'));
+ expect(imageCache.pendingImageCount, 0);
+ expect(imageCache.statusForKey(imageProvider).untracked, true);
+
+ final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+
+ expect(imageCache.pendingImageCount, 1);
+ expect(imageCache.statusForKey(imageProvider).pending, true);
+ final Completer<dynamic> caughtError = Completer<dynamic>();
+ result.addListener(ImageStreamListener(
+ (ImageInfo info, bool syncCall) {},
+ onError: (dynamic error, StackTrace stackTrace) {
+ caughtError.complete(error);
+ },
+ ));
+
+ final dynamic err = await caughtError.future;
+
+ expect(err, isA<SocketException>());
+
+ expect(imageCache.pendingImageCount, 0);
+ expect(imageCache.statusForKey(imageProvider).untracked, true);
+ expect(imageCache.containsKey(result), isFalse);
+
+ debugNetworkImageHttpClientProvider = null;
+ }, skip: isBrowser); // Browser does not resolve images this way.
+}
+
+class _MockHttpClient extends Mock implements HttpClient {}
+class _MockHttpClientRequest extends Mock implements HttpClientRequest {}
+class _MockHttpClientResponse extends Mock implements HttpClientResponse {}
diff --git a/packages/flutter/test/painting/image_provider_resize_image_test.dart b/packages/flutter/test/painting/image_provider_resize_image_test.dart
new file mode 100644
index 0000000..6f8d268
--- /dev/null
+++ b/packages/flutter/test/painting/image_provider_resize_image_test.dart
@@ -0,0 +1,134 @@
+// 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:async';
+import 'dart:typed_data';
+
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+
+void main() {
+ TestRenderingFlutterBinding();
+
+ tearDown(() {
+ PaintingBinding.instance.imageCache.clear();
+ PaintingBinding.instance.imageCache.clearLiveImages();
+ });
+
+ test('ResizeImage resizes to the correct dimensions', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage imageProvider = MemoryImage(bytes);
+ final Size rawImageSize = await _resolveAndGetSize(imageProvider);
+ expect(rawImageSize, const Size(1, 1));
+
+ const Size resizeDims = Size(14, 7);
+ final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round());
+ const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
+ final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
+ expect(resizedImageSize, resizeDims);
+ }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
+
+ test('ResizeImage does not resize when no size is passed', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage imageProvider = MemoryImage(bytes);
+ final Size rawImageSize = await _resolveAndGetSize(imageProvider);
+ expect(rawImageSize, const Size(1, 1));
+
+ // Cannot pass in two null arguments for cache dimensions, so will use the regular
+ // MemoryImage
+ final MemoryImage resizedImage = MemoryImage(bytes);
+ final Size resizedImageSize = await _resolveAndGetSize(resizedImage);
+ expect(resizedImageSize, const Size(1, 1));
+ });
+
+ test('ResizeImage stores values', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage memoryImage = MemoryImage(bytes);
+ memoryImage.resolve(ImageConfiguration.empty);
+ final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: 20);
+ expect(resizeImage.width, 10);
+ expect(resizeImage.height, 20);
+ expect(resizeImage.imageProvider, memoryImage);
+ expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
+ });
+
+ test('ResizeImage takes one dim', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage memoryImage = MemoryImage(bytes);
+ final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: null);
+ expect(resizeImage.width, 10);
+ expect(resizeImage.height, null);
+ expect(resizeImage.imageProvider, memoryImage);
+ expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
+ });
+
+ test('ResizeImage forms closure', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage memoryImage = MemoryImage(bytes);
+ final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+
+ final DecoderCallback decode = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
+ expect(cacheWidth, 123);
+ expect(cacheHeight, 321);
+ return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+ };
+
+ resizeImage.load(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
+ });
+
+ test('ResizeImage handles sync obtainKey', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final MemoryImage memoryImage = MemoryImage(bytes);
+ final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+
+ bool isAsync = false;
+ resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
+ expect(isAsync, false);
+ });
+ isAsync = true;
+ expect(isAsync, true);
+ });
+
+ test('ResizeImage handles async obtainKey', () async {
+ final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+ final _AsyncKeyMemoryImage memoryImage = _AsyncKeyMemoryImage(bytes);
+ final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+
+ bool isAsync = false;
+ resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
+ expect(isAsync, true);
+ });
+ isAsync = true;
+ expect(isAsync, true);
+ });
+}
+
+Future<Size> _resolveAndGetSize(ImageProvider imageProvider,
+ {ImageConfiguration configuration = ImageConfiguration.empty}) async {
+ final ImageStream stream = imageProvider.resolve(configuration);
+ final Completer<Size> completer = Completer<Size>();
+ final ImageStreamListener listener =
+ ImageStreamListener((ImageInfo image, bool synchronousCall) {
+ final int height = image.image.height;
+ final int width = image.image.width;
+ completer.complete(Size(width.toDouble(), height.toDouble()));
+ }
+ );
+ stream.addListener(listener);
+ return await completer.future;
+}
+
+// This version of MemoryImage guarantees obtainKey returns a future that has not been
+// completed synchronously.
+class _AsyncKeyMemoryImage extends MemoryImage {
+ const _AsyncKeyMemoryImage(Uint8List bytes) : super(bytes);
+
+ @override
+ Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
+ return Future<MemoryImage>(() => this);
+ }
+}
diff --git a/packages/flutter/test/painting/image_provider_test.dart b/packages/flutter/test/painting/image_provider_test.dart
index 34b5b97..a342d6a 100644
--- a/packages/flutter/test/painting/image_provider_test.dart
+++ b/packages/flutter/test/painting/image_provider_test.dart
@@ -4,29 +4,17 @@
import 'dart:async';
import 'dart:io';
-import 'dart:math' as math;
-import 'dart:typed_data';
import 'package:file/memory.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
import '../rendering/rendering_tester.dart';
-import 'image_data.dart';
import 'mocks_for_image_cache.dart';
void main() {
-
- final DecoderCallback basicDecoder = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
- return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
- };
-
- setUpAll(() {
- TestRenderingFlutterBinding(); // initializes the imageCache
- });
+ TestRenderingFlutterBinding();
FlutterExceptionHandler oldError;
setUp(() {
@@ -39,485 +27,98 @@
PaintingBinding.instance.imageCache.clearLiveImages();
});
- group('ImageProvider', () {
- group('Image cache', () {
- tearDown(() {
- imageCache.clear();
- });
+ test('obtainKey errors will be caught', () async {
+ final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+ final Completer<bool> caughtError = Completer<bool>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ caughtError.complete(false);
+ };
+ final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+ stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ caughtError.complete(false);
+ }, onError: (dynamic error, StackTrace stackTrace) {
+ caughtError.complete(true);
+ }));
+ expect(await caughtError.future, true);
+ });
- test('AssetImageProvider - evicts on failure to load', () async {
- final Completer<FlutterError> error = Completer<FlutterError>();
- FlutterError.onError = (FlutterErrorDetails details) {
- error.complete(details.exception as FlutterError);
- };
+ test('obtainKey errors will be caught - check location', () async {
+ final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+ final Completer<bool> caughtError = Completer<bool>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ caughtError.complete(true);
+ };
+ await imageProvider.obtainCacheStatus(configuration: ImageConfiguration.empty);
- const ImageProvider provider = ExactAssetImage('does-not-exist');
- final Object key = await provider.obtainKey(ImageConfiguration.empty);
- expect(imageCache.statusForKey(provider).untracked, true);
- expect(imageCache.pendingImageCount, 0);
+ expect(await caughtError.future, true);
+ });
- provider.resolve(ImageConfiguration.empty);
-
- expect(imageCache.statusForKey(key).pending, true);
- expect(imageCache.pendingImageCount, 1);
-
- await error.future;
-
- expect(imageCache.statusForKey(provider).untracked, true);
- expect(imageCache.pendingImageCount, 0);
- }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314
-
- test('AssetImageProvider - evicts on null load', () async {
- final Completer<StateError> error = Completer<StateError>();
- FlutterError.onError = (FlutterErrorDetails details) {
- error.complete(details.exception as StateError);
- };
-
- final ImageProvider provider = ExactAssetImage('does-not-exist', bundle: TestAssetBundle());
- final Object key = await provider.obtainKey(ImageConfiguration.empty);
- expect(imageCache.statusForKey(provider).untracked, true);
- expect(imageCache.pendingImageCount, 0);
-
- provider.resolve(ImageConfiguration.empty);
-
- expect(imageCache.statusForKey(key).pending, true);
- expect(imageCache.pendingImageCount, 1);
-
- await error.future;
-
- expect(imageCache.statusForKey(provider).untracked, true);
- expect(imageCache.pendingImageCount, 0);
- });
-
- test('ImageProvider can evict images', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage imageProvider = MemoryImage(bytes);
- final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
- final Completer<void> completer = Completer<void>();
- stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) => completer.complete()));
- await completer.future;
-
- expect(imageCache.currentSize, 1);
- expect(await MemoryImage(bytes).evict(), true);
- expect(imageCache.currentSize, 0);
- });
-
- test('ImageProvider.evict respects the provided ImageCache', () async {
- final ImageCache otherCache = ImageCache();
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage imageProvider = MemoryImage(bytes);
- final ImageStreamCompleter cacheStream = otherCache.putIfAbsent(
- imageProvider, () => imageProvider.load(imageProvider, basicDecoder),
- );
- final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
- final Completer<void> completer = Completer<void>();
- final Completer<void> cacheCompleter = Completer<void>();
- stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- completer.complete();
- }));
- cacheStream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- cacheCompleter.complete();
- }));
- await Future.wait(<Future<void>>[completer.future, cacheCompleter.future]);
-
- expect(otherCache.currentSize, 1);
- expect(imageCache.currentSize, 1);
- expect(await imageProvider.evict(cache: otherCache), true);
- expect(otherCache.currentSize, 0);
- expect(imageCache.currentSize, 1);
- });
-
- test('ImageProvider errors can always be caught', () async {
- final ErrorImageProvider imageProvider = ErrorImageProvider();
- final Completer<bool> caughtError = Completer<bool>();
- FlutterError.onError = (FlutterErrorDetails details) {
- caughtError.complete(false);
- };
- final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
- stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- caughtError.complete(false);
- }, onError: (dynamic error, StackTrace stackTrace) {
- caughtError.complete(true);
- }));
- expect(await caughtError.future, true);
- });
- });
-
- test('obtainKey errors will be caught', () async {
- final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+ test('resolve sync errors will be caught', () async {
+ bool uncaught = false;
+ final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
+ handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
+ uncaught = true;
+ },
+ ));
+ await testZone.run(() async {
+ final ImageProvider imageProvider = LoadErrorImageProvider();
final Completer<bool> caughtError = Completer<bool>();
FlutterError.onError = (FlutterErrorDetails details) {
- caughtError.complete(false);
+ throw Error();
};
- final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
- stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- caughtError.complete(false);
+ final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+ result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
}, onError: (dynamic error, StackTrace stackTrace) {
caughtError.complete(true);
}));
expect(await caughtError.future, true);
});
+ expect(uncaught, false);
+ });
- test('obtainKey errors will be caught - check location', () async {
- final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+ test('resolve errors in the completer will be caught', () async {
+ bool uncaught = false;
+ final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
+ handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
+ uncaught = true;
+ },
+ ));
+ await testZone.run(() async {
+ final ImageProvider imageProvider = LoadErrorCompleterImageProvider();
final Completer<bool> caughtError = Completer<bool>();
FlutterError.onError = (FlutterErrorDetails details) {
- caughtError.complete(true);
+ throw Error();
};
- await imageProvider.obtainCacheStatus(configuration: ImageConfiguration.empty);
-
+ final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+ result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+ }, onError: (dynamic error, StackTrace stackTrace) {
+ caughtError.complete(true);
+ }));
expect(await caughtError.future, true);
});
-
- test('resolve sync errors will be caught', () async {
- bool uncaught = false;
- final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
- handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
- uncaught = true;
- },
- ));
- await testZone.run(() async {
- final ImageProvider imageProvider = LoadErrorImageProvider();
- final Completer<bool> caughtError = Completer<bool>();
- FlutterError.onError = (FlutterErrorDetails details) {
- throw Error();
- };
- final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
- result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- }, onError: (dynamic error, StackTrace stackTrace) {
- caughtError.complete(true);
- }));
- expect(await caughtError.future, true);
- });
- expect(uncaught, false);
- });
-
- test('resolve errors in the completer will be caught', () async {
- bool uncaught = false;
- final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
- handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
- uncaught = true;
- },
- ));
- await testZone.run(() async {
- final ImageProvider imageProvider = LoadErrorCompleterImageProvider();
- final Completer<bool> caughtError = Completer<bool>();
- FlutterError.onError = (FlutterErrorDetails details) {
- throw Error();
- };
- final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
- result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- }, onError: (dynamic error, StackTrace stackTrace) {
- caughtError.complete(true);
- }));
- expect(await caughtError.future, true);
- });
- expect(uncaught, false);
- });
-
- test('File image with empty file throws expected error and evicts from cache', () async {
- final Completer<StateError> error = Completer<StateError>();
- FlutterError.onError = (FlutterErrorDetails details) {
- error.complete(details.exception as StateError);
- };
- final MemoryFileSystem fs = MemoryFileSystem();
- final File file = fs.file('/empty.png')..createSync(recursive: true);
- final FileImage provider = FileImage(file);
-
- expect(imageCache.statusForKey(provider).untracked, true);
- expect(imageCache.pendingImageCount, 0);
-
- provider.resolve(ImageConfiguration.empty);
-
- expect(imageCache.statusForKey(provider).pending, true);
- expect(imageCache.pendingImageCount, 1);
-
- expect(await error.future, isStateError);
- expect(imageCache.statusForKey(provider).untracked, true);
- expect(imageCache.pendingImageCount, 0);
- });
-
- group('NetworkImage', () {
- MockHttpClient httpClient;
-
- setUp(() {
- httpClient = MockHttpClient();
- debugNetworkImageHttpClientProvider = () => httpClient;
- });
-
- tearDown(() {
- debugNetworkImageHttpClientProvider = null;
- });
-
- test('Expect thrown exception with statusCode - evicts from cache', () async {
- final int errorStatusCode = HttpStatus.notFound;
- const String requestUrl = 'foo-url';
-
- final MockHttpClientRequest request = MockHttpClientRequest();
- final MockHttpClientResponse response = MockHttpClientResponse();
- when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
- when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
- when(response.statusCode).thenReturn(errorStatusCode);
-
- final Completer<dynamic> caughtError = Completer<dynamic>();
-
- final ImageProvider imageProvider = NetworkImage(nonconst(requestUrl));
- expect(imageCache.pendingImageCount, 0);
- expect(imageCache.statusForKey(imageProvider).untracked, true);
-
- final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-
- expect(imageCache.pendingImageCount, 1);
- expect(imageCache.statusForKey(imageProvider).pending, true);
-
- result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- }, onError: (dynamic error, StackTrace stackTrace) {
- caughtError.complete(error);
- }));
-
- final dynamic err = await caughtError.future;
-
- expect(imageCache.pendingImageCount, 0);
- expect(imageCache.statusForKey(imageProvider).untracked, true);
-
- expect(
- err,
- isA<NetworkImageLoadException>()
- .having((NetworkImageLoadException e) => e.statusCode, 'statusCode', errorStatusCode)
- .having((NetworkImageLoadException e) => e.uri, 'uri', Uri.base.resolve(requestUrl)),
- );
- }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
-
- test('Disallows null urls', () {
- expect(() {
- NetworkImage(nonconst(null));
- }, throwsAssertionError);
- });
-
- test('Uses the HttpClient provided by debugNetworkImageHttpClientProvider if set', () async {
- when(httpClient.getUrl(any)).thenThrow('client1');
- final List<dynamic> capturedErrors = <dynamic>[];
-
- Future<void> loadNetworkImage() async {
- final NetworkImage networkImage = NetworkImage(nonconst('foo'));
- final ImageStreamCompleter completer = networkImage.load(networkImage, basicDecoder);
- completer.addListener(ImageStreamListener(
- (ImageInfo image, bool synchronousCall) { },
- onError: (dynamic error, StackTrace stackTrace) {
- capturedErrors.add(error);
- },
- ));
- await Future<void>.value();
- }
-
- await loadNetworkImage();
- expect(capturedErrors, <dynamic>['client1']);
- final MockHttpClient client2 = MockHttpClient();
- when(client2.getUrl(any)).thenThrow('client2');
- debugNetworkImageHttpClientProvider = () => client2;
- await loadNetworkImage();
- expect(capturedErrors, <dynamic>['client1', 'client2']);
- }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
-
- test('Propagates http client errors during resolve()', () async {
- when(httpClient.getUrl(any)).thenThrow(Error());
- bool uncaught = false;
-
- await runZoned(() async {
- const ImageProvider imageProvider = NetworkImage('asdasdasdas');
- final Completer<bool> caughtError = Completer<bool>();
- FlutterError.onError = (FlutterErrorDetails details) {
- throw Error();
- };
- final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
- result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
- }, onError: (dynamic error, StackTrace stackTrace) {
- caughtError.complete(true);
- }));
- expect(await caughtError.future, true);
- }, zoneSpecification: ZoneSpecification(
- handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
- uncaught = true;
- },
- ));
- expect(uncaught, false);
- });
-
- test('Notifies listeners of chunk events', () async {
- const int chunkSize = 8;
- final List<Uint8List> chunks = <Uint8List>[
- for (int offset = 0; offset < kTransparentImage.length; offset += chunkSize)
- Uint8List.fromList(kTransparentImage.skip(offset).take(chunkSize).toList()),
- ];
- final Completer<void> imageAvailable = Completer<void>();
- final MockHttpClientRequest request = MockHttpClientRequest();
- final MockHttpClientResponse response = MockHttpClientResponse();
- when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
- when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
- when(response.statusCode).thenReturn(HttpStatus.ok);
- when(response.contentLength).thenReturn(kTransparentImage.length);
- when(response.listen(
- any,
- onDone: anyNamed('onDone'),
- onError: anyNamed('onError'),
- cancelOnError: anyNamed('cancelOnError'),
- )).thenAnswer((Invocation invocation) {
- final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>);
- final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object);
- final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback;
- final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
-
- return Stream<Uint8List>.fromIterable(chunks).listen(
- onData,
- onDone: onDone,
- onError: onError,
- cancelOnError: cancelOnError,
- );
- });
-
- final ImageProvider imageProvider = NetworkImage(nonconst('foo'));
- final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
- final List<ImageChunkEvent> events = <ImageChunkEvent>[];
- result.addListener(ImageStreamListener(
- (ImageInfo image, bool synchronousCall) {
- imageAvailable.complete();
- },
- onChunk: (ImageChunkEvent event) {
- events.add(event);
- },
- onError: (dynamic error, StackTrace stackTrace) {
- imageAvailable.completeError(error, stackTrace);
- },
- ));
- await imageAvailable.future;
- expect(events.length, chunks.length);
- for (int i = 0; i < events.length; i++) {
- expect(events[i].cumulativeBytesLoaded, math.min((i + 1) * chunkSize, kTransparentImage.length));
- expect(events[i].expectedTotalBytes, kTransparentImage.length);
- }
- }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56317
-
- test('NetworkImage is evicted from cache on SocketException', () async {
- final MockHttpClient mockHttpClient = MockHttpClient();
- when(mockHttpClient.getUrl(any)).thenAnswer((_) => throw const SocketException('test exception'));
- debugNetworkImageHttpClientProvider = () => mockHttpClient;
-
-
- final ImageProvider imageProvider = NetworkImage(nonconst('testing.url'));
- expect(imageCache.pendingImageCount, 0);
- expect(imageCache.statusForKey(imageProvider).untracked, true);
-
- final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-
- expect(imageCache.pendingImageCount, 1);
- expect(imageCache.statusForKey(imageProvider).pending, true);
- final Completer<dynamic> caughtError = Completer<dynamic>();
- result.addListener(ImageStreamListener(
- (ImageInfo info, bool syncCall) {},
- onError: (dynamic error, StackTrace stackTrace) {
- caughtError.complete(error);
- },
- ));
-
- final dynamic err = await caughtError.future;
-
- expect(err, isA<SocketException>());
-
- expect(imageCache.pendingImageCount, 0);
- expect(imageCache.statusForKey(imageProvider).untracked, true);
- expect(imageCache.containsKey(result), isFalse);
-
- debugNetworkImageHttpClientProvider = null;
- }, skip: isBrowser); // Browser does not resolve images this way.
- });
+ expect(uncaught, false);
});
- test('ResizeImage resizes to the correct dimensions', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage imageProvider = MemoryImage(bytes);
- final Size rawImageSize = await _resolveAndGetSize(imageProvider);
- expect(rawImageSize, const Size(1, 1));
-
- const Size resizeDims = Size(14, 7);
- final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round());
- const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
- final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
- expect(resizedImageSize, resizeDims);
- }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
-
- test('ResizeImage does not resize when no size is passed', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage imageProvider = MemoryImage(bytes);
- final Size rawImageSize = await _resolveAndGetSize(imageProvider);
- expect(rawImageSize, const Size(1, 1));
-
- // Cannot pass in two null arguments for cache dimensions, so will use the regular
- // MemoryImage
- final MemoryImage resizedImage = MemoryImage(bytes);
- final Size resizedImageSize = await _resolveAndGetSize(resizedImage);
- expect(resizedImageSize, const Size(1, 1));
- });
-
- test('ResizeImage stores values', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage memoryImage = MemoryImage(bytes);
- final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: 20);
- expect(resizeImage.width, 10);
- expect(resizeImage.height, 20);
- expect(resizeImage.imageProvider, memoryImage);
-
- expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
- });
-
- test('ResizeImage takes one dim', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage memoryImage = MemoryImage(bytes);
- final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: null);
- expect(resizeImage.width, 10);
- expect(resizeImage.height, null);
- expect(resizeImage.imageProvider, memoryImage);
-
- expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
- });
-
- test('ResizeImage forms closure', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage memoryImage = MemoryImage(bytes);
- final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
-
- final DecoderCallback decode = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
- expect(cacheWidth, 123);
- expect(cacheHeight, 321);
- return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+ test('File image with empty file throws expected error and evicts from cache', () async {
+ final Completer<StateError> error = Completer<StateError>();
+ FlutterError.onError = (FlutterErrorDetails details) {
+ error.complete(details.exception as StateError);
};
+ final MemoryFileSystem fs = MemoryFileSystem();
+ final File file = fs.file('/empty.png')..createSync(recursive: true);
+ final FileImage provider = FileImage(file);
- resizeImage.load(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
- });
+ expect(imageCache.statusForKey(provider).untracked, true);
+ expect(imageCache.pendingImageCount, 0);
- test('ResizeImage handles sync obtainKey', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final MemoryImage memoryImage = MemoryImage(bytes);
- final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+ provider.resolve(ImageConfiguration.empty);
- bool isAsync = false;
- resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
- expect(isAsync, false);
- });
- isAsync = true;
- expect(isAsync, true);
- });
+ expect(imageCache.statusForKey(provider).pending, true);
+ expect(imageCache.pendingImageCount, 1);
- test('ResizeImage handles async obtainKey', () async {
- final Uint8List bytes = Uint8List.fromList(kTransparentImage);
- final AsyncKeyMemoryImage memoryImage = AsyncKeyMemoryImage(bytes);
- final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
-
- bool isAsync = false;
- resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
- expect(isAsync, true);
- });
- isAsync = true;
- expect(isAsync, true);
+ expect(await error.future, isStateError);
+ expect(imageCache.statusForKey(provider).untracked, true);
+ expect(imageCache.pendingImageCount, 0);
});
test('File image with empty file throws expected error (load)', () async {
@@ -534,40 +135,3 @@
expect(await error.future, isStateError);
});
}
-
-Future<Size> _resolveAndGetSize(ImageProvider imageProvider,
- {ImageConfiguration configuration = ImageConfiguration.empty}) async {
- final ImageStream stream = imageProvider.resolve(configuration);
- final Completer<Size> completer = Completer<Size>();
- final ImageStreamListener listener =
- ImageStreamListener((ImageInfo image, bool synchronousCall) {
- final int height = image.image.height;
- final int width = image.image.width;
- completer.complete(Size(width.toDouble(), height.toDouble()));
- }
- );
- stream.addListener(listener);
- return await completer.future;
-}
-
-// This version of MemoryImage guarantees obtainKey returns a future that has not been
-// completed synchronously.
-class AsyncKeyMemoryImage extends MemoryImage {
- const AsyncKeyMemoryImage(Uint8List bytes) : super(bytes);
-
- @override
- Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
- return Future<MemoryImage>(() => this);
- }
-}
-
-class MockHttpClient extends Mock implements HttpClient {}
-class MockHttpClientRequest extends Mock implements HttpClientRequest {}
-class MockHttpClientResponse extends Mock implements HttpClientResponse {}
-
-class TestAssetBundle extends CachingAssetBundle {
- @override
- Future<ByteData> load(String key) async {
- return null;
- }
-}
diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart
index 289c1b2..6d24df8 100644
--- a/packages/flutter/test/rendering/rendering_tester.dart
+++ b/packages/flutter/test/rendering/rendering_tester.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
+
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
@@ -19,7 +21,15 @@
/// while drawing the frame. If [onErrors] is null and [FlutterError] caught at least
/// one error, this function fails the test. A test may override [onErrors] and
/// inspect errors using [takeFlutterErrorDetails].
- TestRenderingFlutterBinding({ this.onErrors });
+ ///
+ /// Errors caught between frames will cause the test to fail unless
+ /// [FlutterError.onError] has been overridden.
+ TestRenderingFlutterBinding({ this.onErrors }) {
+ FlutterError.onError = (FlutterErrorDetails details) {
+ FlutterError.dumpErrorToConsole(details);
+ Zone.current.parent.handleUncaughtError(details.exception, details.stack);
+ };
+ }
final List<FlutterErrorDetails> _errors = <FlutterErrorDetails>[];