image_provider_test.dart 7.72 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:io';
7 8
import 'dart:typed_data';
import 'dart:ui';
9

10
import 'package:file/memory.dart';
11
import 'package:flutter/foundation.dart';
12
import 'package:flutter/painting.dart';
13
import 'package:flutter/services.dart';
14 15
import 'package:flutter_test/flutter_test.dart';

16
import '../image_data.dart';
17
import '../rendering/rendering_tester.dart';
18
import 'mocks_for_image_cache.dart';
19

20
void main() {
21
  TestRenderingFlutterBinding.ensureInitialized();
22

23
  FlutterExceptionHandler? oldError;
24 25 26 27 28 29
  setUp(() {
    oldError = FlutterError.onError;
  });

  tearDown(() {
    FlutterError.onError = oldError;
30 31
    PaintingBinding.instance.imageCache.clear();
    PaintingBinding.instance.imageCache.clearLiveImages();
32 33
  });

34 35 36 37 38 39 40 41 42
  test('obtainKey errors will be caught', () async {
    final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
    final Completer<bool> caughtError = Completer<bool>();
    FlutterError.onError = (FlutterErrorDetails details) {
      caughtError.complete(false);
    };
    final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
    stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
      caughtError.complete(false);
43
    }, onError: (dynamic error, StackTrace? stackTrace) {
44 45 46 47
      caughtError.complete(true);
    }));
    expect(await caughtError.future, true);
  });
48

49 50 51 52 53 54 55
  test('obtainKey errors will be caught - check location', () async {
    final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
    final Completer<bool> caughtError = Completer<bool>();
    FlutterError.onError = (FlutterErrorDetails details) {
      caughtError.complete(true);
    };
    await imageProvider.obtainCacheStatus(configuration: ImageConfiguration.empty);
56

57 58
    expect(await caughtError.future, true);
  });
59

60 61 62 63 64 65 66 67 68
  test('resolve sync errors will be caught', () async {
    bool uncaught = false;
    final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
      handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
        uncaught = true;
      },
    ));
    await testZone.run(() async {
      final ImageProvider imageProvider = LoadErrorImageProvider();
69 70
      final Completer<bool> caughtError = Completer<bool>();
      FlutterError.onError = (FlutterErrorDetails details) {
71
        throw Error();
72
      };
73 74
      final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
      result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
75
      }, onError: (dynamic error, StackTrace? stackTrace) {
76
        caughtError.complete(true);
77
      }));
78 79
      expect(await caughtError.future, true);
    });
80 81
    expect(uncaught, false);
  });
82

83 84 85 86 87 88 89 90 91
  test('resolve errors in the completer will be caught', () async {
    bool uncaught = false;
    final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
      handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
        uncaught = true;
      },
    ));
    await testZone.run(() async {
      final ImageProvider imageProvider = LoadErrorCompleterImageProvider();
Dan Field's avatar
Dan Field committed
92
      final Completer<bool> caughtError = Completer<bool>();
93
      final Completer<bool> onErrorCompleter = Completer<bool>();
Dan Field's avatar
Dan Field committed
94
      FlutterError.onError = (FlutterErrorDetails details) {
95
        onErrorCompleter.complete(true);
96
        throw Error();
Dan Field's avatar
Dan Field committed
97
      };
98 99
      final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
      result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
100
      }, onError: (dynamic error, StackTrace? stackTrace) {
101 102
        caughtError.complete(true);
      }));
Dan Field's avatar
Dan Field committed
103
      expect(await caughtError.future, true);
104
      expect(await onErrorCompleter.future, true);
Dan Field's avatar
Dan Field committed
105
    });
106
    expect(uncaught, false);
107
  });
108

109 110 111 112
  test('File image with empty file throws expected error and evicts from cache', () async {
    final Completer<StateError> error = Completer<StateError>();
    FlutterError.onError = (FlutterErrorDetails details) {
      error.complete(details.exception as StateError);
113
    };
114 115 116
    final MemoryFileSystem fs = MemoryFileSystem();
    final File file = fs.file('/empty.png')..createSync(recursive: true);
    final FileImage provider = FileImage(file);
117

118 119
    expect(imageCache.statusForKey(provider).untracked, true);
    expect(imageCache.pendingImageCount, 0);
120

121
    provider.resolve(ImageConfiguration.empty);
122

123 124
    expect(imageCache.statusForKey(provider).pending, true);
    expect(imageCache.pendingImageCount, 1);
125

126
    expect(await error.future, isStateError);
127 128
    expect(imageCache.statusForKey(provider).untracked, true);
    expect(imageCache.pendingImageCount, 0);
129 130
  });

131 132 133 134 135 136 137 138 139
  test('File image with empty file throws expected error (load)', () async {
    final Completer<StateError> error = Completer<StateError>();
    FlutterError.onError = (FlutterErrorDetails details) {
      error.complete(details.exception as StateError);
    };
    final MemoryFileSystem fs = MemoryFileSystem();
    final File file = fs.file('/empty.png')..createSync(recursive: true);
    final FileImage provider = FileImage(file);

140
    expect(provider.load(provider, (Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
141 142
      return Future<Codec>.value(FakeCodec());
    }), isA<MultiFrameImageStreamCompleter>());
143 144 145

    expect(await error.future, isStateError);
  });
146

147
  Future<Codec> _decoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
148 149 150 151 152
    return FakeCodec();
  }

  test('File image sets tag', () async {
    final MemoryFileSystem fs = MemoryFileSystem();
153
    final File file = fs.file('/blue.png')..createSync(recursive: true)..writeAsBytesSync(kBlueSquarePng);
154 155 156 157 158 159 160 161
    final FileImage provider = FileImage(file);

    final MultiFrameImageStreamCompleter completer = provider.load(provider, _decoder) as MultiFrameImageStreamCompleter;

    expect(completer.debugLabel, file.path);
  });

  test('Memory image sets tag', () async {
162
    final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    final MemoryImage provider = MemoryImage(bytes);

    final MultiFrameImageStreamCompleter completer = provider.load(provider, _decoder) as MultiFrameImageStreamCompleter;

    expect(completer.debugLabel, 'MemoryImage(${describeIdentity(bytes)})');
  });

  test('Asset image sets tag', () async {
    const String asset = 'images/blue.png';
    final ExactAssetImage provider = ExactAssetImage(asset, bundle: _TestAssetBundle());
    final AssetBundleImageKey key = await provider.obtainKey(ImageConfiguration.empty);
    final MultiFrameImageStreamCompleter completer = provider.load(key, _decoder) as MultiFrameImageStreamCompleter;

    expect(completer.debugLabel, asset);
  });

  test('Resize image sets tag', () async {
180
    final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    final ResizeImage provider = ResizeImage(MemoryImage(bytes), width: 40, height: 40);
    final MultiFrameImageStreamCompleter completer = provider.load(
      await provider.obtainKey(ImageConfiguration.empty),
      _decoder,
    ) as MultiFrameImageStreamCompleter;

    expect(completer.debugLabel, 'MemoryImage(${describeIdentity(bytes)}) - Resized(40×40)');
  });
}

class FakeCodec implements Codec {
  @override
  void dispose() {}

  @override
  int get frameCount => throw UnimplementedError();

  @override
  Future<FrameInfo> getNextFrame() {
    throw UnimplementedError();
  }

  @override
  int get repetitionCount => throw UnimplementedError();
}

class _TestAssetBundle extends CachingAssetBundle {
  @override
  Future<ByteData> load(String key) async {
210
    return Uint8List.fromList(kBlueSquarePng).buffer.asByteData();
211
  }
212
}