// 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 'dart:ui' as ui; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; import '../scheduler/scheduler_tester.dart'; class BogusCurve extends Curve { @override double transform(double t) => 100.0; } void main() { setUp(() { WidgetsFlutterBinding.ensureInitialized(); WidgetsBinding.instance!.resetEpoch(); ui.window.onBeginFrame = null; ui.window.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: BogusCurve()); FlutterError? error; try { curved.value; } on FlutterError catch (e) { error = e; } expect(error, isNotNull); expect(error!.toStringDeep(), matches( // 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 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('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); }); }