| // Copyright 2014 The Flutter 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 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../scheduler/scheduler_tester.dart'; |
| |
| class BogusCurve extends Curve { |
| const BogusCurve(); |
| |
| @override |
| double transform(double t) => 100.0; |
| } |
| |
| void main() { |
| setUp(() { |
| WidgetsFlutterBinding.ensureInitialized(); |
| WidgetsBinding.instance |
| ..resetEpoch() |
| ..platformDispatcher.onBeginFrame = null |
| ..platformDispatcher.onDrawFrame = null; |
| }); |
| |
| test('toString control test', () { |
| expect(kAlwaysCompleteAnimation, hasOneLineDescription); |
| expect(kAlwaysDismissedAnimation, hasOneLineDescription); |
| expect(const AlwaysStoppedAnimation<double>(0.5), hasOneLineDescription); |
| CurvedAnimation curvedAnimation = CurvedAnimation( |
| parent: kAlwaysDismissedAnimation, |
| curve: Curves.ease, |
| ); |
| expect(curvedAnimation, hasOneLineDescription); |
| curvedAnimation.reverseCurve = Curves.elasticOut; |
| expect(curvedAnimation, hasOneLineDescription); |
| final AnimationController controller = AnimationController( |
| duration: const Duration(milliseconds: 500), |
| vsync: const TestVSync(), |
| ); |
| controller |
| ..value = 0.5 |
| ..reverse(); |
| curvedAnimation = CurvedAnimation( |
| parent: controller, |
| curve: Curves.ease, |
| reverseCurve: Curves.elasticOut, |
| ); |
| expect(curvedAnimation, hasOneLineDescription); |
| controller.stop(); |
| }); |
| |
| test('ProxyAnimation.toString control test', () { |
| final ProxyAnimation animation = ProxyAnimation(); |
| expect(animation.value, 0.0); |
| expect(animation.status, AnimationStatus.dismissed); |
| expect(animation, hasOneLineDescription); |
| animation.parent = kAlwaysDismissedAnimation; |
| expect(animation, hasOneLineDescription); |
| }); |
| |
| test('ProxyAnimation set parent generates value changed', () { |
| final AnimationController controller = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| controller.value = 0.5; |
| bool didReceiveCallback = false; |
| final ProxyAnimation animation = ProxyAnimation() |
| ..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', () { |
| final AnimationController controller = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| controller.value = 0.5; |
| bool didReceiveCallback = false; |
| void listener() { |
| didReceiveCallback = true; |
| } |
| final ReverseAnimation animation = ReverseAnimation(controller) |
| ..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); |
| expect(animation, hasOneLineDescription); |
| }); |
| |
| test('TrainHoppingAnimation', () { |
| final AnimationController currentTrain = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| final AnimationController nextTrain = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| currentTrain.value = 0.5; |
| nextTrain.value = 0.75; |
| bool didSwitchTrains = false; |
| final TrainHoppingAnimation animation = TrainHoppingAnimation( |
| currentTrain, |
| nextTrain, |
| onSwitchedTrain: () { |
| didSwitchTrains = true; |
| }, |
| ); |
| expect(didSwitchTrains, isFalse); |
| expect(animation.value, 0.5); |
| expect(animation, hasOneLineDescription); |
| nextTrain.value = 0.25; |
| expect(didSwitchTrains, isTrue); |
| expect(animation.value, 0.25); |
| expect(animation, hasOneLineDescription); |
| expect(animation.toString(), contains('no next')); |
| }); |
| |
| test('AnimationMean control test', () { |
| final AnimationController left = AnimationController( |
| value: 0.5, |
| vsync: const TestVSync(), |
| ); |
| final AnimationController right = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| final AnimationMean mean = AnimationMean(left: left, right: right); |
| |
| expect(mean, hasOneLineDescription); |
| expect(mean.value, equals(0.25)); |
| |
| final List<double> log = <double>[]; |
| 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); |
| }); |
| |
| test('AnimationMax control test', () { |
| final AnimationController first = AnimationController( |
| value: 0.5, |
| vsync: const TestVSync(), |
| ); |
| final AnimationController second = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| final AnimationMax<double> max = AnimationMax<double>(first, second); |
| |
| 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', () { |
| final AnimationController first = AnimationController( |
| value: 0.5, |
| vsync: const TestVSync(), |
| ); |
| final AnimationController second = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| final AnimationMin<double> min = AnimationMin<double>(first, second); |
| |
| 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); |
| }); |
| |
| test('CurvedAnimation with bogus curve', () { |
| final AnimationController controller = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: const BogusCurve()); |
| FlutterError? error; |
| try { |
| curved.value; |
| } on FlutterError catch (e) { |
| error = e; |
| } |
| expect(error, isNotNull); |
| expect( |
| error!.toStringDeep(), |
| // 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 |
| matches(RegExp(r''' |
| FlutterError |
| 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*)?\. |
| ''', multiLine: true)), |
| ); |
| }); |
| |
| 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(); |
| tick(Duration.zero); |
| tick(const Duration(milliseconds: 10)); |
| expect(curved.value, moreOrLessEquals(0.1)); |
| tick(const Duration(milliseconds: 20)); |
| expect(curved.value, moreOrLessEquals(0.2)); |
| tick(const Duration(milliseconds: 30)); |
| expect(curved.value, moreOrLessEquals(0.3)); |
| tick(const Duration(milliseconds: 40)); |
| expect(curved.value, moreOrLessEquals(0.4)); |
| tick(const Duration(milliseconds: 50)); |
| expect(curved.value, moreOrLessEquals(0.5)); |
| tick(const Duration(milliseconds: 60)); |
| expect(curved.value, moreOrLessEquals(0.6)); |
| tick(const Duration(milliseconds: 70)); |
| expect(curved.value, moreOrLessEquals(0.7)); |
| tick(const Duration(milliseconds: 80)); |
| expect(curved.value, moreOrLessEquals(0.8)); |
| tick(const Duration(milliseconds: 90)); |
| expect(curved.value, moreOrLessEquals(0.9)); |
| tick(const Duration(milliseconds: 100)); |
| expect(curved.value, moreOrLessEquals(1.0)); |
| controller.reverse(); |
| tick(const Duration(milliseconds: 110)); |
| expect(curved.value, moreOrLessEquals(1.0)); |
| tick(const Duration(milliseconds: 120)); |
| expect(curved.value, moreOrLessEquals(0.8)); |
| tick(const Duration(milliseconds: 130)); |
| expect(curved.value, moreOrLessEquals(0.6)); |
| tick(const Duration(milliseconds: 140)); |
| expect(curved.value, moreOrLessEquals(0.4)); |
| tick(const Duration(milliseconds: 150)); |
| expect(curved.value, moreOrLessEquals(0.2)); |
| tick(const Duration(milliseconds: 160)); |
| expect(curved.value, moreOrLessEquals(0.0)); |
| }); |
| |
| 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)); |
| }); |
| |
| 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(); |
| tick(Duration.zero); |
| tick(const Duration(milliseconds: 10)); |
| expect(reversed.value, moreOrLessEquals(0.9)); |
| tick(const Duration(milliseconds: 20)); |
| expect(reversed.value, moreOrLessEquals(0.8)); |
| tick(const Duration(milliseconds: 30)); |
| expect(reversed.value, moreOrLessEquals(0.7)); |
| tick(const Duration(milliseconds: 40)); |
| expect(reversed.value, moreOrLessEquals(0.6)); |
| tick(const Duration(milliseconds: 50)); |
| expect(reversed.value, moreOrLessEquals(0.5)); |
| tick(const Duration(milliseconds: 60)); |
| expect(reversed.value, moreOrLessEquals(0.4)); |
| tick(const Duration(milliseconds: 70)); |
| expect(reversed.value, moreOrLessEquals(0.3)); |
| tick(const Duration(milliseconds: 80)); |
| expect(reversed.value, moreOrLessEquals(0.2)); |
| tick(const Duration(milliseconds: 90)); |
| expect(reversed.value, moreOrLessEquals(0.1)); |
| tick(const Duration(milliseconds: 100)); |
| expect(reversed.value, moreOrLessEquals(0.0)); |
| controller.reverse(); |
| tick(const Duration(milliseconds: 110)); |
| expect(reversed.value, moreOrLessEquals(0.0)); |
| tick(const Duration(milliseconds: 120)); |
| expect(reversed.value, moreOrLessEquals(0.2)); |
| tick(const Duration(milliseconds: 130)); |
| expect(reversed.value, moreOrLessEquals(0.4)); |
| tick(const Duration(milliseconds: 140)); |
| expect(reversed.value, moreOrLessEquals(0.6)); |
| tick(const Duration(milliseconds: 150)); |
| expect(reversed.value, moreOrLessEquals(0.8)); |
| tick(const Duration(milliseconds: 160)); |
| expect(reversed.value, moreOrLessEquals(1.0)); |
| }); |
| |
| test('TweenSequence', () { |
| final AnimationController controller = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| final Animation<double> animation = TweenSequence<double>( |
| <TweenSequenceItem<double>>[ |
| TweenSequenceItem<double>( |
| tween: Tween<double>(begin: 5.0, end: 10.0), |
| weight: 4.0, |
| ), |
| TweenSequenceItem<double>( |
| tween: ConstantTween<double>(10.0), |
| weight: 2.0, |
| ), |
| TweenSequenceItem<double>( |
| tween: Tween<double>(begin: 10.0, end: 5.0), |
| 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', () { |
| final AnimationController controller = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| final Animation<double> animation = TweenSequence<double>( |
| <TweenSequenceItem<double>>[ |
| TweenSequenceItem<double>( |
| tween: Tween<double>(begin: 5.0, end: 10.0) |
| .chain(CurveTween(curve: const Interval(0.5, 1.0))), |
| weight: 4.0, |
| ), |
| TweenSequenceItem<double>( |
| tween: ConstantTween<double>(10.0) |
| .chain(CurveTween(curve: Curves.linear)), // linear is a no-op |
| weight: 2.0, |
| ), |
| TweenSequenceItem<double>( |
| tween: Tween<double>(begin: 10.0, end: 5.0) |
| .chain(CurveTween(curve: const Interval(0.0, 0.5))), |
| 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', () { |
| final AnimationController controller = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| final Animation<double> animation = TweenSequence<double>( |
| <TweenSequenceItem<double>>[ |
| TweenSequenceItem<double>( |
| tween: Tween<double>(begin: 5.0, end: 10.0), |
| 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); |
| }); |
| |
| } |