image_provider_test.dart 7.56 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();
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 93
      final Completer<bool> caughtError = Completer<bool>();
      FlutterError.onError = (FlutterErrorDetails details) {
94
        throw Error();
Dan Field's avatar
Dan Field committed
95
      };
96 97
      final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
      result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
98
      }, onError: (dynamic error, StackTrace? stackTrace) {
99 100
        caughtError.complete(true);
      }));
Dan Field's avatar
Dan Field committed
101 102
      expect(await caughtError.future, true);
    });
103
    expect(uncaught, false);
104
  });
105

106 107 108 109
  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);
110
    };
111 112 113
    final MemoryFileSystem fs = MemoryFileSystem();
    final File file = fs.file('/empty.png')..createSync(recursive: true);
    final FileImage provider = FileImage(file);
114

115 116
    expect(imageCache!.statusForKey(provider).untracked, true);
    expect(imageCache!.pendingImageCount, 0);
117

118
    provider.resolve(ImageConfiguration.empty);
119

120 121
    expect(imageCache!.statusForKey(provider).pending, true);
    expect(imageCache!.pendingImageCount, 1);
122

123
    expect(await error.future, isStateError);
124 125
    expect(imageCache!.statusForKey(provider).untracked, true);
    expect(imageCache!.pendingImageCount, 0);
126 127
  });

128 129 130 131 132 133 134 135 136
  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);

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

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

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

  test('File image sets tag', () async {
    final MemoryFileSystem fs = MemoryFileSystem();
150
    final File file = fs.file('/blue.png')..createSync(recursive: true)..writeAsBytesSync(kBlueSquarePng);
151 152 153 154 155 156 157 158
    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 {
159
    final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    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 {
177
    final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
178 179 180 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
    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 {
207
    return Uint8List.fromList(kBlueSquarePng).buffer.asByteData();
208
  }
209
}