Files
2019-08-13 20:38:46 +08:00

876 lines
30 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2016 The Chromium 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_web_ui/ui.dart' as ui show Image, ImageByteFormat;
import 'package:flutter_web/foundation.dart';
import 'package:flutter_web/rendering.dart';
import 'package:flutter_web/widgets.dart';
import 'package:flutter_web_test/flutter_web_test.dart';
import '../painting/image_data.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Verify Image resets its RenderImage when changing providers',
(WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final TestImageProvider imageProvider1 = TestImageProvider();
await tester.pumpWidget(
Container(
key: key,
child: Image(
image: imageProvider1,
excludeFromSemantics: true,
)),
null,
EnginePhase.layout,
);
RenderImage renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
imageProvider1.complete();
await tester.idle(); // resolve the future from the image provider
await tester.pump(null, EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
final TestImageProvider imageProvider2 = TestImageProvider();
await tester.pumpWidget(
Container(
key: key,
child: Image(
image: imageProvider2,
excludeFromSemantics: true,
)),
null,
EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
});
testWidgets(
'Verify Image doesn\'t reset its RenderImage when changing providers if it has gaplessPlayback set',
(WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final TestImageProvider imageProvider1 = TestImageProvider();
await tester.pumpWidget(
Container(
key: key,
child: Image(
gaplessPlayback: true,
image: imageProvider1,
excludeFromSemantics: true,
)),
null,
EnginePhase.layout);
RenderImage renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
imageProvider1.complete();
await tester.idle(); // resolve the future from the image provider
await tester.pump(null, EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
final TestImageProvider imageProvider2 = TestImageProvider();
await tester.pumpWidget(
Container(
key: key,
child: Image(
gaplessPlayback: true,
image: imageProvider2,
excludeFromSemantics: true,
)),
null,
EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
});
testWidgets(
'Verify Image resets its RenderImage when changing providers if it has a key',
(WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final TestImageProvider imageProvider1 = TestImageProvider();
await tester.pumpWidget(
Image(
key: key,
image: imageProvider1,
excludeFromSemantics: true,
),
null,
EnginePhase.layout);
RenderImage renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
imageProvider1.complete();
await tester.idle(); // resolve the future from the image provider
await tester.pump(null, EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
final TestImageProvider imageProvider2 = TestImageProvider();
await tester.pumpWidget(
Image(
key: key,
image: imageProvider2,
excludeFromSemantics: true,
),
null,
EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
});
testWidgets(
'Verify Image doesn\'t reset its RenderImage when changing providers if it has gaplessPlayback set',
(WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final TestImageProvider imageProvider1 = TestImageProvider();
await tester.pumpWidget(
Image(
key: key,
gaplessPlayback: true,
image: imageProvider1,
excludeFromSemantics: true,
),
null,
EnginePhase.layout);
RenderImage renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
imageProvider1.complete();
await tester.idle(); // resolve the future from the image provider
await tester.pump(null, EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
final TestImageProvider imageProvider2 = TestImageProvider();
await tester.pumpWidget(
Image(
key: key,
gaplessPlayback: true,
excludeFromSemantics: true,
image: imageProvider2),
null,
EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
});
testWidgets('Verify ImageProvider configuration inheritance',
(WidgetTester tester) async {
final GlobalKey mediaQueryKey1 = GlobalKey(debugLabel: 'mediaQueryKey1');
final GlobalKey mediaQueryKey2 = GlobalKey(debugLabel: 'mediaQueryKey2');
final GlobalKey imageKey = GlobalKey(debugLabel: 'image');
final TestImageProvider imageProvider = TestImageProvider();
// Of the two nested MediaQuery objects, the innermost one,
// mediaQuery2, should define the configuration of the imageProvider.
await tester.pumpWidget(MediaQuery(
key: mediaQueryKey1,
data: const MediaQueryData(
devicePixelRatio: 10.0,
padding: EdgeInsets.zero,
),
child: MediaQuery(
key: mediaQueryKey2,
data: const MediaQueryData(
devicePixelRatio: 5.0,
padding: EdgeInsets.zero,
),
child: Image(
excludeFromSemantics: true, key: imageKey, image: imageProvider),
)));
expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 5.0);
// This is the same widget hierarchy as before except that the
// two MediaQuery objects have exchanged places. The imageProvider
// should be resolved again, with the new innermost MediaQuery.
await tester.pumpWidget(MediaQuery(
key: mediaQueryKey2,
data: const MediaQueryData(
devicePixelRatio: 5.0,
padding: EdgeInsets.zero,
),
child: MediaQuery(
key: mediaQueryKey1,
data: const MediaQueryData(
devicePixelRatio: 10.0,
padding: EdgeInsets.zero,
),
child: Image(
excludeFromSemantics: true, key: imageKey, image: imageProvider),
)));
expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 10.0);
});
testWidgets('Verify ImageProvider configuration inheritance again',
(WidgetTester tester) async {
final GlobalKey mediaQueryKey1 = GlobalKey(debugLabel: 'mediaQueryKey1');
final GlobalKey mediaQueryKey2 = GlobalKey(debugLabel: 'mediaQueryKey2');
final GlobalKey imageKey = GlobalKey(debugLabel: 'image');
final TestImageProvider imageProvider = TestImageProvider();
// This is just a variation on the previous test. In this version the location
// of the Image changes and the MediaQuery widgets do not.
await tester
.pumpWidget(Row(textDirection: TextDirection.ltr, children: <Widget>[
MediaQuery(
key: mediaQueryKey2,
data: const MediaQueryData(
devicePixelRatio: 5.0,
padding: EdgeInsets.zero,
),
child: Image(
excludeFromSemantics: true, key: imageKey, image: imageProvider)),
MediaQuery(
key: mediaQueryKey1,
data: const MediaQueryData(
devicePixelRatio: 10.0,
padding: EdgeInsets.zero,
),
child: Container(width: 100.0))
]));
expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 5.0);
await tester
.pumpWidget(Row(textDirection: TextDirection.ltr, children: <Widget>[
MediaQuery(
key: mediaQueryKey2,
data: const MediaQueryData(
devicePixelRatio: 5.0,
padding: EdgeInsets.zero,
),
child: Container(width: 100.0)),
MediaQuery(
key: mediaQueryKey1,
data: const MediaQueryData(
devicePixelRatio: 10.0,
padding: EdgeInsets.zero,
),
child: Image(
excludeFromSemantics: true, key: imageKey, image: imageProvider))
]));
expect(imageProvider._lastResolvedConfiguration.devicePixelRatio, 10.0);
});
testWidgets('Verify Image stops listening to ImageStream',
(WidgetTester tester) async {
final TestImageProvider imageProvider = TestImageProvider();
await tester
.pumpWidget(Image(image: imageProvider, excludeFromSemantics: true));
final State<Image> image = tester.state<State<Image>>(find.byType(Image));
expect(
image.toString(),
equalsIgnoringHashCodes(
'_ImageState#00000(stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, unresolved, 2 listeners), pixels: null)'));
imageProvider.complete();
await tester.pump();
expect(
image.toString(),
equalsIgnoringHashCodes(
'_ImageState#00000(stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, [100×100] @ 1.0x, 1 listener), pixels: [100×100] @ 1.0x)'));
await tester.pumpWidget(Container());
expect(
image.toString(),
equalsIgnoringHashCodes(
'_ImageState#00000(lifecycle state: defunct, not mounted, stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, [100×100] @ 1.0x, 0 listeners), pixels: [100×100] @ 1.0x)'));
});
testWidgets(
'Stream completer errors can be listened to by attaching before resolving',
(WidgetTester tester) async {
dynamic capturedException;
StackTrace capturedStackTrace;
ImageInfo capturedImage;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
capturedException = exception;
capturedStackTrace = stackTrace;
};
final ImageListener listener = (ImageInfo info, bool synchronous) {
capturedImage = info;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
imageProvider._streamCompleter
.addListener(listener, onError: errorListener);
ImageConfiguration configuration;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
configuration = createLocalImageConfiguration(context);
return Container();
},
),
);
imageProvider.resolve(configuration);
imageProvider.fail(testException, testStack);
expect(tester.binding.microtaskCount, 1);
await tester
.idle(); // Let the failed completer's future hit the stream completer.
expect(tester.binding.microtaskCount, 0);
expect(capturedImage,
isNull); // The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect(capturedException, testException);
expect(capturedStackTrace, testStack);
// If there is an error listener, there should be no FlutterError reported.
expect(tester.takeException(), isNull);
});
testWidgets(
'Stream completer errors can be listened to by attaching after resolving',
(WidgetTester tester) async {
dynamic capturedException;
StackTrace capturedStackTrace;
dynamic reportedException;
StackTrace reportedStackTrace;
ImageInfo capturedImage;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
capturedException = exception;
capturedStackTrace = stackTrace;
};
final ImageListener listener = (ImageInfo info, bool synchronous) {
capturedImage = info;
};
FlutterError.onError = (FlutterErrorDetails flutterError) {
reportedException = flutterError.exception;
reportedStackTrace = flutterError.stack;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
ImageConfiguration configuration;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
configuration = createLocalImageConfiguration(context);
return Container();
},
),
);
final ImageStream streamUnderTest = imageProvider.resolve(configuration);
imageProvider.fail(testException, testStack);
expect(tester.binding.microtaskCount, 1);
await tester
.idle(); // Let the failed completer's future hit the stream completer.
expect(tester.binding.microtaskCount, 0);
// Since there's no listeners attached yet, report error up via
// FlutterError.
expect(reportedException, testException);
expect(reportedStackTrace, testStack);
streamUnderTest.addListener(listener, onError: errorListener);
expect(capturedImage,
isNull); // The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect(capturedException, testException);
expect(capturedStackTrace, testStack);
});
testWidgets('Duplicate listener registration does not affect error listeners',
(WidgetTester tester) async {
dynamic capturedException;
StackTrace capturedStackTrace;
ImageInfo capturedImage;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
capturedException = exception;
capturedStackTrace = stackTrace;
};
final ImageListener listener = (ImageInfo info, bool synchronous) {
capturedImage = info;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
imageProvider._streamCompleter
.addListener(listener, onError: errorListener);
// Add the exact same listener a second time without the errorListener.
imageProvider._streamCompleter.addListener(listener);
ImageConfiguration configuration;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
configuration = createLocalImageConfiguration(context);
return Container();
},
),
);
imageProvider.resolve(configuration);
imageProvider.fail(testException, testStack);
expect(tester.binding.microtaskCount, 1);
await tester
.idle(); // Let the failed completer's future hit the stream completer.
expect(tester.binding.microtaskCount, 0);
expect(capturedImage,
isNull); // The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect(capturedException, testException);
expect(capturedStackTrace, testStack);
// If there is an error listener, there should be no FlutterError reported.
expect(tester.takeException(), isNull);
});
testWidgets('Duplicate error listeners are all called',
(WidgetTester tester) async {
dynamic capturedException;
StackTrace capturedStackTrace;
ImageInfo capturedImage;
int errorListenerCalled = 0;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
capturedException = exception;
capturedStackTrace = stackTrace;
errorListenerCalled++;
};
final ImageListener listener = (ImageInfo info, bool synchronous) {
capturedImage = info;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
imageProvider._streamCompleter
.addListener(listener, onError: errorListener);
// Add the exact same errorListener a second time.
imageProvider._streamCompleter.addListener(null, onError: errorListener);
ImageConfiguration configuration;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
configuration = createLocalImageConfiguration(context);
return Container();
},
),
);
imageProvider.resolve(configuration);
imageProvider.fail(testException, testStack);
expect(tester.binding.microtaskCount, 1);
await tester
.idle(); // Let the failed completer's future hit the stream completer.
expect(tester.binding.microtaskCount, 0);
expect(capturedImage,
isNull); // The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect(capturedException, testException);
expect(capturedStackTrace, testStack);
expect(errorListenerCalled, 2);
// If there is an error listener, there should be no FlutterError reported.
expect(tester.takeException(), isNull);
});
testWidgets('Error listeners are removed along with listeners',
(WidgetTester tester) async {
bool errorListenerCalled = false;
dynamic reportedException;
StackTrace reportedStackTrace;
ImageInfo capturedImage;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
errorListenerCalled = true;
};
final ImageListener listener = (ImageInfo info, bool synchronous) {
capturedImage = info;
};
FlutterError.onError = (FlutterErrorDetails flutterError) {
reportedException = flutterError.exception;
reportedStackTrace = flutterError.stack;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
imageProvider._streamCompleter
.addListener(listener, onError: errorListener);
// Now remove the listener the error listener is attached to.
// Don't explicitly remove the error listener.
imageProvider._streamCompleter.removeListener(listener);
ImageConfiguration configuration;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
configuration = createLocalImageConfiguration(context);
return Container();
},
),
);
imageProvider.resolve(configuration);
imageProvider.fail(testException, testStack);
expect(tester.binding.microtaskCount, 1);
await tester
.idle(); // Let the failed completer's future hit the stream completer.
expect(tester.binding.microtaskCount, 0);
expect(errorListenerCalled, false);
// Since the error listener is removed, bubble up to FlutterError.
expect(reportedException, testException);
expect(reportedStackTrace, testStack);
expect(capturedImage,
isNull); // The image stream listeners should never be called.
});
testWidgets('Removing listener removes one listener and error listener',
(WidgetTester tester) async {
int errorListenerCalled = 0;
ImageInfo capturedImage;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
errorListenerCalled++;
};
final ImageListener listener = (ImageInfo info, bool synchronous) {
capturedImage = info;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
imageProvider._streamCompleter
.addListener(listener, onError: errorListener);
// Duplicates the same set of listener and errorListener.
imageProvider._streamCompleter
.addListener(listener, onError: errorListener);
// Now remove one entry of the specified listener and associated error listener.
// Don't explicitly remove the error listener.
imageProvider._streamCompleter.removeListener(listener);
ImageConfiguration configuration;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
configuration = createLocalImageConfiguration(context);
return Container();
},
),
);
imageProvider.resolve(configuration);
imageProvider.fail(testException, testStack);
expect(tester.binding.microtaskCount, 1);
await tester
.idle(); // Let the failed completer's future hit the stream completer.
expect(tester.binding.microtaskCount, 0);
expect(errorListenerCalled, 1);
expect(capturedImage,
isNull); // The image stream listeners should never be called.
});
testWidgets('Image.memory control test', (WidgetTester tester) async {
await tester.pumpWidget(Image.memory(
Uint8List.fromList(kTransparentImage),
excludeFromSemantics: true,
));
});
testWidgets('Image color and colorBlend parameters',
(WidgetTester tester) async {
await tester.pumpWidget(Image(
excludeFromSemantics: true,
image: TestImageProvider(),
color: const Color(0xFF00FF00),
colorBlendMode: BlendMode.clear));
final RenderImage renderer =
tester.renderObject<RenderImage>(find.byType(Image));
expect(renderer.color, const Color(0xFF00FF00));
expect(renderer.colorBlendMode, BlendMode.clear);
});
testWidgets('Precache', (WidgetTester tester) async {
final TestImageProvider provider = TestImageProvider();
Future<void> precache;
await tester.pumpWidget(Builder(builder: (BuildContext context) {
precache = precacheImage(provider, context);
return Container();
}));
provider.complete();
await precache;
expect(provider._lastResolvedConfiguration, isNotNull);
// Check that a second resolve of the same image is synchronous.
final ImageStream stream =
provider.resolve(provider._lastResolvedConfiguration);
bool isSync;
stream.addListener((ImageInfo image, bool sync) {
isSync = sync;
});
expect(isSync, isTrue);
});
testWidgets('Precache completes with onError on error',
(WidgetTester tester) async {
dynamic capturedException;
StackTrace capturedStackTrace;
final ImageErrorListener errorListener =
(dynamic exception, StackTrace stackTrace) {
capturedException = exception;
capturedStackTrace = stackTrace;
};
final Exception testException = Exception('cannot resolve host');
final StackTrace testStack = StackTrace.current;
final TestImageProvider imageProvider = TestImageProvider();
Future<void> precache;
await tester.pumpWidget(Builder(builder: (BuildContext context) {
precache = precacheImage(imageProvider, context, onError: errorListener);
return Container();
}));
imageProvider.fail(testException, testStack);
await precache;
// The image stream error handler should have the original exception.
expect(capturedException, testException);
expect(capturedStackTrace, testStack);
// If there is an error listener, there should be no FlutterError reported.
expect(tester.takeException(), isNull);
});
testWidgets('TickerMode controls stream registration',
(WidgetTester tester) async {
final TestImageStreamCompleter imageStreamCompleter =
TestImageStreamCompleter();
final Image image = Image(
excludeFromSemantics: true,
image: TestImageProvider(streamCompleter: imageStreamCompleter),
);
await tester.pumpWidget(
TickerMode(
enabled: true,
child: image,
),
);
expect(imageStreamCompleter.listeners.length, 2);
await tester.pumpWidget(
TickerMode(
enabled: false,
child: image,
),
);
expect(imageStreamCompleter.listeners.length, 1);
});
testWidgets(
'Verify Image shows correct RenderImage when changing to an already completed provider',
(WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final TestImageProvider imageProvider1 = TestImageProvider();
final TestImageProvider imageProvider2 = TestImageProvider();
await tester.pumpWidget(
Container(
key: key,
child: Image(excludeFromSemantics: true, image: imageProvider1)),
null,
EnginePhase.layout);
RenderImage renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNull);
imageProvider1.complete();
imageProvider2.complete();
await tester.idle(); // resolve the future from the image provider
await tester.pump(null, EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
final ui.Image oldImage = renderImage.image;
await tester.pumpWidget(
Container(
key: key,
child: Image(excludeFromSemantics: true, image: imageProvider2)),
null,
EnginePhase.layout);
renderImage = key.currentContext.findRenderObject();
expect(renderImage.image, isNotNull);
expect(renderImage.image, isNot(equals(oldImage)));
});
testWidgets('Image State can be reconfigured to use another image',
(WidgetTester tester) async {
final Image image1 = Image(
image: TestImageProvider()..complete(),
width: 10.0,
excludeFromSemantics: true);
final Image image2 = Image(
image: TestImageProvider()..complete(),
width: 20.0,
excludeFromSemantics: true);
final Column column = Column(children: <Widget>[image1, image2]);
await tester.pumpWidget(column, null, EnginePhase.layout);
final Column columnSwapped = Column(children: <Widget>[image2, image1]);
await tester.pumpWidget(columnSwapped, null, EnginePhase.layout);
final List<RenderImage> renderObjects =
tester.renderObjectList<RenderImage>(find.byType(Image)).toList();
expect(renderObjects, hasLength(2));
expect(renderObjects[0].image, isNotNull);
expect(renderObjects[0].width, 20.0);
expect(renderObjects[1].image, isNotNull);
expect(renderObjects[1].width, 10.0);
});
testWidgets('Image contributes semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Row(
children: <Widget>[
Image(
image: TestImageProvider(),
width: 100.0,
height: 100.0,
semanticLabel: 'test',
),
],
),
),
);
expect(
semantics,
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
label: 'test',
rect: Rect.fromLTWH(0.0, 0.0, 100.0, 100.0),
textDirection: TextDirection.ltr,
flags: <SemanticsFlag>[SemanticsFlag.isImage],
)
]),
ignoreTransform: true));
semantics.dispose();
});
testWidgets('Image can exclude semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Image(
image: TestImageProvider(),
width: 100.0,
height: 100.0,
excludeFromSemantics: true,
),
),
);
expect(semantics,
hasSemantics(TestSemantics.root(children: <TestSemantics>[])));
semantics.dispose();
});
}
class TestImageProvider extends ImageProvider<TestImageProvider> {
TestImageProvider({ImageStreamCompleter streamCompleter}) {
_streamCompleter =
streamCompleter ?? OneFrameImageStreamCompleter(_completer.future);
}
final Completer<ImageInfo> _completer = Completer<ImageInfo>();
ImageStreamCompleter _streamCompleter;
ImageConfiguration _lastResolvedConfiguration;
@override
Future<TestImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<TestImageProvider>(this);
}
@override
ImageStream resolve(ImageConfiguration configuration) {
_lastResolvedConfiguration = configuration;
return super.resolve(configuration);
}
@override
ImageStreamCompleter load(TestImageProvider key) => _streamCompleter;
void complete() {
_completer.complete(ImageInfo(image: TestImage()));
}
void fail(dynamic exception, StackTrace stackTrace) {
_completer.completeError(exception, stackTrace);
}
@override
String toString() => '${describeIdentity(this)}()';
}
class TestImageStreamCompleter extends ImageStreamCompleter {
final Map<ImageListener, ImageErrorListener> listeners =
<ImageListener, ImageErrorListener>{};
@override
void addListener(ImageListener listener, {ImageErrorListener onError}) {
listeners[listener] = onError;
}
@override
void removeListener(ImageListener listener) {
listeners.remove(listener);
}
}
class TestImage implements ui.Image {
@override
int get width => 100;
@override
int get height => 100;
@override
void dispose() {}
@override
Future<ByteData> toByteData(
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) async {
throw UnsupportedError('Cannot encode test image');
}
@override
String toString() => '[$width\u00D7$height]';
}