mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
1093 lines
36 KiB
Dart
1093 lines
36 KiB
Dart
// 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;
|
|
}
|