image_stream_test.dart 25.2 KB
Newer Older
1 2 3 4 5
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

import 'package:flutter/painting.dart';
10 11
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter_test/flutter_test.dart';
12
import 'package:meta/meta.dart';
13

14
class FakeFrameInfo implements FrameInfo {
15 16
  FakeFrameInfo(int width, int height, this._duration)
    : _image = FakeImage(width, height);
17

18 19 20
  final Duration _duration;
  final Image _image;

21 22 23 24 25 26 27
  @override
  Duration get duration => _duration;

  @override
  Image get image => _image;
}

28
class FakeImage implements Image {
29 30
  FakeImage(this._width, this._height);

31 32 33 34 35 36 37 38 39 40
  final int _width;
  final int _height;

  @override
  int get width => _width;

  @override
  int get height => _height;

  @override
41
  void dispose() { }
42 43

  @override
44
  Future<ByteData> toByteData({ ImageByteFormat format = ImageByteFormat.rawRgba }) async {
45
    throw UnsupportedError('Cannot encode test image');
46
  }
47 48 49 50 51 52 53 54 55 56 57 58
}

class MockCodec implements Codec {

  @override
  int frameCount;

  @override
  int repetitionCount;

  int numFramesAsked = 0;

59
  Completer<FrameInfo> _nextFrameCompleter = Completer<FrameInfo>();
60 61 62 63 64 65 66 67 68

  @override
  Future<FrameInfo> getNextFrame() {
    numFramesAsked += 1;
    return _nextFrameCompleter.future;
  }

  void completeNextFrame(FrameInfo frameInfo) {
    _nextFrameCompleter.complete(frameInfo);
69
    _nextFrameCompleter = Completer<FrameInfo>();
70 71 72 73 74 75 76
  }

  void failNextFrame(String err) {
    _nextFrameCompleter.completeError(err);
  }

  @override
77
  void dispose() { }
78 79 80 81 82

}

void main() {
  testWidgets('Codec future fails', (WidgetTester tester) async {
83 84
    final Completer<Codec> completer = Completer<Codec>();
    MultiFrameImageStreamCompleter(
85 86 87 88 89 90 91 92
      codec: completer.future,
      scale: 1.0,
    );
    completer.completeError('failure message');
    await tester.idle();
    expect(tester.takeException(), 'failure message');
  });

93
  testWidgets('Decoding starts when a listener is added after codec is ready', (WidgetTester tester) async {
94 95
    final Completer<Codec> completer = Completer<Codec>();
    final MockCodec mockCodec = MockCodec();
96
    mockCodec.frameCount = 1;
97
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
98 99 100 101
      codec: completer.future,
      scale: 1.0,
    );

102 103 104 105 106
    completer.complete(mockCodec);
    await tester.idle();
    expect(mockCodec.numFramesAsked, 0);

    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
107
    imageStream.addListener(ImageStreamListener(listener));
108 109 110 111 112 113 114 115 116 117 118 119 120 121
    await tester.idle();
    expect(mockCodec.numFramesAsked, 1);
  });

  testWidgets('Decoding starts when a codec is ready after a listener is added', (WidgetTester tester) async {
    final Completer<Codec> completer = Completer<Codec>();
    final MockCodec mockCodec = MockCodec();
    mockCodec.frameCount = 1;
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
      codec: completer.future,
      scale: 1.0,
    );

    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
122
    imageStream.addListener(ImageStreamListener(listener));
123 124 125
    await tester.idle();
    expect(mockCodec.numFramesAsked, 0);

126 127 128 129 130
    completer.complete(mockCodec);
    await tester.idle();
    expect(mockCodec.numFramesAsked, 1);
  });

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 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 207 208 209
  testWidgets('Chunk events are delivered', (WidgetTester tester) async {
    final List<ImageChunkEvent> chunkEvents = <ImageChunkEvent>[];
    final Completer<Codec> completer = Completer<Codec>();
    final StreamController<ImageChunkEvent> streamController = StreamController<ImageChunkEvent>();
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
      codec: completer.future,
      chunkEvents: streamController.stream,
      scale: 1.0,
    );

    imageStream.addListener(ImageStreamListener(
      (ImageInfo image, bool synchronousCall) { },
      onChunk: (ImageChunkEvent event) {
        chunkEvents.add(event);
      },
    ));
    streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 1, expectedTotalBytes: 3));
    streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 2, expectedTotalBytes: 3));
    await tester.idle();

    expect(chunkEvents.length, 2);
    expect(chunkEvents[0].cumulativeBytesLoaded, 1);
    expect(chunkEvents[0].expectedTotalBytes, 3);
    expect(chunkEvents[1].cumulativeBytesLoaded, 2);
    expect(chunkEvents[1].expectedTotalBytes, 3);
  });

  testWidgets('Chunk events are not buffered before listener registration', (WidgetTester tester) async {
    final List<ImageChunkEvent> chunkEvents = <ImageChunkEvent>[];
    final Completer<Codec> completer = Completer<Codec>();
    final StreamController<ImageChunkEvent> streamController = StreamController<ImageChunkEvent>();
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
      codec: completer.future,
      chunkEvents: streamController.stream,
      scale: 1.0,
    );

    streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 1, expectedTotalBytes: 3));
    await tester.idle();
    imageStream.addListener(ImageStreamListener(
      (ImageInfo image, bool synchronousCall) { },
      onChunk: (ImageChunkEvent event) {
        chunkEvents.add(event);
      },
    ));
    streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 2, expectedTotalBytes: 3));
    await tester.idle();

    expect(chunkEvents.length, 1);
    expect(chunkEvents[0].cumulativeBytesLoaded, 2);
    expect(chunkEvents[0].expectedTotalBytes, 3);
  });

  testWidgets('Chunk errors are reported', (WidgetTester tester) async {
    final List<ImageChunkEvent> chunkEvents = <ImageChunkEvent>[];
    final Completer<Codec> completer = Completer<Codec>();
    final StreamController<ImageChunkEvent> streamController = StreamController<ImageChunkEvent>();
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
      codec: completer.future,
      chunkEvents: streamController.stream,
      scale: 1.0,
    );

    imageStream.addListener(ImageStreamListener(
      (ImageInfo image, bool synchronousCall) { },
      onChunk: (ImageChunkEvent event) {
        chunkEvents.add(event);
      },
    ));
    streamController.addError(Error());
    streamController.add(const ImageChunkEvent(cumulativeBytesLoaded: 2, expectedTotalBytes: 3));
    await tester.idle();

    expect(tester.takeException(), isNotNull);
    expect(chunkEvents.length, 1);
    expect(chunkEvents[0].cumulativeBytesLoaded, 2);
    expect(chunkEvents[0].expectedTotalBytes, 3);
  });

210
  testWidgets('getNextFrame future fails', (WidgetTester tester) async {
211 212 213 214
    final MockCodec mockCodec = MockCodec();
    mockCodec.frameCount = 1;
    final Completer<Codec> codecCompleter = Completer<Codec>();

215
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
216 217 218 219
      codec: codecCompleter.future,
      scale: 1.0,
    );

220
    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
221
    imageStream.addListener(ImageStreamListener(listener));
222 223 224 225 226 227 228 229 230 231 232 233
    codecCompleter.complete(mockCodec);
    // MultiFrameImageStreamCompleter only sets an error handler for the next
    // frame future after the codec future has completed.
    // Idling here lets the MultiFrameImageStreamCompleter advance and set the
    // error handler for the nextFrame future.
    await tester.idle();

    mockCodec.failNextFrame('frame completion error');
    await tester.idle();

    expect(tester.takeException(), 'frame completion error');
  });
234 235

  testWidgets('ImageStream emits frame (static image)', (WidgetTester tester) async {
236
    final MockCodec mockCodec = MockCodec();
237
    mockCodec.frameCount = 1;
238
    final Completer<Codec> codecCompleter = Completer<Codec>();
239

240
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
241 242 243 244 245
      codec: codecCompleter.future,
      scale: 1.0,
    );

    final List<ImageInfo> emittedImages = <ImageInfo>[];
246
    imageStream.addListener(ImageStreamListener((ImageInfo image, bool synchronousCall) {
247
      emittedImages.add(image);
248
    }));
249 250 251 252

    codecCompleter.complete(mockCodec);
    await tester.idle();

253
    final FrameInfo frame = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
254 255 256
    mockCodec.completeNextFrame(frame);
    await tester.idle();

257
    expect(emittedImages, equals(<ImageInfo>[ImageInfo(image: frame.image)]));
258 259
  });

260
  testWidgets('ImageStream emits frames (animated images)', (WidgetTester tester) async {
261
    final MockCodec mockCodec = MockCodec();
262 263
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = -1;
264
    final Completer<Codec> codecCompleter = Completer<Codec>();
265

266
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
267 268 269
      codec: codecCompleter.future,
      scale: 1.0,
    );
270

271
    final List<ImageInfo> emittedImages = <ImageInfo>[];
272
    imageStream.addListener(ImageStreamListener((ImageInfo image, bool synchronousCall) {
273
      emittedImages.add(image);
274
    }));
275

276 277
    codecCompleter.complete(mockCodec);
    await tester.idle();
278

279
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
280 281 282 283 284 285 286
    mockCodec.completeNextFrame(frame1);
    await tester.idle();
    // We are waiting for the next animation tick, so at this point no frames
    // should have been emitted.
    expect(emittedImages.length, 0);

    await tester.pump();
287
    expect(emittedImages, equals(<ImageInfo>[ImageInfo(image: frame1.image)]));
288

289
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
290 291 292 293 294 295 296 297 298
    mockCodec.completeNextFrame(frame2);

    await tester.pump(const Duration(milliseconds: 100));
    // The duration for the current frame was 200ms, so we don't emit the next
    // frame yet even though it is ready.
    expect(emittedImages.length, 1);

    await tester.pump(const Duration(milliseconds: 100));
    expect(emittedImages, equals(<ImageInfo>[
299 300
      ImageInfo(image: frame1.image),
      ImageInfo(image: frame2.image),
301 302 303 304 305 306
    ]));

    // Let the pending timer for the next frame to complete so we can cleanly
    // quit the test without pending timers.
    await tester.pump(const Duration(milliseconds: 400));
  });
307

308
  testWidgets('animation wraps back', (WidgetTester tester) async {
309
    final MockCodec mockCodec = MockCodec();
310 311
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = -1;
312
    final Completer<Codec> codecCompleter = Completer<Codec>();
313

314
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
315 316 317
      codec: codecCompleter.future,
      scale: 1.0,
    );
318

319
    final List<ImageInfo> emittedImages = <ImageInfo>[];
320
    imageStream.addListener(ImageStreamListener((ImageInfo image, bool synchronousCall) {
321
      emittedImages.add(image);
322
    }));
323

324 325
    codecCompleter.complete(mockCodec);
    await tester.idle();
326

327 328
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
329 330 331 332 333 334 335 336 337 338 339 340

    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.
    mockCodec.completeNextFrame(frame2);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(const Duration(milliseconds: 200)); // emit 2nd frame.
    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(const Duration(milliseconds: 400)); // emit 3rd frame

    expect(emittedImages, equals(<ImageInfo>[
341 342 343
      ImageInfo(image: frame1.image),
      ImageInfo(image: frame2.image),
      ImageInfo(image: frame1.image),
344 345 346 347 348 349
    ]));

    // Let the pending timer for the next frame to complete so we can cleanly
    // quit the test without pending timers.
    await tester.pump(const Duration(milliseconds: 200));
  });
350

351
  testWidgets('animation doesnt repeat more than specified', (WidgetTester tester) async {
352
    final MockCodec mockCodec = MockCodec();
353 354
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = 0;
355
    final Completer<Codec> codecCompleter = Completer<Codec>();
356

357
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
358 359 360
      codec: codecCompleter.future,
      scale: 1.0,
    );
361

362
    final List<ImageInfo> emittedImages = <ImageInfo>[];
363
    imageStream.addListener(ImageStreamListener((ImageInfo image, bool synchronousCall) {
364
      emittedImages.add(image);
365
    }));
366

367 368
    codecCompleter.complete(mockCodec);
    await tester.idle();
369

370 371
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
372 373 374 375 376 377 378 379 380 381 382 383

    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.
    mockCodec.completeNextFrame(frame2);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(const Duration(milliseconds: 200)); // emit 2nd frame.
    mockCodec.completeNextFrame(frame1);
    // allow another frame to complete (but we shouldn't be asking for it as
    // this animation should not repeat.
    await tester.idle();
    await tester.pump(const Duration(milliseconds: 400));
384

385
    expect(emittedImages, equals(<ImageInfo>[
386 387
      ImageInfo(image: frame1.image),
      ImageInfo(image: frame2.image),
388 389
    ]));
  });
390

391
  testWidgets('frames are only decoded when there are active listeners', (WidgetTester tester) async {
392
    final MockCodec mockCodec = MockCodec();
393 394
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = -1;
395
    final Completer<Codec> codecCompleter = Completer<Codec>();
396

397
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
398 399 400
      codec: codecCompleter.future,
      scale: 1.0,
    );
401

402
    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
403
    imageStream.addListener(ImageStreamListener(listener));
404

405 406
    codecCompleter.complete(mockCodec);
    await tester.idle();
407

408 409
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
410

411 412 413 414
    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.
    mockCodec.completeNextFrame(frame2);
415
    imageStream.removeListener(ImageStreamListener(listener));
416 417
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(const Duration(milliseconds: 400)); // emit 2nd frame.
418

419 420 421
    // Decoding of the 3rd frame should not start as there are no registered
    // listeners to the stream
    expect(mockCodec.numFramesAsked, 2);
422

423
    imageStream.addListener(ImageStreamListener(listener));
424 425 426
    await tester.idle(); // let nextFrameFuture complete
    expect(mockCodec.numFramesAsked, 3);
  });
427

428
  testWidgets('multiple stream listeners', (WidgetTester tester) async {
429
    final MockCodec mockCodec = MockCodec();
430 431
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = -1;
432
    final Completer<Codec> codecCompleter = Completer<Codec>();
433

434
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
435 436 437
      codec: codecCompleter.future,
      scale: 1.0,
    );
438

439 440 441 442 443 444 445 446
    final List<ImageInfo> emittedImages1 = <ImageInfo>[];
    final ImageListener listener1 = (ImageInfo image, bool synchronousCall) {
      emittedImages1.add(image);
    };
    final List<ImageInfo> emittedImages2 = <ImageInfo>[];
    final ImageListener listener2 = (ImageInfo image, bool synchronousCall) {
      emittedImages2.add(image);
    };
447 448
    imageStream.addListener(ImageStreamListener(listener1));
    imageStream.addListener(ImageStreamListener(listener2));
449

450 451
    codecCompleter.complete(mockCodec);
    await tester.idle();
452

453 454
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
455 456 457 458

    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.
459 460
    expect(emittedImages1, equals(<ImageInfo>[ImageInfo(image: frame1.image)]));
    expect(emittedImages2, equals(<ImageInfo>[ImageInfo(image: frame1.image)]));
461 462 463 464

    mockCodec.completeNextFrame(frame2);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // next app frame will schedule a timer.
465
    imageStream.removeListener(ImageStreamListener(listener1));
466 467

    await tester.pump(const Duration(milliseconds: 400)); // emit 2nd frame.
468
    expect(emittedImages1, equals(<ImageInfo>[ImageInfo(image: frame1.image)]));
469
    expect(emittedImages2, equals(<ImageInfo>[
470 471
      ImageInfo(image: frame1.image),
      ImageInfo(image: frame2.image),
472 473
    ]));
  });
474

475
  testWidgets('timer is canceled when listeners are removed', (WidgetTester tester) async {
476
    final MockCodec mockCodec = MockCodec();
477 478
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = -1;
479
    final Completer<Codec> codecCompleter = Completer<Codec>();
480

481
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
482 483 484
      codec: codecCompleter.future,
      scale: 1.0,
    );
485

486
    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
487
    imageStream.addListener(ImageStreamListener(listener));
488

489 490
    codecCompleter.complete(mockCodec);
    await tester.idle();
491

492 493
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
494

495 496 497
    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.
498

499 500 501
    mockCodec.completeNextFrame(frame2);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump();
502

503
    imageStream.removeListener(ImageStreamListener(listener));
504 505 506
    // The test framework will fail this if there are pending timers at this
    // point.
  });
507

508
  testWidgets('timeDilation affects animation frame timers', (WidgetTester tester) async {
509
    final MockCodec mockCodec = MockCodec();
510 511
    mockCodec.frameCount = 2;
    mockCodec.repetitionCount = -1;
512
    final Completer<Codec> codecCompleter = Completer<Codec>();
513

514
    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
515 516 517
      codec: codecCompleter.future,
      scale: 1.0,
    );
518

519
    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
520
    imageStream.addListener(ImageStreamListener(listener));
521

522 523
    codecCompleter.complete(mockCodec);
    await tester.idle();
524

525 526
    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
    final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.

    timeDilation = 2.0;
    mockCodec.completeNextFrame(frame2);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // schedule next app frame
    await tester.pump(const Duration(milliseconds: 200)); // emit 2nd frame.
    // Decoding of the 3rd frame should not start after 200 ms, as time is
    // dilated by a factor of 2.
    expect(mockCodec.numFramesAsked, 2);
    await tester.pump(const Duration(milliseconds: 200)); // emit 2nd frame.
    expect(mockCodec.numFramesAsked, 3);
  });
543

544
  testWidgets('error handlers can intercept errors', (WidgetTester tester) async {
545
    final MockCodec mockCodec = MockCodec();
546
    mockCodec.frameCount = 1;
547
    final Completer<Codec> codecCompleter = Completer<Codec>();
548

549
    final ImageStreamCompleter streamUnderTest = MultiFrameImageStreamCompleter(
550 551 552
      codec: codecCompleter.future,
      scale: 1.0,
    );
553

554 555 556 557 558
    dynamic capturedException;
    final ImageErrorListener errorListener = (dynamic exception, StackTrace stackTrace) {
      capturedException = exception;
    };

559
    streamUnderTest.addListener(ImageStreamListener(
560
      (ImageInfo image, bool synchronousCall) { },
561
      onError: errorListener,
562
    ));
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577

    codecCompleter.complete(mockCodec);
    // MultiFrameImageStreamCompleter only sets an error handler for the next
    // frame future after the codec future has completed.
    // Idling here lets the MultiFrameImageStreamCompleter advance and set the
    // error handler for the nextFrame future.
    await tester.idle();

    mockCodec.failNextFrame('frame completion error');
    await tester.idle();

    // No exception is passed up.
    expect(tester.takeException(), isNull);
    expect(capturedException, 'frame completion error');
  });
578 579 580 581 582 583 584 585 586 587 588 589 590

  testWidgets('remove and add listener ', (WidgetTester tester) async {
    final MockCodec mockCodec = MockCodec();
    mockCodec.frameCount = 3;
    mockCodec.repetitionCount = 0;
    final Completer<Codec> codecCompleter = Completer<Codec>();

    final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
      codec: codecCompleter.future,
      scale: 1.0,
    );

    final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
591
    imageStream.addListener(ImageStreamListener(listener));
592 593 594 595 596

    codecCompleter.complete(mockCodec);

    await tester.idle(); // let nextFrameFuture complete

597 598
    imageStream.removeListener(ImageStreamListener(listener));
    imageStream.addListener(ImageStreamListener(listener));
599 600 601 602 603 604 605 606 607 608 609


    final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));

    mockCodec.completeNextFrame(frame1);
    await tester.idle(); // let nextFrameFuture complete
    await tester.pump(); // first animation frame shows on first app frame.

    await tester.pump(const Duration(milliseconds: 200)); // emit 2nd frame.
  });

610
  testWidgets('ImageStreamListener hashCode and equals', (WidgetTester tester) async {
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
    void handleImage(ImageInfo image, bool synchronousCall) { }
    void handleImageDifferently(ImageInfo image, bool synchronousCall) { }
    void handleError(dynamic error, StackTrace stackTrace) { }
    void handleChunk(ImageChunkEvent event) { }

    void compare({
      @required ImageListener onImage1,
      @required ImageListener onImage2,
      ImageChunkListener onChunk1,
      ImageChunkListener onChunk2,
      ImageErrorListener onError1,
      ImageErrorListener onError2,
      bool areEqual = true,
    }) {
      final ImageStreamListener l1 = ImageStreamListener(onImage1, onChunk: onChunk1, onError: onError1);
      final ImageStreamListener l2 = ImageStreamListener(onImage2, onChunk: onChunk2, onError: onError2);
      Matcher comparison(dynamic expected) => areEqual ? equals(expected) : isNot(equals(expected));
      expect(l1, comparison(l2));
      expect(l1.hashCode, comparison(l2.hashCode));
    }

    compare(onImage1: handleImage, onImage2: handleImage);
    compare(onImage1: handleImage, onImage2: handleImageDifferently, areEqual: false);
    compare(onImage1: handleImage, onChunk1: handleChunk, onImage2: handleImage, onChunk2: handleChunk);
    compare(onImage1: handleImage, onChunk1: handleChunk, onError1: handleError, onImage2: handleImage, onChunk2: handleChunk, onError2: handleError);
    compare(onImage1: handleImage, onChunk1: handleChunk, onImage2: handleImage, areEqual: false);
    compare(onImage1: handleImage, onChunk1: handleChunk, onError1: handleError, onImage2: handleImage, areEqual: false);
    compare(onImage1: handleImage, onChunk1: handleChunk, onError1: handleError, onImage2: handleImage, onChunk2: handleChunk, areEqual: false);
    compare(onImage1: handleImage, onChunk1: handleChunk, onError1: handleError, onImage2: handleImage, onError2: handleError, areEqual: false);
  });

642 643 644 645 646 647 648 649 650 651 652 653 654 655
  // TODO(amirh): enable this once WidgetTester supports flushTimers.
  // https://github.com/flutter/flutter/issues/30344
  // testWidgets('remove and add listener before a delayed frame is scheduled', (WidgetTester tester) async {
  //   final MockCodec mockCodec = MockCodec();
  //   mockCodec.frameCount = 3;
  //   mockCodec.repetitionCount = 0;
  //   final Completer<Codec> codecCompleter = Completer<Codec>();
  //
  //   final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
  //     codec: codecCompleter.future,
  //     scale: 1.0,
  //   );
  //
  //   final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
656
  //   imageStream.addListener(ImageLoadingListener(listener));
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
  //
  //   codecCompleter.complete(mockCodec);
  //   await tester.idle();
  //
  //   final FrameInfo frame1 = FakeFrameInfo(20, 10, const Duration(milliseconds: 200));
  //   final FrameInfo frame2 = FakeFrameInfo(200, 100, const Duration(milliseconds: 400));
  //   final FrameInfo frame3 = FakeFrameInfo(200, 100, const Duration(milliseconds: 0));
  //
  //   mockCodec.completeNextFrame(frame1);
  //   await tester.idle(); // let nextFrameFuture complete
  //   await tester.pump(); // first animation frame shows on first app frame.
  //
  //   mockCodec.completeNextFrame(frame2);
  //   await tester.pump(const Duration(milliseconds: 100)); // emit 2nd frame.
  //
  //   tester.flushTimers();
  //
  //   imageStream.removeListener(listener);
675
  //   imageStream.addListener(ImageLoadingListener(listener));
676 677 678 679 680 681
  //
  //   mockCodec.completeNextFrame(frame3);
  //   await tester.idle(); // let nextFrameFuture complete
  //
  //   await tester.pump();
  // });
682
}