blob: 55bb776ca52bfb266f1055de2128134014f1db44 [file] [log] [blame]
// 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/foundation.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../scheduler/scheduler_tester.dart';
void main() {
setUp(() {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance
..resetEpoch()
..platformDispatcher.onBeginFrame = null
..platformDispatcher.onDrawFrame = null;
});
test('Can set value during status callback', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
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);
tick(const Duration(seconds: 1));
expect(didComplete, isFalse);
expect(didDismiss, isFalse);
tick(const Duration(seconds: 2));
expect(didComplete, isTrue);
expect(didDismiss, isTrue);
controller.stop();
controller.dispose();
});
test('Receives status callbacks for forward and reverse', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
final List<double> valueLog = <double>[];
final List<AnimationStatus> log = <AnimationStatus>[];
controller
..addStatusListener(log.add)
..addListener(() {
valueLog.add(controller.value);
});
expect(log, equals(<AnimationStatus>[]));
expect(valueLog, equals(<AnimationStatus>[]));
controller.forward();
expect(log, equals(<AnimationStatus>[AnimationStatus.forward]));
expect(valueLog, equals(<AnimationStatus>[]));
controller.reverse();
expect(log, equals(<AnimationStatus>[AnimationStatus.forward, AnimationStatus.dismissed]));
expect(valueLog, equals(<AnimationStatus>[]));
controller.reverse();
expect(log, equals(<AnimationStatus>[AnimationStatus.forward, AnimationStatus.dismissed]));
expect(valueLog, equals(<AnimationStatus>[]));
log.clear();
controller.forward();
expect(log, equals(<AnimationStatus>[AnimationStatus.forward]));
expect(valueLog, equals(<AnimationStatus>[]));
controller.forward();
expect(log, equals(<AnimationStatus>[AnimationStatus.forward]));
expect(valueLog, equals(<AnimationStatus>[]));
controller.reverse();
log.clear();
tick(const Duration(seconds: 10));
expect(log, equals(<AnimationStatus>[]));
expect(valueLog, equals(<AnimationStatus>[]));
tick(const Duration(seconds: 20));
expect(log, equals(<AnimationStatus>[]));
expect(valueLog, equals(<AnimationStatus>[]));
tick(const Duration(seconds: 30));
expect(log, equals(<AnimationStatus>[]));
expect(valueLog, equals(<AnimationStatus>[]));
tick(const Duration(seconds: 40));
expect(log, equals(<AnimationStatus>[]));
expect(valueLog, equals(<AnimationStatus>[]));
controller.stop();
controller.dispose();
});
test('Forward and reverse from values', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
final List<double> valueLog = <double>[];
final List<AnimationStatus> statusLog = <AnimationStatus>[];
controller
..addStatusListener(statusLog.add)
..addListener(() {
valueLog.add(controller.value);
});
controller.reverse(from: 0.2);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse ]));
expect(valueLog, equals(<double>[ 0.2 ]));
expect(controller.value, equals(0.2));
statusLog.clear();
valueLog.clear();
controller.forward(from: 0.0);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.dismissed, AnimationStatus.forward ]));
expect(valueLog, equals(<double>[ 0.0 ]));
expect(controller.value, equals(0.0));
controller.dispose();
});
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));
expect(controller.value, moreOrLessEquals(0.2));
tick(const Duration(milliseconds: 60));
expect(controller.value, moreOrLessEquals(0.5));
tick(const Duration(milliseconds: 90));
expect(controller.value, moreOrLessEquals(0.8));
tick(const Duration(milliseconds: 120));
expect(controller.value, moreOrLessEquals(1.0));
controller.stop();
controller.reverse();
tick(const Duration(milliseconds: 210));
tick(const Duration(milliseconds: 220));
expect(controller.value, moreOrLessEquals(0.8));
tick(const Duration(milliseconds: 230));
expect(controller.value, moreOrLessEquals(0.6));
tick(const Duration(milliseconds: 240));
expect(controller.value, moreOrLessEquals(0.4));
tick(const Duration(milliseconds: 260));
expect(controller.value, moreOrLessEquals(0.0));
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));
expect(controller.value, moreOrLessEquals(0.4));
tick(const Duration(milliseconds: 60));
expect(controller.value, moreOrLessEquals(1.0));
tick(const Duration(milliseconds: 90));
expect(controller.value, moreOrLessEquals(1.0));
controller.stop();
controller.reverse();
tick(const Duration(milliseconds: 210));
tick(const Duration(milliseconds: 220));
expect(controller.value, moreOrLessEquals(0.9));
tick(const Duration(milliseconds: 230));
expect(controller.value, moreOrLessEquals(0.8));
tick(const Duration(milliseconds: 240));
expect(controller.value, moreOrLessEquals(0.7));
tick(const Duration(milliseconds: 260));
expect(controller.value, moreOrLessEquals(0.5));
tick(const Duration(milliseconds: 310));
expect(controller.value, moreOrLessEquals(0.0));
controller.stop();
controller.dispose();
});
test('Forward only from value', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
final List<double> valueLog = <double>[];
final List<AnimationStatus> statusLog = <AnimationStatus>[];
controller
..addStatusListener(statusLog.add)
..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));
controller.dispose();
});
test('Can fling to upper and lower bounds', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
controller.fling();
tick(const Duration(seconds: 1));
tick(const Duration(seconds: 2));
expect(controller.value, 1.0);
controller.stop();
final AnimationController largeRangeController = AnimationController(
duration: const Duration(milliseconds: 100),
lowerBound: -30.0,
upperBound: 45.0,
vsync: const TestVSync(),
);
largeRangeController.fling();
tick(const Duration(seconds: 3));
tick(const Duration(seconds: 4));
expect(largeRangeController.value, 45.0);
largeRangeController.fling(velocity: -1.0);
tick(const Duration(seconds: 5));
tick(const Duration(seconds: 6));
expect(largeRangeController.value, -30.0);
largeRangeController.stop();
controller.dispose();
largeRangeController.dispose();
});
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,
),
);
tick(Duration.zero);
tick(const Duration(milliseconds: 50));
expect(customSpringController.value < controller.value, true);
controller.dispose();
customSpringController.dispose();
});
test('lastElapsedDuration control test', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
controller.forward();
tick(const Duration(milliseconds: 20));
tick(const Duration(milliseconds: 30));
tick(const Duration(milliseconds: 40));
expect(controller.lastElapsedDuration, equals(const Duration(milliseconds: 20)));
controller.stop();
controller.dispose();
});
test('toString control test', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
expect(controller, hasOneLineDescription);
controller.forward();
tick(const Duration(milliseconds: 10));
tick(const Duration(milliseconds: 20));
expect(controller, hasOneLineDescription);
tick(const Duration(milliseconds: 30));
expect(controller, hasOneLineDescription);
controller.reverse();
tick(const Duration(milliseconds: 40));
tick(const Duration(milliseconds: 50));
expect(controller, hasOneLineDescription);
controller.stop();
controller.dispose();
});
test('velocity test - linear', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: const TestVSync(),
);
// mid-flight
controller.forward();
tick(Duration.zero);
tick(const Duration(milliseconds: 500));
expect(controller.velocity, inInclusiveRange(0.9, 1.1));
// edges
controller.forward();
expect(controller.velocity, inInclusiveRange(0.4, 0.6));
tick(Duration.zero);
expect(controller.velocity, inInclusiveRange(0.4, 0.6));
tick(const Duration(milliseconds: 5));
expect(controller.velocity, inInclusiveRange(0.9, 1.1));
controller.forward(from: 0.5);
expect(controller.velocity, inInclusiveRange(0.4, 0.6));
tick(Duration.zero);
expect(controller.velocity, inInclusiveRange(0.4, 0.6));
tick(const Duration(milliseconds: 5));
expect(controller.velocity, inInclusiveRange(0.9, 1.1));
// stopped
controller.forward(from: 1.0);
expect(controller.velocity, 0.0);
tick(Duration.zero);
expect(controller.velocity, 0.0);
tick(const Duration(milliseconds: 500));
expect(controller.velocity, 0.0);
controller.forward();
tick(Duration.zero);
tick(const Duration(milliseconds: 1000));
expect(controller.velocity, 0.0);
controller.stop();
controller.dispose();
});
test('Disposed AnimationController toString works', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
controller.dispose();
expect(controller, hasOneLineDescription);
});
test('AnimationController error handling', () {
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
);
expect(controller.forward, throwsFlutterError);
expect(controller.reverse, throwsFlutterError);
expect(() { controller.animateTo(0.5); }, throwsFlutterError);
expect(controller.repeat, throwsFlutterError);
controller.dispose();
FlutterError? result;
try {
controller.dispose();
} on FlutterError catch (e) {
result = e;
}
expect(result, isNotNull);
expect(
result!.toStringDeep(),
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'
' AnimationController#00000(⏮ 0.000; paused; DISPOSED)\n',
),
);
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);
});
test('AnimationController repeat() throws if period is not specified', () {
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
);
expect(() { controller.repeat(); }, throwsFlutterError);
expect(() { controller.repeat(); }, throwsFlutterError);
controller.dispose();
});
test('Do not animate if already at target', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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));
controller.dispose();
});
test('Do not animate to upperBound if already at upperBound', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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));
controller.dispose();
});
test('Do not animate to lowerBound if already at lowerBound', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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));
controller.dispose();
});
test('Do not animate if already at target mid-flight (forward)', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
value: 0.0,
duration: const Duration(milliseconds: 1000),
vsync: const TestVSync(),
)..addStatusListener(statusLog.add);
expect(controller.value, equals(0.0));
controller.forward();
tick(Duration.zero);
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);
controller.dispose();
});
test('Do not animate if already at target mid-flight (reverse)', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
value: 1.0,
duration: const Duration(milliseconds: 1000),
vsync: const TestVSync(),
)..addStatusListener(statusLog.add);
expect(controller.value, equals(1.0));
controller.reverse();
tick(Duration.zero);
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));
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse, AnimationStatus.completed ]));
expect(controller.value, currentValue);
controller.dispose();
});
test('animateTo can deal with duration == Duration.zero', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
controller.forward(from: 0.2);
expect(controller.value, 0.2);
controller.animateTo(1.0, duration: Duration.zero);
expect(SchedulerBinding.instance.transientCallbackCount, equals(0), reason: 'Expected no animation.');
expect(controller.value, 1.0);
controller.dispose();
});
test('resetting animation works at all phases', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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();
tick(Duration.zero);
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();
tick(Duration.zero);
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();
tick(Duration.zero);
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 ]));
controller.dispose();
});
test('setting value directly sets correct status', () {
final AnimationController controller = AnimationController(
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);
controller.dispose();
});
test('animateTo sets correct status', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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);
tick(Duration.zero);
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);
tick(Duration.zero);
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);
tick(Duration.zero);
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);
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 0.0);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
statusLog.clear();
controller.dispose();
});
test('after a reverse call animateTo sets correct status', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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();
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 0.0);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.reverse, AnimationStatus.dismissed ]));
statusLog.clear();
controller.animateTo(0.5);
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 0.5);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
statusLog.clear();
controller.dispose();
});
test('after a forward call animateTo sets correct status', () {
final List<AnimationStatus> statusLog = <AnimationStatus>[];
final AnimationController controller = AnimationController(
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();
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 1.0);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
statusLog.clear();
controller.animateTo(0.5);
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 0.5);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward, AnimationStatus.completed ]));
statusLog.clear();
controller.dispose();
});
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(),
);
expect(controller.value, 0.0);
controller.repeat(reverse: true);
tick(Duration.zero);
tick(const Duration(milliseconds: 25));
expect(controller.value, 0.25);
tick(Duration.zero);
tick(const Duration(milliseconds: 125));
expect(controller.value, 0.75);
controller.reset();
controller.value = 1.0;
expect(controller.value, 1.0);
controller.repeat(reverse: true);
tick(Duration.zero);
tick(const Duration(milliseconds: 25));
expect(controller.value, 0.75);
tick(Duration.zero);
tick(const Duration(milliseconds: 125));
expect(controller.value, 0.25);
controller.reset();
controller.value = 0.5;
expect(controller.value, 0.5);
controller.repeat(reverse: true);
tick(Duration.zero);
tick(const Duration(milliseconds: 50));
expect(controller.value, 1.0);
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 0.0);
controller.dispose();
},
);
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(),
);
expect(controller.value, 0.0);
controller.repeat(reverse: true, min: 0.5, max: 1.0);
tick(Duration.zero);
tick(const Duration(milliseconds: 50));
expect(controller.value, 0.75);
tick(Duration.zero);
tick(const Duration(milliseconds: 100));
expect(controller.value, 1.00);
tick(Duration.zero);
tick(const Duration(milliseconds: 200));
expect(controller.value, 0.5);
controller.reset();
controller.value = 0.0;
expect(controller.value, 0.0);
controller.repeat(reverse: true, min: 1.0, max: 1.0);
tick(Duration.zero);
tick(const Duration(milliseconds: 25));
expect(controller.value, 1.0);
tick(Duration.zero);
tick(const Duration(milliseconds: 125));
expect(controller.value, 1.0);
controller.dispose();
},
);
group('AnimationBehavior', () {
test('Default values for constructor', () {
final AnimationController controller = AnimationController(vsync: const TestVSync());
expect(controller.animationBehavior, AnimationBehavior.normal);
final AnimationController repeating = AnimationController.unbounded(vsync: const TestVSync());
expect(repeating.animationBehavior, AnimationBehavior.preserve);
controller.dispose();
repeating.dispose();
});
test('AnimationBehavior.preserve runs at normal speed when animatingTo', () {
debugSemanticsDisableAnimations = true;
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
animationBehavior: AnimationBehavior.preserve,
);
expect(controller.value, 0.0);
expect(controller.status, AnimationStatus.dismissed);
controller.animateTo(1.0, duration: const Duration(milliseconds: 100));
tick(Duration.zero);
tick(const Duration(milliseconds: 50));
expect(controller.value, 0.5);
expect(controller.status, AnimationStatus.forward);
tick(Duration.zero);
tick(const Duration(milliseconds: 150));
expect(controller.value, 1.0);
expect(controller.status, AnimationStatus.completed);
debugSemanticsDisableAnimations = false;
controller.dispose();
});
test('AnimationBehavior.normal runs at 20x speed when animatingTo', () {
debugSemanticsDisableAnimations = true;
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
);
expect(controller.value, 0.0);
expect(controller.status, AnimationStatus.dismissed);
controller.animateTo(1.0, duration: const Duration(milliseconds: 100));
tick(Duration.zero);
tick(const Duration(microseconds: 2500));
expect(controller.value, 0.5);
expect(controller.status, AnimationStatus.forward);
tick(Duration.zero);
tick(const Duration(milliseconds: 5, microseconds: 1000));
expect(controller.value, 1.0);
expect(controller.status, AnimationStatus.completed);
debugSemanticsDisableAnimations = null;
controller.dispose();
});
test('AnimationBehavior.normal runs "faster" than AnimationBehavior.preserve', () {
debugSemanticsDisableAnimations = true;
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
);
final AnimationController fastController = AnimationController(
vsync: const TestVSync(),
);
controller.fling(animationBehavior: AnimationBehavior.preserve);
fastController.fling(animationBehavior: AnimationBehavior.normal);
tick(Duration.zero);
tick(const Duration(milliseconds: 50));
// We don't assert a specific faction that normal animation.
expect(controller.value < fastController.value, true);
debugSemanticsDisableAnimations = null;
controller.dispose();
fastController.dispose();
});
});
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);
});
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());
tick(Duration.zero);
tick(const Duration(seconds: 2));
expect(statuses, <AnimationStatus>[AnimationStatus.forward]);
controller.dispose();
});
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);
tick(Duration.zero);
tick(const Duration(seconds: 2));
expect(statuses, <AnimationStatus>[AnimationStatus.completed, AnimationStatus.reverse, AnimationStatus.dismissed]);
statuses.clear();
controller.animateWith(TestSimulation());
tick(Duration.zero);
tick(const Duration(seconds: 2));
expect(statuses, <AnimationStatus>[AnimationStatus.forward]);
controller.dispose();
});
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);
tick(Duration.zero);
tick(const Duration(milliseconds: 999));
expect(statuses, <AnimationStatus>[AnimationStatus.forward]);
statuses.clear();
tick(const Duration(seconds: 1));
expect(statuses, <AnimationStatus>[AnimationStatus.reverse]);
controller.dispose();
});
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();
tick(Duration.zero);
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'
' 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',
);
controller.dispose();
});
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'
' 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'
' function.\n',
);
controller.dispose();
},
);
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'
' 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',
);
controller.dispose();
});
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'
' 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'
' the animateBack() function.\n',
);
controller.dispose();
},
);
});
}
class TestSimulation extends Simulation {
@override
double dx(double time) => time;
@override
bool isDone(double time) => false;
@override
double x(double time) => time;
}