animation_controller_test.dart 36.3 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 'package:flutter/foundation.dart';
6
import 'package:flutter/physics.dart';
7
import 'package:flutter/scheduler.dart';
8
import 'package:flutter/semantics.dart';
9
import 'package:flutter/widgets.dart';
10
import 'package:flutter_test/flutter_test.dart';
11

12 13
import '../scheduler/scheduler_tester.dart';

14
void main() {
15
  setUp(() {
16
    WidgetsFlutterBinding.ensureInitialized();
17 18 19 20
    WidgetsBinding.instance
        ..resetEpoch()
        ..platformDispatcher.onBeginFrame = null
        ..platformDispatcher.onDrawFrame = null;
21 22 23
  });

  test('Can set value during status callback', () {
24
    final AnimationController controller = AnimationController(
25 26
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
    );
    bool didComplete = false;
    bool didDismiss = false;
    controller.addStatusListener((AnimationStatus status) {
      if (status == AnimationStatus.completed) {
        didComplete = true;
        controller.value = 0.0;
        controller.forward();
      } else if (status == AnimationStatus.dismissed) {
        didDismiss = true;
        controller.value = 0.0;
        controller.forward();
      }
    });

    controller.forward();
    expect(didComplete, isFalse);
    expect(didDismiss, isFalse);
45
    tick(const Duration(seconds: 1));
46 47
    expect(didComplete, isFalse);
    expect(didDismiss, isFalse);
48
    tick(const Duration(seconds: 2));
49 50
    expect(didComplete, isTrue);
    expect(didDismiss, isTrue);
51 52

    controller.stop();
53
    controller.dispose();
54 55
  });

56
  test('Receives status callbacks for forward and reverse', () {
57
    final AnimationController controller = AnimationController(
58 59
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
60
    );
61 62
    final List<double> valueLog = <double>[];
    final List<AnimationStatus> log = <AnimationStatus>[];
63
    controller
64
      ..addStatusListener(log.add)
65 66 67 68
      ..addListener(() {
        valueLog.add(controller.value);
      });

69 70
    expect(log, equals(<AnimationStatus>[]));
    expect(valueLog, equals(<AnimationStatus>[]));
71 72 73

    controller.forward();

74 75
    expect(log, equals(<AnimationStatus>[AnimationStatus.forward]));
    expect(valueLog, equals(<AnimationStatus>[]));
76 77 78

    controller.reverse();

79 80
    expect(log, equals(<AnimationStatus>[AnimationStatus.forward, AnimationStatus.dismissed]));
    expect(valueLog, equals(<AnimationStatus>[]));
81 82 83

    controller.reverse();

84 85
    expect(log, equals(<AnimationStatus>[AnimationStatus.forward, AnimationStatus.dismissed]));
    expect(valueLog, equals(<AnimationStatus>[]));
86 87 88 89

    log.clear();
    controller.forward();

90 91
    expect(log, equals(<AnimationStatus>[AnimationStatus.forward]));
    expect(valueLog, equals(<AnimationStatus>[]));
92 93 94

    controller.forward();

95 96
    expect(log, equals(<AnimationStatus>[AnimationStatus.forward]));
    expect(valueLog, equals(<AnimationStatus>[]));
97 98 99 100

    controller.reverse();
    log.clear();

101
    tick(const Duration(seconds: 10));
102 103
    expect(log, equals(<AnimationStatus>[]));
    expect(valueLog, equals(<AnimationStatus>[]));
104
    tick(const Duration(seconds: 20));
105 106
    expect(log, equals(<AnimationStatus>[]));
    expect(valueLog, equals(<AnimationStatus>[]));
107
    tick(const Duration(seconds: 30));
108 109
    expect(log, equals(<AnimationStatus>[]));
    expect(valueLog, equals(<AnimationStatus>[]));
110
    tick(const Duration(seconds: 40));
111 112
    expect(log, equals(<AnimationStatus>[]));
    expect(valueLog, equals(<AnimationStatus>[]));
113 114

    controller.stop();
115
    controller.dispose();
116
  });
117

118
  test('Forward and reverse from values', () {
119
    final AnimationController controller = AnimationController(
120 121
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
122
    );
123 124
    final List<double> valueLog = <double>[];
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
125
    controller
126
      ..addStatusListener(statusLog.add)
127 128 129 130 131
      ..addListener(() {
        valueLog.add(controller.value);
      });

    controller.reverse(from: 0.2);
132 133
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse ]));
    expect(valueLog, equals(<double>[ 0.2 ]));
134 135 136 137 138
    expect(controller.value, equals(0.2));
    statusLog.clear();
    valueLog.clear();

    controller.forward(from: 0.0);
139 140
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.dismissed, AnimationStatus.forward ]));
    expect(valueLog, equals(<double>[ 0.0 ]));
141
    expect(controller.value, equals(0.0));
142
    controller.dispose();
143
  });
144

145 146 147 148 149 150 151 152 153 154
  test('Forward and reverse with different durations', () {
    AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 50),
      vsync: const TestVSync(),
    );

    controller.forward();
    tick(const Duration(milliseconds: 10));
    tick(const Duration(milliseconds: 30));
155
    expect(controller.value, moreOrLessEquals(0.2));
156
    tick(const Duration(milliseconds: 60));
157
    expect(controller.value, moreOrLessEquals(0.5));
158
    tick(const Duration(milliseconds: 90));
159
    expect(controller.value, moreOrLessEquals(0.8));
160
    tick(const Duration(milliseconds: 120));
161
    expect(controller.value, moreOrLessEquals(1.0));
162 163 164 165 166
    controller.stop();

    controller.reverse();
    tick(const Duration(milliseconds: 210));
    tick(const Duration(milliseconds: 220));
167
    expect(controller.value, moreOrLessEquals(0.8));
168
    tick(const Duration(milliseconds: 230));
169
    expect(controller.value, moreOrLessEquals(0.6));
170
    tick(const Duration(milliseconds: 240));
171
    expect(controller.value, moreOrLessEquals(0.4));
172
    tick(const Duration(milliseconds: 260));
173
    expect(controller.value, moreOrLessEquals(0.0));
174 175 176 177 178 179 180 181 182 183 184 185
    controller.stop();

    // Swap which duration is longer.
    controller = AnimationController(
      duration: const Duration(milliseconds: 50),
      reverseDuration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
    );

    controller.forward();
    tick(const Duration(milliseconds: 10));
    tick(const Duration(milliseconds: 30));
186
    expect(controller.value, moreOrLessEquals(0.4));
187
    tick(const Duration(milliseconds: 60));
188
    expect(controller.value, moreOrLessEquals(1.0));
189
    tick(const Duration(milliseconds: 90));
190
    expect(controller.value, moreOrLessEquals(1.0));
191 192 193 194 195
    controller.stop();

    controller.reverse();
    tick(const Duration(milliseconds: 210));
    tick(const Duration(milliseconds: 220));
196
    expect(controller.value, moreOrLessEquals(0.9));
197
    tick(const Duration(milliseconds: 230));
198
    expect(controller.value, moreOrLessEquals(0.8));
199
    tick(const Duration(milliseconds: 240));
200
    expect(controller.value, moreOrLessEquals(0.7));
201
    tick(const Duration(milliseconds: 260));
202
    expect(controller.value, moreOrLessEquals(0.5));
203
    tick(const Duration(milliseconds: 310));
204
    expect(controller.value, moreOrLessEquals(0.0));
205
    controller.stop();
206
    controller.dispose();
207 208
  });

209
  test('Forward only from value', () {
210
    final AnimationController controller = AnimationController(
211 212
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
213
    );
214 215
    final List<double> valueLog = <double>[];
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
216
    controller
217
      ..addStatusListener(statusLog.add)
218 219 220 221 222 223 224 225
      ..addListener(() {
        valueLog.add(controller.value);
      });

    controller.forward(from: 0.2);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward ]));
    expect(valueLog, equals(<double>[ 0.2 ]));
    expect(controller.value, equals(0.2));
226
    controller.dispose();
227 228
  });

229
  test('Can fling to upper and lower bounds', () {
230
    final AnimationController controller = AnimationController(
231 232
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
233 234 235
    );

    controller.fling();
236 237
    tick(const Duration(seconds: 1));
    tick(const Duration(seconds: 2));
238 239 240
    expect(controller.value, 1.0);
    controller.stop();

241
    final AnimationController largeRangeController = AnimationController(
242 243
      duration: const Duration(milliseconds: 100),
      lowerBound: -30.0,
244 245
      upperBound: 45.0,
      vsync: const TestVSync(),
246 247 248
    );

    largeRangeController.fling();
249 250
    tick(const Duration(seconds: 3));
    tick(const Duration(seconds: 4));
251 252
    expect(largeRangeController.value, 45.0);
    largeRangeController.fling(velocity: -1.0);
253 254
    tick(const Duration(seconds: 5));
    tick(const Duration(seconds: 6));
255 256
    expect(largeRangeController.value, -30.0);
    largeRangeController.stop();
257 258
    controller.dispose();
    largeRangeController.dispose();
259
  });
260

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
  test('Custom springDescription can be applied', () {
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
    );
    final AnimationController customSpringController = AnimationController(
      vsync: const TestVSync(),
    );

    controller.fling();
    // Will produce longer and smoother animation than the default.
    customSpringController.fling(
      springDescription: SpringDescription.withDampingRatio(
        mass: 0.01,
        stiffness: 10.0,
        ratio: 2.0,
      ),
    );
278
    tick(Duration.zero);
279 280 281
    tick(const Duration(milliseconds: 50));

    expect(customSpringController.value < controller.value, true);
282 283
    controller.dispose();
    customSpringController.dispose();
284 285
  });

286
  test('lastElapsedDuration control test', () {
287
    final AnimationController controller = AnimationController(
288 289
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
290 291
    );
    controller.forward();
292 293 294
    tick(const Duration(milliseconds: 20));
    tick(const Duration(milliseconds: 30));
    tick(const Duration(milliseconds: 40));
295 296
    expect(controller.lastElapsedDuration, equals(const Duration(milliseconds: 20)));
    controller.stop();
297
    controller.dispose();
298
  });
299 300

  test('toString control test', () {
301
    final AnimationController controller = AnimationController(
302 303
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
304
    );
305
    expect(controller, hasOneLineDescription);
306
    controller.forward();
307 308
    tick(const Duration(milliseconds: 10));
    tick(const Duration(milliseconds: 20));
309
    expect(controller, hasOneLineDescription);
310
    tick(const Duration(milliseconds: 30));
311
    expect(controller, hasOneLineDescription);
312
    controller.reverse();
313 314
    tick(const Duration(milliseconds: 40));
    tick(const Duration(milliseconds: 50));
315
    expect(controller, hasOneLineDescription);
316
    controller.stop();
317
    controller.dispose();
318
  });
319 320

  test('velocity test - linear', () {
321
    final AnimationController controller = AnimationController(
322 323 324 325 326 327
      duration: const Duration(milliseconds: 1000),
      vsync: const TestVSync(),
    );

    // mid-flight
    controller.forward();
328
    tick(Duration.zero);
329
    tick(const Duration(milliseconds: 500));
330 331 332 333 334
    expect(controller.velocity, inInclusiveRange(0.9, 1.1));

    // edges
    controller.forward();
    expect(controller.velocity, inInclusiveRange(0.4, 0.6));
335
    tick(Duration.zero);
336
    expect(controller.velocity, inInclusiveRange(0.4, 0.6));
337
    tick(const Duration(milliseconds: 5));
338 339 340 341
    expect(controller.velocity, inInclusiveRange(0.9, 1.1));

    controller.forward(from: 0.5);
    expect(controller.velocity, inInclusiveRange(0.4, 0.6));
342
    tick(Duration.zero);
343
    expect(controller.velocity, inInclusiveRange(0.4, 0.6));
344
    tick(const Duration(milliseconds: 5));
345 346 347 348 349
    expect(controller.velocity, inInclusiveRange(0.9, 1.1));

    // stopped
    controller.forward(from: 1.0);
    expect(controller.velocity, 0.0);
350
    tick(Duration.zero);
351
    expect(controller.velocity, 0.0);
352
    tick(const Duration(milliseconds: 500));
353 354 355
    expect(controller.velocity, 0.0);

    controller.forward();
356
    tick(Duration.zero);
357
    tick(const Duration(milliseconds: 1000));
358 359 360
    expect(controller.velocity, 0.0);

    controller.stop();
361
    controller.dispose();
362
  });
363 364

  test('Disposed AnimationController toString works', () {
365
    final AnimationController controller = AnimationController(
366 367 368 369 370 371
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
    );
    controller.dispose();
    expect(controller, hasOneLineDescription);
  });
372 373

  test('AnimationController error handling', () {
374
    final AnimationController controller = AnimationController(
375 376 377 378 379 380 381 382 383
      vsync: const TestVSync(),
    );

    expect(controller.forward, throwsFlutterError);
    expect(controller.reverse, throwsFlutterError);
    expect(() { controller.animateTo(0.5); }, throwsFlutterError);
    expect(controller.repeat, throwsFlutterError);

    controller.dispose();
384
    FlutterError? result;
385 386 387 388 389 390 391
    try {
      controller.dispose();
    } on FlutterError catch (e) {
      result = e;
    }
    expect(result, isNotNull);
    expect(
392
      result!.toStringDeep(),
393 394 395 396 397 398
      equalsIgnoringHashCodes(
        'FlutterError\n'
        '   AnimationController.dispose() called more than once.\n'
        '   A given AnimationController cannot be disposed more than once.\n'
        '   The following AnimationController object was disposed multiple\n'
        '   times:\n'
399
        '     AnimationController#00000(⏮ 0.000; paused; DISPOSED)\n',
400 401 402 403 404 405 406
      ),
    );
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    result.debugFillProperties(builder);
    final DiagnosticsNode controllerProperty = builder.properties.last;
    expect(controllerProperty.name, 'The following AnimationController object was disposed multiple times');
    expect(controllerProperty.value, controller);
407
  });
408 409

  test('AnimationController repeat() throws if period is not specified', () {
410
    final AnimationController controller = AnimationController(
411 412
      vsync: const TestVSync(),
    );
413
    expect(() { controller.repeat(); }, throwsFlutterError);
414
    expect(() { controller.repeat(); }, throwsFlutterError);
415
    controller.dispose();
416 417
  });

418 419 420
  test('Do not animate if already at target', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];

421
    final AnimationController controller = AnimationController(
422 423 424 425 426 427 428 429
      value: 0.5,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, equals(0.5));
    controller.animateTo(0.5, duration: const Duration(milliseconds: 100));
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.completed ]));
    expect(controller.value, equals(0.5));
430
    controller.dispose();
431 432 433 434 435
  });

  test('Do not animate to upperBound if already at upperBound', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];

436
    final AnimationController controller = AnimationController(
437 438 439 440 441 442 443 444
      value: 1.0,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, equals(1.0));
    controller.animateTo(1.0, duration: const Duration(milliseconds: 100));
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.completed ]));
    expect(controller.value, equals(1.0));
445
    controller.dispose();
446 447 448 449 450
  });

  test('Do not animate to lowerBound if already at lowerBound', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];

451
    final AnimationController controller = AnimationController(
452 453 454 455 456 457 458 459
      value: 0.0,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, equals(0.0));
    controller.animateTo(0.0, duration: const Duration(milliseconds: 100));
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.completed ]));
    expect(controller.value, equals(0.0));
460
    controller.dispose();
461 462 463 464
  });

  test('Do not animate if already at target mid-flight (forward)', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
465
    final AnimationController controller = AnimationController(
466 467 468 469 470 471 472 473
      value: 0.0,
      duration: const Duration(milliseconds: 1000),
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, equals(0.0));

    controller.forward();
474
    tick(Duration.zero);
475 476 477 478 479 480 481 482
    tick(const Duration(milliseconds: 500));
    expect(controller.value, inInclusiveRange(0.4, 0.6));
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward ]));

    final double currentValue = controller.value;
    controller.animateTo(currentValue, duration: const Duration(milliseconds: 100));
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    expect(controller.value, currentValue);
483
    controller.dispose();
484 485 486 487
  });

  test('Do not animate if already at target mid-flight (reverse)', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
488
    final AnimationController controller = AnimationController(
489 490 491 492 493 494 495 496
      value: 1.0,
      duration: const Duration(milliseconds: 1000),
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, equals(1.0));

    controller.reverse();
497
    tick(Duration.zero);
498 499 500 501 502 503
    tick(const Duration(milliseconds: 500));
    expect(controller.value, inInclusiveRange(0.4, 0.6));
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse ]));

    final double currentValue = controller.value;
    controller.animateTo(currentValue, duration: const Duration(milliseconds: 100));
504
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse, AnimationStatus.completed ]));
505
    expect(controller.value, currentValue);
506
    controller.dispose();
507
  });
508

509
  test('animateTo can deal with duration == Duration.zero', () {
510
    final AnimationController controller = AnimationController(
511 512 513 514 515 516
      duration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
    );

    controller.forward(from: 0.2);
    expect(controller.value, 0.2);
517
    controller.animateTo(1.0, duration: Duration.zero);
518
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0), reason: 'Expected no animation.');
519
    expect(controller.value, 1.0);
520
    controller.dispose();
521
  });
522

523
  test('resetting animation works at all phases', () {
524
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
525
    final AnimationController controller = AnimationController(
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
      duration: const Duration(milliseconds: 100),
      value: 0.0,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);

    controller.reset();

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);

    statusLog.clear();
    controller.forward();
541
    tick(Duration.zero);
542 543 544 545 546 547 548 549 550 551 552
    tick(const Duration(milliseconds: 50));
    expect(controller.status, AnimationStatus.forward);
    controller.reset();

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.dismissed ]));

    controller.value = 1.0;
    statusLog.clear();
    controller.reverse();
553
    tick(Duration.zero);
554 555 556 557 558 559 560 561 562 563
    tick(const Duration(milliseconds: 50));
    expect(controller.status, AnimationStatus.reverse);
    controller.reset();

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse, AnimationStatus.dismissed ]));

    statusLog.clear();
    controller.forward();
564
    tick(Duration.zero);
565 566 567 568 569 570 571
    tick(const Duration(milliseconds: 150));
    expect(controller.status, AnimationStatus.completed);
    controller.reset();

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed, AnimationStatus.dismissed ]));
572
    controller.dispose();
573 574
  });

575
  test('setting value directly sets correct status', () {
576
    final AnimationController controller = AnimationController(
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
      value: 0.0,
      vsync: const TestVSync(),
    );

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);

    controller.value = 0.5;
    expect(controller.value, 0.5);
    expect(controller.status, AnimationStatus.forward);

    controller.value = 1.0;
    expect(controller.value, 1.0);
    expect(controller.status, AnimationStatus.completed);

    controller.value = 0.5;
    expect(controller.value, 0.5);
    expect(controller.status, AnimationStatus.forward);

    controller.value = 0.0;
    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);
599
    controller.dispose();
600 601 602 603
  });

  test('animateTo sets correct status', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
604
    final AnimationController controller = AnimationController(
605 606 607 608 609 610 611 612 613 614
      duration: const Duration(milliseconds: 100),
      value: 0.0,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);

    // Animate from 0.0 to 0.5
    controller.animateTo(0.5);
615
    tick(Duration.zero);
616 617 618 619 620 621 622
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 0.5);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();

    // Animate from 0.5 to 1.0
    controller.animateTo(1.0);
623
    tick(Duration.zero);
624 625 626 627 628 629 630
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 1.0);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();

    // Animate from 1.0 to 0.5
    controller.animateTo(0.5);
631
    tick(Duration.zero);
632 633 634 635 636 637 638
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 0.5);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();

    // Animate from 0.5 to 1.0
    controller.animateTo(0.0);
639
    tick(Duration.zero);
640 641 642 643
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 0.0);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();
644
    controller.dispose();
645 646 647 648
  });

  test('after a reverse call animateTo sets correct status', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
649
    final AnimationController controller = AnimationController(
650 651 652 653 654 655 656 657 658
      duration: const Duration(milliseconds: 100),
      value: 1.0,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, 1.0);
    expect(controller.status, AnimationStatus.completed);

    controller.reverse();
659
    tick(Duration.zero);
660 661 662 663 664 665
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 0.0);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse, AnimationStatus.dismissed ]));
    statusLog.clear();

    controller.animateTo(0.5);
666
    tick(Duration.zero);
667 668 669 670
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 0.5);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();
671
    controller.dispose();
672 673 674 675
  });

  test('after a forward call animateTo sets correct status', () {
    final List<AnimationStatus> statusLog = <AnimationStatus>[];
676
    final AnimationController controller = AnimationController(
677 678 679 680 681 682 683 684 685
      duration: const Duration(milliseconds: 100),
      value: 0.0,
      vsync: const TestVSync(),
    )..addStatusListener(statusLog.add);

    expect(controller.value, 0.0);
    expect(controller.status, AnimationStatus.dismissed);

    controller.forward();
686
    tick(Duration.zero);
687 688 689 690 691 692
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 1.0);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();

    controller.animateTo(0.5);
693
    tick(Duration.zero);
694 695 696 697
    tick(const Duration(milliseconds: 150));
    expect(controller.value, 0.5);
    expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
    statusLog.clear();
698
    controller.dispose();
699
  });
700

701 702 703 704 705 706 707 708 709
  test(
    'calling repeat with reverse set to true makes the animation alternate '
    'between lowerBound and upperBound values on each repeat',
    () {
      final AnimationController controller = AnimationController(
        duration: const Duration(milliseconds: 100),
        value: 0.0,
        vsync: const TestVSync(),
      );
710

711
      expect(controller.value, 0.0);
712

713
      controller.repeat(reverse: true);
714
      tick(Duration.zero);
715 716
      tick(const Duration(milliseconds: 25));
      expect(controller.value, 0.25);
717

718
      tick(Duration.zero);
719 720
      tick(const Duration(milliseconds: 125));
      expect(controller.value, 0.75);
721

722 723 724
      controller.reset();
      controller.value = 1.0;
      expect(controller.value, 1.0);
725

726
      controller.repeat(reverse: true);
727
      tick(Duration.zero);
728 729
      tick(const Duration(milliseconds: 25));
      expect(controller.value, 0.75);
730

731
      tick(Duration.zero);
732 733
      tick(const Duration(milliseconds: 125));
      expect(controller.value, 0.25);
734

735 736 737
      controller.reset();
      controller.value = 0.5;
      expect(controller.value, 0.5);
738

739
      controller.repeat(reverse: true);
740
      tick(Duration.zero);
741 742
      tick(const Duration(milliseconds: 50));
      expect(controller.value, 1.0);
743

744
      tick(Duration.zero);
745 746
      tick(const Duration(milliseconds: 150));
      expect(controller.value, 0.0);
747
      controller.dispose();
748 749
    },
  );
750

751 752 753 754 755 756 757 758 759
  test(
    'calling repeat with specified min and max values makes the animation '
    'alternate between min and max values on each repeat',
    () {
      final AnimationController controller = AnimationController(
        duration: const Duration(milliseconds: 100),
        value: 0.0,
        vsync: const TestVSync(),
      );
760

761
      expect(controller.value, 0.0);
762

763
      controller.repeat(reverse: true, min: 0.5, max: 1.0);
764
      tick(Duration.zero);
765 766
      tick(const Duration(milliseconds: 50));
      expect(controller.value, 0.75);
767

768
      tick(Duration.zero);
769 770
      tick(const Duration(milliseconds: 100));
      expect(controller.value, 1.00);
771

772
      tick(Duration.zero);
773 774
      tick(const Duration(milliseconds: 200));
      expect(controller.value, 0.5);
775

776 777 778
      controller.reset();
      controller.value = 0.0;
      expect(controller.value, 0.0);
779

780
      controller.repeat(reverse: true, min: 1.0, max: 1.0);
781
      tick(Duration.zero);
782 783
      tick(const Duration(milliseconds: 25));
      expect(controller.value, 1.0);
784

785
      tick(Duration.zero);
786 787
      tick(const Duration(milliseconds: 125));
      expect(controller.value, 1.0);
788
      controller.dispose();
789 790
    },
  );
791

792 793
  group('AnimationBehavior', () {
    test('Default values for constructor', () {
794
      final AnimationController controller = AnimationController(vsync: const TestVSync());
795 796
      expect(controller.animationBehavior, AnimationBehavior.normal);

797
      final AnimationController repeating = AnimationController.unbounded(vsync: const TestVSync());
798
      expect(repeating.animationBehavior, AnimationBehavior.preserve);
799 800
      controller.dispose();
      repeating.dispose();
801 802
    });

803
    test('AnimationBehavior.preserve runs at normal speed when animatingTo', () {
804
      debugSemanticsDisableAnimations = true;
805
      final AnimationController controller = AnimationController(
806
        vsync: const TestVSync(),
807 808 809 810 811 812 813
        animationBehavior: AnimationBehavior.preserve,
      );

      expect(controller.value, 0.0);
      expect(controller.status, AnimationStatus.dismissed);

      controller.animateTo(1.0, duration: const Duration(milliseconds: 100));
814
      tick(Duration.zero);
815 816 817 818 819
      tick(const Duration(milliseconds: 50));

      expect(controller.value, 0.5);
      expect(controller.status, AnimationStatus.forward);

820
      tick(Duration.zero);
821 822 823 824
      tick(const Duration(milliseconds: 150));

      expect(controller.value, 1.0);
      expect(controller.status, AnimationStatus.completed);
825
      debugSemanticsDisableAnimations = false;
826
      controller.dispose();
827 828
    });

829
    test('AnimationBehavior.normal runs at 20x speed when animatingTo', () {
830
      debugSemanticsDisableAnimations = true;
831
      final AnimationController controller = AnimationController(
832
        vsync: const TestVSync(),
833 834 835 836 837 838
      );

      expect(controller.value, 0.0);
      expect(controller.status, AnimationStatus.dismissed);

      controller.animateTo(1.0, duration: const Duration(milliseconds: 100));
839
      tick(Duration.zero);
840 841 842 843 844
      tick(const Duration(microseconds: 2500));

      expect(controller.value, 0.5);
      expect(controller.status, AnimationStatus.forward);

845
      tick(Duration.zero);
846 847 848 849
      tick(const Duration(milliseconds: 5, microseconds: 1000));

      expect(controller.value, 1.0);
      expect(controller.status, AnimationStatus.completed);
850
      debugSemanticsDisableAnimations = null;
851
      controller.dispose();
852 853
    });

854
    test('AnimationBehavior.normal runs "faster" than AnimationBehavior.preserve', () {
855
      debugSemanticsDisableAnimations = true;
856
      final AnimationController controller = AnimationController(
857
        vsync: const TestVSync(),
858
      );
859
      final AnimationController fastController = AnimationController(
860
        vsync: const TestVSync(),
861 862
      );

863 864
      controller.fling(animationBehavior: AnimationBehavior.preserve);
      fastController.fling(animationBehavior: AnimationBehavior.normal);
865
      tick(Duration.zero);
866 867 868 869
      tick(const Duration(milliseconds: 50));

      // We don't assert a specific faction that normal animation.
      expect(controller.value < fastController.value, true);
870
      debugSemanticsDisableAnimations = null;
871 872
      controller.dispose();
      fastController.dispose();
873 874
    });
  });
875 876 877 878 879 880 881 882 883 884 885 886 887 888

  test('AnimationController methods assert _ticker is not null', () {
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
    );

    controller.dispose();

    expect(() => controller.animateBack(0), throwsAssertionError);
    expect(() => controller.animateTo(0), throwsAssertionError);
    expect(() => controller.animateWith(GravitySimulation(0, 0, 0, 0)), throwsAssertionError);
    expect(() => controller.stop(), throwsAssertionError);
    expect(() => controller.forward(), throwsAssertionError);
    expect(() => controller.reverse(), throwsAssertionError);
889 890 891 892 893 894 895 896 897 898 899 900
  });

  test('Simulations run forward', () {
    final List<AnimationStatus> statuses = <AnimationStatus>[];
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
      duration: const Duration(seconds: 1),
    )..addStatusListener((AnimationStatus status) {
      statuses.add(status);
    });

    controller.animateWith(TestSimulation());
901
    tick(Duration.zero);
902 903
    tick(const Duration(seconds: 2));
    expect(statuses, <AnimationStatus>[AnimationStatus.forward]);
904
    controller.dispose();
905 906 907 908 909 910 911 912 913 914 915
  });

  test('Simulations run forward even after a reverse run', () {
    final List<AnimationStatus> statuses = <AnimationStatus>[];
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
      duration: const Duration(seconds: 1),
    )..addStatusListener((AnimationStatus status) {
      statuses.add(status);
    });
    controller.reverse(from: 1.0);
916
    tick(Duration.zero);
917 918 919
    tick(const Duration(seconds: 2));
    expect(statuses, <AnimationStatus>[AnimationStatus.completed, AnimationStatus.reverse, AnimationStatus.dismissed]);
    statuses.clear();
920

921
    controller.animateWith(TestSimulation());
922
    tick(Duration.zero);
923 924
    tick(const Duration(seconds: 2));
    expect(statuses, <AnimationStatus>[AnimationStatus.forward]);
925
    controller.dispose();
926
  });
927 928 929 930 931 932 933 934 935 936 937

  test('Repeating animation with reverse: true report as forward and reverse', () {
    final List<AnimationStatus> statuses = <AnimationStatus>[];
    final AnimationController controller = AnimationController(
      vsync: const TestVSync(),
      duration: const Duration(seconds: 1),
    )..addStatusListener((AnimationStatus status) {
      statuses.add(status);
    });

    controller.repeat(reverse: true);
938
    tick(Duration.zero);
939 940 941 942 943
    tick(const Duration(milliseconds: 999));
    expect(statuses, <AnimationStatus>[AnimationStatus.forward]);
    statuses.clear();
    tick(const Duration(seconds: 1));
    expect(statuses, <AnimationStatus>[AnimationStatus.reverse]);
944
    controller.dispose();
945
  });
946 947 948 949 950 951 952 953 954 955 956 957 958 959

  test('AnimateBack can runs successfully with just "reverseDuration" property set', () {
    final List<AnimationStatus> statuses = <AnimationStatus>[];
    final AnimationController controller = AnimationController(
      reverseDuration: const Duration(seconds: 2),
      vsync: const TestVSync(),
    )..addStatusListener((AnimationStatus status) {
      statuses.add(status);
    });

    controller.animateBack(0.8);

    expect(statuses, <AnimationStatus>[AnimationStatus.reverse]);
    statuses.clear();
960
    tick(Duration.zero);
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
    tick(const Duration(seconds: 2));
    expect(statuses, <AnimationStatus>[AnimationStatus.dismissed]);

    controller.dispose();
  });

  group('AnimationController "duration" error test', () {
    test('AnimationController forward() will throw an error if there is no default duration', () {
      final AnimationController controller = AnimationController(
        vsync: const TestVSync(),
      );

      late FlutterError error;
      try {
        controller.forward();
      } on FlutterError catch (e) {
        error = e;
      }

      expect(error, isNotNull);
      expect(
        error.toStringDeep(),
        'FlutterError\n'
984 985 986
        '   AnimationController.forward() called with no default duration.\n'
        '   The "duration" property should be set, either in the constructor\n'
        '   or later, before calling the forward() function.\n',
987 988 989 990 991
      );

      controller.dispose();
    });

992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
    test(
      'AnimationController animateTo() will throw an error if there is no explicit duration '
      'and default duration',
      () {
        final AnimationController controller = AnimationController(
          vsync: const TestVSync(),
        );

        late FlutterError error;
        try {
          controller.animateTo(0.8);
        } on FlutterError catch (e) {
          error = e;
        }

        expect(error, isNotNull);
        expect(
          error.toStringDeep(),
          'FlutterError\n'
1011 1012 1013 1014 1015
          '   AnimationController.animateTo() called with no explicit duration\n'
          '   and no default duration.\n'
          '   Either the "duration" argument to the animateTo() method should\n'
          '   be provided, or the "duration" property should be set, either in\n'
          '   the constructor or later, before calling the animateTo()\n'
1016 1017
          '   function.\n',
        );
1018

1019 1020 1021
        controller.dispose();
      },
    );
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038

    test('AnimationController reverse() will throw an error if there is no default duration or reverseDuration', () {
      final AnimationController controller = AnimationController(
        vsync: const TestVSync(),
      );

      late FlutterError error;
      try {
        controller.reverse();
      } on FlutterError catch (e) {
        error = e;
      }

      expect(error, isNotNull);
      expect(
        error.toStringDeep(),
        'FlutterError\n'
1039 1040 1041 1042 1043
        '   AnimationController.reverse() called with no default duration or\n'
        '   reverseDuration.\n'
        '   The "duration" or "reverseDuration" property should be set,\n'
        '   either in the constructor or later, before calling the reverse()\n'
        '   function.\n',
1044 1045 1046 1047 1048
      );

      controller.dispose();
    });

1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
    test(
      'AnimationController animateBack() will throw an error if there is no explicit duration and '
      'no default duration or reverseDuration',
      () {
        final AnimationController controller = AnimationController(
          vsync: const TestVSync(),
        );

        late FlutterError error;
        try {
          controller.animateBack(0.8);
        } on FlutterError catch (e) {
          error = e;
        }

        expect(error, isNotNull);
        expect(
          error.toStringDeep(),
          'FlutterError\n'
1068 1069 1070 1071 1072
          '   AnimationController.animateBack() called with no explicit\n'
          '   duration and no default duration or reverseDuration.\n'
          '   Either the "duration" argument to the animateBack() method should\n'
          '   be provided, or the "duration" or "reverseDuration" property\n'
          '   should be set, either in the constructor or later, before calling\n'
1073 1074
          '   the animateBack() function.\n',
        );
1075

1076 1077 1078
        controller.dispose();
      },
    );
1079 1080
  });

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
}

class TestSimulation extends Simulation {
  @override
  double dx(double time) => time;

  @override
  bool isDone(double time) => false;

  @override
  double x(double time) => time;
1092
}