From 405788c9ac1fad9930ea95afd27f5c83b8dafb80 Mon Sep 17 00:00:00 2001 From: Kirpal Demian Date: Thu, 10 Mar 2022 15:29:14 -0500 Subject: [PATCH] fix!: load asset images for tests (#25) * fix!: load asset images for tests * feat: add precacheImages to pumps.dart * test precacheImages * fix doc comment * use expectLater * fix merge conflict * fix test --- lib/src/golden_test.dart | 17 +- lib/src/golden_test_adapter.dart | 5 + lib/src/golden_test_runner.dart | 3 +- lib/src/pumps.dart | 32 ++++ pubspec.yaml | 3 +- test/helpers/fake_test_asset_bundle.dart | 22 +++ test/helpers/helpers.dart | 1 + test/smoke_tests/asset_image_smoke_test.dart | 20 +++ .../composited_transform_smoke_test.dart | 2 +- .../goldens/ci/asset_image_smoke_test.png | Bin 0 -> 412 bytes test/smoke_tests/interactions_smoke_test.dart | 6 +- test/smoke_tests/timer_button_smoke_test.dart | 2 +- test/src/golden_test_adapter_test.dart | 24 ++- test/src/golden_test_runner_test.dart | 27 ++- test/src/golden_test_test.dart | 11 +- test/src/pumps_test.dart | 167 ++++++++++++++++++ 16 files changed, 302 insertions(+), 40 deletions(-) create mode 100644 test/helpers/fake_test_asset_bundle.dart create mode 100644 test/helpers/helpers.dart create mode 100644 test/smoke_tests/asset_image_smoke_test.dart create mode 100644 test/smoke_tests/goldens/ci/asset_image_smoke_test.png diff --git a/lib/src/golden_test.dart b/lib/src/golden_test.dart index fb85fa0..8cd007b 100644 --- a/lib/src/golden_test.dart +++ b/lib/src/golden_test.dart @@ -70,10 +70,9 @@ Future loadFonts() async { /// golden image under the `goldens` directory. This name should be unique, and /// may not contain an extension (such as `.png`). /// -/// The provided [widget] describes the scenarios and layout of the widgets that -/// are included in the test. A child must be provided. Alchemist provides two -/// widgets to make creating a golden test scenario. See [GoldenTestGroup] and -/// [GoldenTestScenario] for more details. +/// The provided [builder] builds the widget under test. +/// Usually, it creates multiple scenarios using [GoldenTestGroup] +/// and [GoldenTestScenario]. /// /// The [description] must be a unique description for the test. /// @@ -103,9 +102,9 @@ Future loadFonts() async { /// prime the widget tree before golden evaluation. By default, it is set to /// [onlyPumpAndSettle], which will pump the widget tree and wait for all /// scheduled frames to be completed, but can be overridden to customize the -/// pump behavior. For example, a button tap can be simulated using -/// `tester.tap(finder)`, after which the tester can be pumped and settled. -/// See [pumpOnce], [pumpNTimes] and [onlyPumpAndSettle] for more details. +/// pump behavior. +/// See [pumpOnce], [pumpNTimes], [onlyPumpAndSettle], and [precacheImages] for +/// more details. /// /// The [whilePerforming] interaction, if provided, will be called with the /// [WidgetTester] to perform a desired interaction during the golden test. @@ -131,7 +130,7 @@ Future goldenTest( BoxConstraints constraints = const BoxConstraints(), PumpAction pumpBeforeTest = onlyPumpAndSettle, Interaction? whilePerforming, - required Widget widget, + required ValueGetter builder, }) async { if (skip) return; @@ -163,7 +162,7 @@ Future goldenTest( fileName, goldensConfig.environmentName, ), - widget: widget, + widget: builder(), forceUpdate: config.forceUpdateGoldenFiles, obscureText: goldensConfig.obscureText, renderShadows: goldensConfig.renderShadows, diff --git a/lib/src/golden_test_adapter.dart b/lib/src/golden_test_adapter.dart index 2219467..64208f5 100644 --- a/lib/src/golden_test_adapter.dart +++ b/lib/src/golden_test_adapter.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:alchemist/src/blocked_text_image.dart'; +import 'package:alchemist/src/pumps.dart'; import 'package:alchemist/src/utilities.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -161,6 +162,7 @@ abstract class GoldenTestAdapter { required BoxConstraints constraints, required ThemeData theme, required Widget widget, + required PumpAction pumpBeforeTest, }); /// Generates an image of the widget at the given [finder] with all text @@ -220,6 +222,7 @@ class FlutterGoldenTestAdapter extends GoldenTestAdapter { required BoxConstraints constraints, required ThemeData theme, required Widget widget, + required PumpAction pumpBeforeTest, }) async { final initialSize = Size( constraints.hasBoundedWidth ? constraints.maxWidth : 2000, @@ -261,6 +264,8 @@ class FlutterGoldenTestAdapter extends GoldenTestAdapter { final shouldTryResize = !constraints.isTight; + await pumpBeforeTest(tester); + if (shouldTryResize) { final childSize = tester.getSize(find.byKey(childKey)); final newSize = Size( diff --git a/lib/src/golden_test_runner.dart b/lib/src/golden_test_runner.dart index 1fbb349..7a8d79f 100644 --- a/lib/src/golden_test_runner.dart +++ b/lib/src/golden_test_runner.dart @@ -84,6 +84,7 @@ class FlutterGoldenTestRunner extends GoldenTestRunner { rootKey: rootKey, textScaleFactor: textScaleFactor, constraints: constraints, + pumpBeforeTest: pumpBeforeTest, theme: themeData.copyWith( textTheme: obscureText ? themeData.textTheme.apply( @@ -94,8 +95,6 @@ class FlutterGoldenTestRunner extends GoldenTestRunner { widget: widget, ); - await pumpBeforeTest(tester); - AsyncCallback? cleanup; if (whilePerforming != null) { cleanup = await whilePerforming(tester); diff --git a/lib/src/pumps.dart b/lib/src/pumps.dart index bacf50c..543c354 100644 --- a/lib/src/pumps.dart +++ b/lib/src/pumps.dart @@ -1,4 +1,5 @@ import 'package:alchemist/alchemist.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; /// A function that may perform pumping actions to prime a golden test. @@ -30,3 +31,34 @@ final pumpOnce = pumpNTimes(1); /// /// See [PumpAction] for more details. Future onlyPumpAndSettle(WidgetTester tester) => tester.pumpAndSettle(); + +/// A custom pump action to ensure that the images for all [Image], +/// [FadeInImage], and [DecoratedBox] widgets are loaded before the golden file +/// is generated. +/// +/// See [PumpAction] for more details. +Future precacheImages(WidgetTester tester) async { + await tester.runAsync(() async { + final images = >[]; + for (final element in find.byType(Image).evaluate()) { + final widget = element.widget as Image; + final image = widget.image; + images.add(precacheImage(image, element)); + } + for (final element in find.byType(FadeInImage).evaluate()) { + final widget = element.widget as FadeInImage; + final image = widget.image; + images.add(precacheImage(image, element)); + } + for (final element in find.byType(DecoratedBox).evaluate()) { + final widget = element.widget as DecoratedBox; + final decoration = widget.decoration; + if (decoration is BoxDecoration && decoration.image != null) { + final image = decoration.image!.image; + images.add(precacheImage(image, element)); + } + } + await Future.wait(images); + }); + await tester.pumpAndSettle(); +} diff --git a/pubspec.yaml b/pubspec.yaml index f210e4f..6515066 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,8 @@ dependencies: meta: ^1.3.0 dev_dependencies: - mocktail: ^0.1.3 + mocktail: ^0.3.0 + mocktail_image_network: ^0.3.1 test: ^1.17.12 very_good_analysis: 2.4.0 diff --git a/test/helpers/fake_test_asset_bundle.dart b/test/helpers/fake_test_asset_bundle.dart new file mode 100644 index 0000000..d9f9d1d --- /dev/null +++ b/test/helpers/fake_test_asset_bundle.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:alchemist/src/utilities.dart'; + +final redPixelImage = base64Decode( + 'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAApElEQVR42u3RAQ0AAAjD' + 'MO5fNCCDkC5z0HTVrisFCBABASIgQAQEiIAAAQJEQIAICBABASIgQAREQIAICBABASIg' + 'QAREQIAICBABASIgQAREQIAICBABASIgQAREQIAICBABASIgQAREQIAICBABASIgQARE' + 'QIAICBABASIgQAREQIAICBABASIgQAREQIAICBABASIgQAQECBAgAgJEQIAIyPcGFY7H' + 'nV2aPXoAAAAASUVORK5CYII=', +); + +class FakeTestAssetBundle extends TestAssetBundle { + @override + Future load(String key) async { + if (key.endsWith('png')) { + return ByteData.view(redPixelImage.buffer); + } + return super.load(key); + } +} diff --git a/test/helpers/helpers.dart b/test/helpers/helpers.dart new file mode 100644 index 0000000..7ea7a9d --- /dev/null +++ b/test/helpers/helpers.dart @@ -0,0 +1 @@ +export 'fake_test_asset_bundle.dart'; diff --git a/test/smoke_tests/asset_image_smoke_test.dart b/test/smoke_tests/asset_image_smoke_test.dart new file mode 100644 index 0000000..e0b4646 --- /dev/null +++ b/test/smoke_tests/asset_image_smoke_test.dart @@ -0,0 +1,20 @@ +import 'package:alchemist/src/golden_test.dart'; +import 'package:alchemist/src/pumps.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../helpers/helpers.dart'; + +void main() { + group('smoke test', () { + goldenTest( + 'succeeds with an asset image', + fileName: 'asset_image_smoke_test', + pumpBeforeTest: precacheImages, + builder: () => DefaultAssetBundle( + bundle: FakeTestAssetBundle(), + child: Image.asset('test.png'), + ), + ); + }); +} diff --git a/test/smoke_tests/composited_transform_smoke_test.dart b/test/smoke_tests/composited_transform_smoke_test.dart index bcce0bb..ff6e66c 100644 --- a/test/smoke_tests/composited_transform_smoke_test.dart +++ b/test/smoke_tests/composited_transform_smoke_test.dart @@ -31,7 +31,7 @@ void main() { goldenTest( 'succeeds with CompositedTransformFollower', fileName: 'composited_transform_smoke_test', - widget: _SmokeTest(), + builder: _SmokeTest.new, ); }); } diff --git a/test/smoke_tests/goldens/ci/asset_image_smoke_test.png b/test/smoke_tests/goldens/ci/asset_image_smoke_test.png new file mode 100644 index 0000000000000000000000000000000000000000..a41750396f8b304cf1191871815f120c8dec6fe3 GIT binary patch literal 412 zcmeAS@N?(olHy`uVBq!ia0vp^B_Pbf1|&mfiKzoA#^NA%Cx&(BWL^R}EX7WqAsj$Z z!;#VflymcRaSW-L^Y-#V&O;6Y4uMl0tTyRJ8|LP^S6|}mX`gW5`QF!O5+4|U|2|{B zv}FHk{s$3F$2g>vGkO*nf*70M=KTC)7Z^4v4?i{%j#a23Odar)jNvuBd!1p?^) ZjK9jnIq9fZASfOfJYD@<);T3K0RX)^lc)dy literal 0 HcmV?d00001 diff --git a/test/smoke_tests/interactions_smoke_test.dart b/test/smoke_tests/interactions_smoke_test.dart index 935a869..2467452 100644 --- a/test/smoke_tests/interactions_smoke_test.dart +++ b/test/smoke_tests/interactions_smoke_test.dart @@ -26,21 +26,21 @@ void main() { goldenTest( 'succeeds in regular state', fileName: 'interactions_smoke_test_regular', - widget: buildSmokeTestGroup(), + builder: buildSmokeTestGroup, ); goldenTest( 'succeeds while pressed', fileName: 'interactions_smoke_test_pressed', whilePerforming: press(find.byType(ElevatedButton)), - widget: buildSmokeTestGroup(), + builder: buildSmokeTestGroup, ); goldenTest( 'succeeds while long pressed', fileName: 'interactions_smoke_test_long_pressed', whilePerforming: longPress(find.byType(ElevatedButton)), - widget: buildSmokeTestGroup(), + builder: buildSmokeTestGroup, ); }); } diff --git a/test/smoke_tests/timer_button_smoke_test.dart b/test/smoke_tests/timer_button_smoke_test.dart index 361fc1e..42f10ac 100644 --- a/test/smoke_tests/timer_button_smoke_test.dart +++ b/test/smoke_tests/timer_button_smoke_test.dart @@ -27,7 +27,7 @@ void main() { await tester.tap(find.byType(TimerButton)); await tester.pumpAndSettle(); }, - widget: const TimerButton(), + builder: () => const TimerButton(), ); }); } diff --git a/test/src/golden_test_adapter_test.dart b/test/src/golden_test_adapter_test.dart index 93840c9..1f44d2c 100644 --- a/test/src/golden_test_adapter_test.dart +++ b/test/src/golden_test_adapter_test.dart @@ -4,6 +4,7 @@ import 'package:alchemist/src/blocked_text_image.dart'; import 'package:alchemist/src/golden_test_adapter.dart'; import 'package:alchemist/src/golden_test_group.dart'; import 'package:alchemist/src/golden_test_scenario.dart'; +import 'package:alchemist/src/pumps.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -20,7 +21,7 @@ class MockRenderObject extends Mock implements RenderObject { void main() { setUpAll(() { - registerFallbackValue(MockRenderObject()); + registerFallbackValue(MockRenderObject()); }); group('overrides', () { group('goldenFileExpectationFn', () { @@ -213,6 +214,7 @@ void main() { textScaleFactor: 1, constraints: const BoxConstraints(), theme: ThemeData.light(), + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -233,6 +235,7 @@ void main() { textScaleFactor: 1, constraints: BoxConstraints.tight(providedSize), theme: ThemeData.light(), + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -256,6 +259,7 @@ void main() { textScaleFactor: 1, constraints: const BoxConstraints(), theme: ThemeData.light(), + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -286,6 +290,7 @@ void main() { minHeight: minSize.height, ), theme: ThemeData.light(), + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -326,6 +331,7 @@ void main() { maxHeight: maxSize.height, ), theme: ThemeData.light(), + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -342,6 +348,7 @@ void main() { textScaleFactor: 2, constraints: const BoxConstraints(), theme: ThemeData.light(), + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -366,6 +373,7 @@ void main() { textScaleFactor: 1, constraints: const BoxConstraints(), theme: theme, + pumpBeforeTest: onlyPumpAndSettle, widget: buildGroup(), ); @@ -397,6 +405,20 @@ void main() { ); }, ); + + testWidgets('calls the provided pumpBeforeTest', (tester) async { + var pumpBeforeTestCalled = false; + await adapter.pumpGoldenTest( + tester: tester, + textScaleFactor: 2, + constraints: const BoxConstraints(), + theme: ThemeData.light(), + pumpBeforeTest: (_) async => pumpBeforeTestCalled = true, + widget: buildGroup(), + ); + + expect(pumpBeforeTestCalled, isTrue); + }); }); }); } diff --git a/test/src/golden_test_runner_test.dart b/test/src/golden_test_runner_test.dart index fbe2530..3fd866d 100644 --- a/test/src/golden_test_runner_test.dart +++ b/test/src/golden_test_runner_test.dart @@ -15,11 +15,11 @@ class MockWidgetTester extends Mock implements WidgetTester {} void main() { setUpAll(() { - registerFallbackValue(MockWidgetTester()); - registerFallbackValue(const BoxConstraints()); - registerFallbackValue(ThemeData.light()); - registerFallbackValue(const SizedBox.square()); - registerFallbackValue(find.byType(Widget)); + registerFallbackValue(MockWidgetTester()); + registerFallbackValue(const BoxConstraints()); + registerFallbackValue(ThemeData.light()); + registerFallbackValue(const SizedBox.square()); + registerFallbackValue(find.byType(Widget)); }); group('Overrides', () { @@ -44,16 +44,11 @@ void main() { group('GoldenTestRunner', () { const goldenTestRunner = FlutterGoldenTestRunner(); late MockAdapter adapter; - var pumpBeforeTestCalled = false; var cleanupCalled = false; var interactionCalled = false; var goldenFileExpectationCalled = false; var matcherInvocationCalled = false; - Future pumpBeforeTest(WidgetTester tester) async { - pumpBeforeTestCalled = true; - } - Future cleanup() async { cleanupCalled = true; } @@ -79,7 +74,6 @@ void main() { adapter = MockAdapter(); goldenTestAdapter = adapter; - pumpBeforeTestCalled = false; cleanupCalled = false; interactionCalled = false; goldenFileExpectationCalled = false; @@ -92,6 +86,7 @@ void main() { textScaleFactor: any(named: 'textScaleFactor'), constraints: any(named: 'constraints'), theme: any(named: 'theme'), + pumpBeforeTest: any(named: 'pumpBeforeTest'), widget: any(named: 'widget'), ), ).thenAnswer((_) async {}); @@ -108,7 +103,7 @@ void main() { ).thenReturn(goldenFileExpectation); when( - () => goldenTestAdapter.withForceUpdateGoldenFiles( + () => goldenTestAdapter.withForceUpdateGoldenFiles( callback: any(named: 'callback'), ), ).thenAnswer((invocation) async { @@ -116,7 +111,6 @@ void main() { await (invocation.namedArguments[#callback] as MatchesGoldenFileInvocation) .call(); - return; }); }); @@ -141,12 +135,10 @@ void main() { goldenPath: 'path/to/golden', widget: Container(), theme: theme, - pumpBeforeTest: pumpBeforeTest, whilePerforming: interaction, obscureText: true, ); - expect(pumpBeforeTestCalled, isTrue); expect(interactionCalled, isTrue); expect(cleanupCalled, isTrue); expect(goldenFileExpectationCalled, isTrue); @@ -159,6 +151,7 @@ void main() { textScaleFactor: any(named: 'textScaleFactor'), constraints: any(named: 'constraints'), theme: captureAny(named: 'theme'), + pumpBeforeTest: any(named: 'pumpBeforeTest'), widget: any(named: 'widget'), ), ).captured.first as ThemeData; @@ -189,7 +182,6 @@ void main() { widget: Container(), ); - expect(pumpBeforeTestCalled, isFalse); expect(interactionCalled, isFalse); expect(cleanupCalled, isFalse); expect(goldenFileExpectationCalled, isTrue); @@ -202,6 +194,7 @@ void main() { textScaleFactor: any(named: 'textScaleFactor'), constraints: any(named: 'constraints'), theme: captureAny(named: 'theme'), + pumpBeforeTest: any(named: 'pumpBeforeTest'), widget: any(named: 'widget'), ), ).captured.first as ThemeData; @@ -250,7 +243,7 @@ void main() { goldenPath: 'path/to/golden', renderShadows: true, widget: Container(), - pumpBeforeTest: (_) async { + whilePerforming: (_) { debugDisableShadowsDuringTestRun = debugDisableShadows; throw givenException; }, diff --git a/test/src/golden_test_test.dart b/test/src/golden_test_test.dart index 7f959d4..86ef46c 100644 --- a/test/src/golden_test_test.dart +++ b/test/src/golden_test_test.dart @@ -39,6 +39,7 @@ class FakeGoldenTestAdapter extends Mock implements GoldenTestAdapter { required double textScaleFactor, required BoxConstraints constraints, required ThemeData theme, + required PumpAction pumpBeforeTest, required Widget widget, }) { return Future.value(); @@ -78,9 +79,9 @@ class FakeGoldenTestAdapter extends Mock implements GoldenTestAdapter { void main() { setUpAll(() { - registerFallbackValue(MockWidgetTester()); - registerFallbackValue(Container()); - registerFallbackValue(const BoxConstraints()); + registerFallbackValue(MockWidgetTester()); + registerFallbackValue(Container()); + registerFallbackValue(const BoxConstraints()); }); group('goldenTest', () { @@ -120,7 +121,7 @@ void main() { goldenTest( 'golden test test', fileName: 'test.png', - widget: Container(), + builder: () => const SizedBox(), ), throwsAssertionError, ); @@ -151,7 +152,7 @@ void main() { run: () async => goldenTest( 'test golden test', fileName: 'test_golden_test', - widget: widget, + builder: () => widget, ), ); expect(filePathResolverCalled, isTrue); diff --git a/test/src/pumps_test.dart b/test/src/pumps_test.dart index 62ac3b0..4df8989 100644 --- a/test/src/pumps_test.dart +++ b/test/src/pumps_test.dart @@ -1,9 +1,41 @@ import 'package:alchemist/alchemist.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import '../helpers/helpers.dart'; class MockWidgetTester extends Mock implements WidgetTester {} +Future _isCached(ImageProvider image, Finder findImage) async { + final cacheStatus = await image.obtainCacheStatus( + configuration: createLocalImageConfiguration( + findImage.evaluate().first, + ), + ); + return cacheStatus?.keepAlive ?? false; +} + +extension on CommonFinders { + Finder fadeInImage(ImageProvider image) => byWidgetPredicate( + (widget) => widget is FadeInImage && widget.image == image, + ); + + Finder decorationImage(ImageProvider image) => byWidgetPredicate( + (widget) { + if (widget is DecoratedBox) { + final decoration = widget.decoration; + if (decoration is BoxDecoration && + decoration.image?.image == image) { + return true; + } + } + return false; + }, + ); +} + void main() { group('Custom pump functions', () { late WidgetTester tester; @@ -52,5 +84,140 @@ void main() { verify(() => tester.pumpAndSettle()).called(1); }); }); + + group('precacheImages', () { + const networkImage = NetworkImage('https://fakeurl.com/image.png'); + const assetImage = AssetImage('path.png'); + final memoryImage = MemoryImage(redPixelImage); + + testWidgets( + 'caches all Image widgets', + (tester) => mockNetworkImages(() async { + await tester.pumpWidget( + DefaultAssetBundle( + bundle: FakeTestAssetBundle(), + child: Column( + children: [ + const Image( + image: networkImage, + ), + const Image( + image: assetImage, + ), + Image( + image: memoryImage, + ), + ], + ), + ), + ); + await precacheImages(tester); + + await expectLater( + _isCached(networkImage, find.image(networkImage)), + completion(isTrue), + ); + await expectLater( + _isCached(assetImage, find.image(assetImage)), + completion(isTrue), + ); + await expectLater( + _isCached(memoryImage, find.image(memoryImage)), + completion(isTrue), + ); + }), + ); + + testWidgets( + 'caches all FadeInImage widgets', + (tester) => mockNetworkImages(() async { + await tester.pumpWidget( + DefaultAssetBundle( + bundle: FakeTestAssetBundle(), + child: Column( + children: [ + FadeInImage( + image: networkImage, + placeholder: MemoryImage(redPixelImage), + ), + FadeInImage( + image: assetImage, + placeholder: MemoryImage(redPixelImage), + ), + FadeInImage( + image: memoryImage, + placeholder: MemoryImage(redPixelImage), + ), + ], + ), + ), + ); + await precacheImages(tester); + + await expectLater( + _isCached(networkImage, find.fadeInImage(networkImage)), + completion(isTrue), + ); + await expectLater( + _isCached(assetImage, find.fadeInImage(assetImage)), + completion(isTrue), + ); + await expectLater( + _isCached(memoryImage, find.fadeInImage(memoryImage)), + completion(isTrue), + ); + }), + ); + + testWidgets( + 'caches all DecoratedBox widgets', + (tester) => mockNetworkImages(() async { + await tester.pumpWidget( + DefaultAssetBundle( + bundle: FakeTestAssetBundle(), + child: Column( + children: [ + const DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: networkImage, + ), + ), + ), + const DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: assetImage, + ), + ), + ), + DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: memoryImage, + ), + ), + ), + ], + ), + ), + ); + await precacheImages(tester); + + await expectLater( + _isCached(networkImage, find.decorationImage(networkImage)), + completion(isTrue), + ); + await expectLater( + _isCached(assetImage, find.decorationImage(assetImage)), + completion(isTrue), + ); + await expectLater( + _isCached(memoryImage, find.decorationImage(memoryImage)), + completion(isTrue), + ); + }), + ); + }); }); }