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

import 'package:flutter/widgets.dart';
6
import 'package:flutter_test/flutter_test.dart';
7

8 9
import '../scheduler/scheduler_tester.dart';

10
class BogusCurve extends Curve {
11 12
  const BogusCurve();

13 14 15 16
  @override
  double transform(double t) => 100.0;
}

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

  test('toString control test', () {
27 28 29
    expect(kAlwaysCompleteAnimation, hasOneLineDescription);
    expect(kAlwaysDismissedAnimation, hasOneLineDescription);
    expect(const AlwaysStoppedAnimation<double>(0.5), hasOneLineDescription);
30
    CurvedAnimation curvedAnimation = CurvedAnimation(
31
      parent: kAlwaysDismissedAnimation,
32
      curve: Curves.ease,
33
    );
34
    expect(curvedAnimation, hasOneLineDescription);
35
    curvedAnimation.reverseCurve = Curves.elasticOut;
36
    expect(curvedAnimation, hasOneLineDescription);
37
    final AnimationController controller = AnimationController(
38 39
      duration: const Duration(milliseconds: 500),
      vsync: const TestVSync(),
40 41 42 43
    );
    controller
      ..value = 0.5
      ..reverse();
44
    curvedAnimation = CurvedAnimation(
45 46
      parent: controller,
      curve: Curves.ease,
47
      reverseCurve: Curves.elasticOut,
48
    );
49
    expect(curvedAnimation, hasOneLineDescription);
50
    controller.stop();
51 52
  });

53
  test('ProxyAnimation.toString control test', () {
54
    final ProxyAnimation animation = ProxyAnimation();
55 56
    expect(animation.value, 0.0);
    expect(animation.status, AnimationStatus.dismissed);
57
    expect(animation, hasOneLineDescription);
58
    animation.parent = kAlwaysDismissedAnimation;
59
    expect(animation, hasOneLineDescription);
60
  });
61 62

  test('ProxyAnimation set parent generates value changed', () {
63
    final AnimationController controller = AnimationController(
64 65
      vsync: const TestVSync(),
    );
66 67
    controller.value = 0.5;
    bool didReceiveCallback = false;
68
    final ProxyAnimation animation = ProxyAnimation()
69 70 71 72 73 74 75 76 77 78 79 80 81
      ..addListener(() {
        didReceiveCallback = true;
      });
    expect(didReceiveCallback, isFalse);
    animation.parent = controller;
    expect(didReceiveCallback, isTrue);
    didReceiveCallback = false;
    expect(didReceiveCallback, isFalse);
    controller.value = 0.6;
    expect(didReceiveCallback, isTrue);
  });

  test('ReverseAnimation calls listeners', () {
82
    final AnimationController controller = AnimationController(
83 84
      vsync: const TestVSync(),
    );
85 86 87 88 89
    controller.value = 0.5;
    bool didReceiveCallback = false;
    void listener() {
      didReceiveCallback = true;
    }
90
    final ReverseAnimation animation = ReverseAnimation(controller)
91 92 93 94 95 96 97 98 99
      ..addListener(listener);
    expect(didReceiveCallback, isFalse);
    controller.value = 0.6;
    expect(didReceiveCallback, isTrue);
    didReceiveCallback = false;
    animation.removeListener(listener);
    expect(didReceiveCallback, isFalse);
    controller.value = 0.7;
    expect(didReceiveCallback, isFalse);
100
    expect(animation, hasOneLineDescription);
101 102 103
  });

  test('TrainHoppingAnimation', () {
104
    final AnimationController currentTrain = AnimationController(
105 106
      vsync: const TestVSync(),
    );
107
    final AnimationController nextTrain = AnimationController(
108 109
      vsync: const TestVSync(),
    );
110 111 112
    currentTrain.value = 0.5;
    nextTrain.value = 0.75;
    bool didSwitchTrains = false;
113
    final TrainHoppingAnimation animation = TrainHoppingAnimation(
114 115 116
      currentTrain,
      nextTrain,
      onSwitchedTrain: () {
117
        didSwitchTrains = true;
118 119
      },
    );
120 121
    expect(didSwitchTrains, isFalse);
    expect(animation.value, 0.5);
122
    expect(animation, hasOneLineDescription);
123 124 125
    nextTrain.value = 0.25;
    expect(didSwitchTrains, isTrue);
    expect(animation.value, 0.25);
126
    expect(animation, hasOneLineDescription);
127 128
    expect(animation.toString(), contains('no next'));
  });
129 130

  test('AnimationMean control test', () {
131
    final AnimationController left = AnimationController(
132 133 134
      value: 0.5,
      vsync: const TestVSync(),
    );
135
    final AnimationController right = AnimationController(
136 137 138
      vsync: const TestVSync(),
    );

139
    final AnimationMean mean = AnimationMean(left: left, right: right);
140 141 142 143

    expect(mean, hasOneLineDescription);
    expect(mean.value, equals(0.25));

144
    final List<double> log = <double>[];
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    void logValue() {
      log.add(mean.value);
    }

    mean.addListener(logValue);

    right.value = 1.0;

    expect(mean.value, equals(0.75));
    expect(log, equals(<double>[0.75]));
    log.clear();

    mean.removeListener(logValue);

    left.value = 0.0;

    expect(mean.value, equals(0.50));
    expect(log, isEmpty);
  });
164

165
  test('AnimationMax control test', () {
166
    final AnimationController first = AnimationController(
167 168 169
      value: 0.5,
      vsync: const TestVSync(),
    );
170
    final AnimationController second = AnimationController(
171 172 173
      vsync: const TestVSync(),
    );

174
    final AnimationMax<double> max = AnimationMax<double>(first, second);
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

    expect(max, hasOneLineDescription);
    expect(max.value, equals(0.5));

    final List<double> log = <double>[];
    void logValue() {
      log.add(max.value);
    }

    max.addListener(logValue);

    second.value = 1.0;

    expect(max.value, equals(1.0));
    expect(log, equals(<double>[1.0]));
    log.clear();

    max.removeListener(logValue);

    first.value = 0.0;

    expect(max.value, equals(1.0));
    expect(log, isEmpty);
  });

  test('AnimationMin control test', () {
201
    final AnimationController first = AnimationController(
202 203 204
      value: 0.5,
      vsync: const TestVSync(),
    );
205
    final AnimationController second = AnimationController(
206 207 208
      vsync: const TestVSync(),
    );

209
    final AnimationMin<double> min = AnimationMin<double>(first, second);
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234

    expect(min, hasOneLineDescription);
    expect(min.value, equals(0.0));

    final List<double> log = <double>[];
    void logValue() {
      log.add(min.value);
    }

    min.addListener(logValue);

    second.value = 1.0;

    expect(min.value, equals(0.5));
    expect(log, equals(<double>[0.5]));
    log.clear();

    min.removeListener(logValue);

    first.value = 0.25;

    expect(min.value, equals(0.25));
    expect(log, isEmpty);
  });

235
  test('CurvedAnimation with bogus curve', () {
236
    final AnimationController controller = AnimationController(
237 238
      vsync: const TestVSync(),
    );
239
    final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: const BogusCurve());
240
    FlutterError? error;
241 242 243 244 245 246
    try {
      curved.value;
    } on FlutterError catch (e) {
      error = e;
    }
    expect(error, isNotNull);
247 248
    expect(
      error!.toStringDeep(),
249 250 251
      // RegExp matcher is required here due to flutter web and flutter mobile generating
      // slightly different floating point numbers
      // in Flutter web 0.0 sometimes just appears as 0. or 0
252
      matches(RegExp(r'''
253
FlutterError
254 255 256
   Invalid curve endpoint at \d+(\.\d*)?\.
   Curves must map 0\.0 to near zero and 1\.0 to near one but
   BogusCurve mapped \d+(\.\d*)? to \d+(\.\d*)?, which is near \d+(\.\d*)?\.
257 258
''', multiLine: true)),
    );
259
  });
260

261 262 263 264 265 266 267 268 269
  test('CurvedAnimation running with different forward and reverse durations.', () {
    final AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 50),
      vsync: const TestVSync(),
    );
    final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: Curves.linear, reverseCurve: Curves.linear);

    controller.forward();
270
    tick(Duration.zero);
271
    tick(const Duration(milliseconds: 10));
272
    expect(curved.value, moreOrLessEquals(0.1));
273
    tick(const Duration(milliseconds: 20));
274
    expect(curved.value, moreOrLessEquals(0.2));
275
    tick(const Duration(milliseconds: 30));
276
    expect(curved.value, moreOrLessEquals(0.3));
277
    tick(const Duration(milliseconds: 40));
278
    expect(curved.value, moreOrLessEquals(0.4));
279
    tick(const Duration(milliseconds: 50));
280
    expect(curved.value, moreOrLessEquals(0.5));
281
    tick(const Duration(milliseconds: 60));
282
    expect(curved.value, moreOrLessEquals(0.6));
283
    tick(const Duration(milliseconds: 70));
284
    expect(curved.value, moreOrLessEquals(0.7));
285
    tick(const Duration(milliseconds: 80));
286
    expect(curved.value, moreOrLessEquals(0.8));
287
    tick(const Duration(milliseconds: 90));
288
    expect(curved.value, moreOrLessEquals(0.9));
289
    tick(const Duration(milliseconds: 100));
290
    expect(curved.value, moreOrLessEquals(1.0));
291 292
    controller.reverse();
    tick(const Duration(milliseconds: 110));
293
    expect(curved.value, moreOrLessEquals(1.0));
294
    tick(const Duration(milliseconds: 120));
295
    expect(curved.value, moreOrLessEquals(0.8));
296
    tick(const Duration(milliseconds: 130));
297
    expect(curved.value, moreOrLessEquals(0.6));
298
    tick(const Duration(milliseconds: 140));
299
    expect(curved.value, moreOrLessEquals(0.4));
300
    tick(const Duration(milliseconds: 150));
301
    expect(curved.value, moreOrLessEquals(0.2));
302
    tick(const Duration(milliseconds: 160));
303
    expect(curved.value, moreOrLessEquals(0.0));
304 305
  });

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
  test('CurvedAnimation stops listening to parent when disposed.', () async {
    const Interval forwardCurve = Interval(0.0, 0.5);
    const Interval reverseCurve = Interval(0.5, 1.0);

    final AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
    );
    final CurvedAnimation curved = CurvedAnimation(
        parent: controller, curve: forwardCurve, reverseCurve: reverseCurve);

    expect(forwardCurve.transform(0.5), 1.0);
    expect(reverseCurve.transform(0.5), 0.0);

    controller.forward(from: 0.5);
    expect(controller.status, equals(AnimationStatus.forward));
    expect(curved.value, equals(1.0));

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

    controller.reverse(from: 0.5);
    expect(controller.status, equals(AnimationStatus.reverse));
    expect(curved.value, equals(0.0));

    expect(curved.isDisposed, isFalse);
    curved.dispose();
    expect(curved.isDisposed, isTrue);

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

    controller.forward(from: 0.5);
    expect(controller.status, equals(AnimationStatus.forward));
    expect(curved.value, equals(0.0));
  });

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
  test('ReverseAnimation running with different forward and reverse durations.', () {
    final AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 50),
      vsync: const TestVSync(),
    );
    final ReverseAnimation reversed = ReverseAnimation(
      CurvedAnimation(
        parent: controller,
        curve: Curves.linear,
        reverseCurve: Curves.linear,
      ),
    );

    controller.forward();
359
    tick(Duration.zero);
360
    tick(const Duration(milliseconds: 10));
361
    expect(reversed.value, moreOrLessEquals(0.9));
362
    tick(const Duration(milliseconds: 20));
363
    expect(reversed.value, moreOrLessEquals(0.8));
364
    tick(const Duration(milliseconds: 30));
365
    expect(reversed.value, moreOrLessEquals(0.7));
366
    tick(const Duration(milliseconds: 40));
367
    expect(reversed.value, moreOrLessEquals(0.6));
368
    tick(const Duration(milliseconds: 50));
369
    expect(reversed.value, moreOrLessEquals(0.5));
370
    tick(const Duration(milliseconds: 60));
371
    expect(reversed.value, moreOrLessEquals(0.4));
372
    tick(const Duration(milliseconds: 70));
373
    expect(reversed.value, moreOrLessEquals(0.3));
374
    tick(const Duration(milliseconds: 80));
375
    expect(reversed.value, moreOrLessEquals(0.2));
376
    tick(const Duration(milliseconds: 90));
377
    expect(reversed.value, moreOrLessEquals(0.1));
378
    tick(const Duration(milliseconds: 100));
379
    expect(reversed.value, moreOrLessEquals(0.0));
380 381
    controller.reverse();
    tick(const Duration(milliseconds: 110));
382
    expect(reversed.value, moreOrLessEquals(0.0));
383
    tick(const Duration(milliseconds: 120));
384
    expect(reversed.value, moreOrLessEquals(0.2));
385
    tick(const Duration(milliseconds: 130));
386
    expect(reversed.value, moreOrLessEquals(0.4));
387
    tick(const Duration(milliseconds: 140));
388
    expect(reversed.value, moreOrLessEquals(0.6));
389
    tick(const Duration(milliseconds: 150));
390
    expect(reversed.value, moreOrLessEquals(0.8));
391
    tick(const Duration(milliseconds: 160));
392
    expect(reversed.value, moreOrLessEquals(1.0));
393 394
  });

395
  test('TweenSequence', () {
396
    final AnimationController controller = AnimationController(
397 398 399
      vsync: const TestVSync(),
    );

400
    final Animation<double> animation = TweenSequence<double>(
401
      <TweenSequenceItem<double>>[
402 403
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0),
404 405
          weight: 4.0,
        ),
406 407
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(10.0),
408 409
          weight: 2.0,
        ),
410 411
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 10.0, end: 5.0),
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
          weight: 4.0,
        ),
      ],
    ).animate(controller);

    expect(animation.value, 5.0);

    controller.value = 0.2;
    expect(animation.value, 7.5);

    controller.value = 0.4;
    expect(animation.value, 10.0);

    controller.value = 0.6;
    expect(animation.value, 10.0);

    controller.value = 0.8;
    expect(animation.value, 7.5);

    controller.value = 1.0;
    expect(animation.value, 5.0);
  });

  test('TweenSequence with curves', () {
436
    final AnimationController controller = AnimationController(
437 438 439
      vsync: const TestVSync(),
    );

440
    final Animation<double> animation = TweenSequence<double>(
441
      <TweenSequenceItem<double>>[
442 443 444
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0)
            .chain(CurveTween(curve: const Interval(0.5, 1.0))),
445 446
          weight: 4.0,
        ),
447 448 449
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(10.0)
            .chain(CurveTween(curve: Curves.linear)), // linear is a no-op
450 451
          weight: 2.0,
        ),
452 453 454
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 10.0, end: 5.0)
            .chain(CurveTween(curve: const Interval(0.0, 0.5))),
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
          weight: 4.0,
        ),
      ],
    ).animate(controller);

    expect(animation.value, 5.0);

    controller.value = 0.2;
    expect(animation.value, 5.0);

    controller.value = 0.4;
    expect(animation.value, 10.0);

    controller.value = 0.6;
    expect(animation.value, 10.0);

    controller.value = 0.8;
    expect(animation.value, 5.0);

    controller.value = 1.0;
    expect(animation.value, 5.0);
  });

  test('TweenSequence, one tween', () {
479
    final AnimationController controller = AnimationController(
480 481 482
      vsync: const TestVSync(),
    );

483
    final Animation<double> animation = TweenSequence<double>(
484
      <TweenSequenceItem<double>>[
485 486
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0),
487 488 489 490 491 492 493 494 495 496 497 498 499 500
          weight: 1.0,
        ),
      ],
    ).animate(controller);

    expect(animation.value, 5.0);

    controller.value = 0.5;
    expect(animation.value, 7.5);

    controller.value = 1.0;
    expect(animation.value, 10.0);
  });

501
}