image_provider_test.dart 6.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
import 'dart:ui';
8

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

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

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

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

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

33 34 35 36 37 38 39 40 41
  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);
42
    }, onError: (dynamic error, StackTrace? stackTrace) {
43 44 45 46
      caughtError.complete(true);
    }));
    expect(await caughtError.future, true);
  });
47

48 49 50 51 52 53 54
  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);
55

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

59 60 61 62
  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);
63
    };
64 65 66
    final MemoryFileSystem fs = MemoryFileSystem();
    final File file = fs.file('/empty.png')..createSync(recursive: true);
    final FileImage provider = FileImage(file);
67

68 69
    expect(imageCache.statusForKey(provider).untracked, true);
    expect(imageCache.pendingImageCount, 0);
70

71
    provider.resolve(ImageConfiguration.empty);
72

73 74
    expect(imageCache.statusForKey(provider).pending, true);
    expect(imageCache.pendingImageCount, 1);
75

76
    expect(await error.future, isStateError);
77 78
    expect(imageCache.statusForKey(provider).untracked, true);
    expect(imageCache.pendingImageCount, 0);
79 80
  });

81 82 83 84 85 86 87 88 89
  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);

90
    expect(provider.loadBuffer(provider, (ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
91 92
      return Future<Codec>.value(FakeCodec());
    }), isA<MultiFrameImageStreamCompleter>());
93 94 95

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

97
  Future<Codec> decoder(ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
98 99 100 101 102
    return FakeCodec();
  }

  test('File image sets tag', () async {
    final MemoryFileSystem fs = MemoryFileSystem();
103
    final File file = fs.file('/blue.png')..createSync(recursive: true)..writeAsBytesSync(kBlueSquarePng);
104 105
    final FileImage provider = FileImage(file);

106
    final MultiFrameImageStreamCompleter completer = provider.loadBuffer(provider, decoder) as MultiFrameImageStreamCompleter;
107 108 109 110 111

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

  test('Memory image sets tag', () async {
112
    final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
113 114
    final MemoryImage provider = MemoryImage(bytes);

115
    final MultiFrameImageStreamCompleter completer = provider.loadBuffer(provider, decoder) as MultiFrameImageStreamCompleter;
116 117 118 119 120 121 122 123

    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);
124
    final MultiFrameImageStreamCompleter completer = provider.loadBuffer(key, decoder) as MultiFrameImageStreamCompleter;
125 126 127 128 129

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

  test('Resize image sets tag', () async {
130
    final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
131
    final ResizeImage provider = ResizeImage(MemoryImage(bytes), width: 40, height: 40);
132
    final MultiFrameImageStreamCompleter completer = provider.loadBuffer(
133
      await provider.obtainKey(ImageConfiguration.empty),
134
      decoder,
135 136 137 138
    ) as MultiFrameImageStreamCompleter;

    expect(completer.debugLabel, 'MemoryImage(${describeIdentity(bytes)}) - Resized(40×40)');
  });
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

  test('File image throws error when given a real but non-image file', () async {
    final Completer<Exception> error = Completer<Exception>();
    FlutterError.onError = (FlutterErrorDetails details) {
      error.complete(details.exception as Exception);
    };
    final FileImage provider = FileImage(File('pubspec.yaml'));

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

    provider.resolve(ImageConfiguration.empty);

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

    expect(await error.future, isException
      .having((Exception exception) => exception.toString(), 'toString', contains('Invalid image data')));

    // Invalid images are marked as pending so that we do not attempt to reload them.
    expect(imageCache.statusForKey(provider).untracked, false);
    expect(imageCache.pendingImageCount, 1);
  }, skip: kIsWeb); // [intended] The web cannot load files.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
}

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 {
183
    return Uint8List.fromList(kBlueSquarePng).buffer.asByteData();
184
  }
185
}