blob: 51bc8950154dfab2910098bb61b7419d39859722 [file]
// 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.
/// @docImport 'package:fake_async/fake_async.dart';
/// @docImport 'package:flutter/widgets.dart';
///
/// @docImport 'binding.dart';
/// @docImport 'test_compat.dart';
/// @docImport 'widget_tester.dart';
library;
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/painting.dart';
import 'binding.dart';
import 'test_async_utils.dart';
final Map<int, ui.Image> _cache = <int, ui.Image>{};
/// Creates an arbitrarily sized image for testing.
///
/// If the [cache] parameter is set to true, the image will be cached for the
/// rest of this suite. This is normally desirable, assuming a test suite uses
/// images with the same dimensions in most tests, as it will save on memory
/// usage and CPU time over the course of the suite. However, it should be
/// avoided for images that are used only once in a test suite, especially if
/// the image is large, as it will require holding on to the memory for that
/// image for the duration of the suite.
///
/// This method requires real async work, and will not work properly in the
/// [FakeAsync] zones set up by [testWidgets]. Typically, it should be invoked
/// as a setup step before [testWidgets] are run, such as [setUp] or [setUpAll].
/// If needed, it can be invoked using [WidgetTester.runAsync].
Future<ui.Image> createTestImage({int width = 1, int height = 1, bool cache = true}) =>
TestAsyncUtils.guard(() async {
assert(width > 0);
assert(height > 0);
final int cacheKey = Object.hash(width, height);
if (cache && _cache.containsKey(cacheKey)) {
return _cache[cacheKey]!.clone();
}
final ui.Image image = await _createImage(width, height);
if (cache) {
_cache[cacheKey] = image.clone();
}
return image;
});
Future<ui.Image> _createImage(int width, int height) async {
final completer = Completer<ui.Image>();
ui.decodeImageFromPixels(
Uint8List.fromList(List<int>.filled(width * height * 4, 0)),
width,
height,
ui.PixelFormat.rgba8888,
(ui.Image image) {
completer.complete(image);
},
);
return completer.future;
}
/// Precaches an [ImageProvider] during testing.
///
/// In a widget test, image decoding operations (such as [MemoryImage],
/// [NetworkImage], and [ExactAssetImage]) run asynchronously outside the test's
/// [FakeAsync] zone.
///
/// Calling [precacheTestImage] resolves the image stream in a real async zone
/// (via [TestWidgetsFlutterBinding.runAsync]), loading the decoded image into
/// [imageCache] before the widget tree is pumped.
///
/// Can be called before [WidgetTester.pumpWidget] inside a [testWidgets] body,
/// or inside [setUp] / [setUpAll].
///
/// If [onError] is provided, it will be invoked if an error occurs while
/// precaching. Otherwise, any error will complete the returned future with that error.
Future<void> precacheTestImage(
ImageProvider provider, {
ImageConfiguration configuration = ImageConfiguration.empty,
ImageErrorListener? onError,
}) {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
Object? caughtError;
StackTrace? caughtStackTrace;
Future<void> resolveImage() async {
final completer = Completer<void>.sync();
final ImageStream stream = provider.resolve(configuration);
late final ImageStreamListener listener;
listener = ImageStreamListener(
(ImageInfo info, bool syncCall) {
info.dispose();
if (!completer.isCompleted) {
stream.removeListener(listener);
completer.complete();
}
},
onError: (Object error, StackTrace? stackTrace) {
if (!completer.isCompleted) {
stream.removeListener(listener);
if (onError != null) {
onError(error, stackTrace);
} else {
caughtError = error;
caughtStackTrace = stackTrace;
}
completer.complete();
}
},
);
stream.addListener(listener);
await completer.future;
}
Future<void> doPrecache() async {
await binding.runAsync<void>(resolveImage);
if (caughtError != null) {
Error.throwWithStackTrace(caughtError!, caughtStackTrace ?? StackTrace.current);
}
}
return TestAsyncUtils.guard(doPrecache);
}