diff --git a/lib/src/layer/tile_layer/tile_image.dart b/lib/src/layer/tile_layer/tile_image.dart index 71fe06a54..c77e4cb32 100644 --- a/lib/src/layer/tile_layer/tile_image.dart +++ b/lib/src/layer/tile_layer/tile_image.dart @@ -176,15 +176,17 @@ class TileImage extends ChangeNotifier { } } - // Initiates fading in and marks this TileImage as readyToDisplay when fading - // finishes. If fading is disabled or a loading error occurred this TileImage - // becomes readyToDisplay immediately. + // Called once loading has settled, successfully or not. Initiates fading in + // and marks this TileImage as readyToDisplay when fading finishes; immediate + // if fading is disabled, or if loading failed and there is an errorImage to + // paint instead. A failure with nothing to paint never becomes + // readyToDisplay. void _display() { final previouslyLoaded = loadFinishedAt != null; loadFinishedAt = DateTime.now(); if (loadError) { - _readyToDisplay = true; + _readyToDisplay = errorImage != null; if (!_disposed) notifyListeners(); return; } diff --git a/test/layer/tile_layer/tile_image_test.dart b/test/layer/tile_layer/tile_image_test.dart new file mode 100644 index 000000000..9948370de --- /dev/null +++ b/test/layer/tile_layer/tile_image_test.dart @@ -0,0 +1,60 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../test_utils/test_tile_image.dart'; + +void main() { + Future loadFailingTile({ImageProvider? errorImage}) { + final loaded = Completer(); + final tileImage = TileImage( + coordinates: const TileCoordinates(0, 0, 0), + vsync: const _TestTickerProvider(), + imageProvider: _FailingImageProvider(), + onLoadComplete: (_) => loaded.complete(), + onLoadError: (_, __, ___) {}, + tileDisplay: const TileDisplay.instantaneous(), + cancelLoading: Completer(), + errorImage: errorImage, + )..load(); + return loaded.future.then((_) => tileImage); + } + + test('a failed tile with no error image is not ready to display', () async { + final tileImage = await loadFailingTile(); + + expect(tileImage.loadError, isTrue); + expect(tileImage.readyToDisplay, isFalse); + }); + + test('a failed tile with an error image is ready to display', () async { + final tileImage = await loadFailingTile(errorImage: testWhiteTileImage); + + expect(tileImage.loadError, isTrue); + expect(tileImage.readyToDisplay, isTrue); + }); +} + +class _FailingImageProvider extends ImageProvider<_FailingImageProvider> { + @override + Future<_FailingImageProvider> obtainKey(ImageConfiguration configuration) => + SynchronousFuture(this); + + @override + ImageStreamCompleter loadImage( + _FailingImageProvider key, + ImageDecoderCallback decode, + ) => + OneFrameImageStreamCompleter(Future.error(Exception('tile unavailable'))); +} + +class _TestTickerProvider implements TickerProvider { + const _TestTickerProvider(); + + @override + Ticker createTicker(TickerCallback onTick) => Ticker((_) {}); +}