| // 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); |
| } |