blob: f7a35fb2dff1b4dd590e1bd2187ec1f355d164bb [file] [log] [blame]
// Copyright 2013 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:animations/animations.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('transitions in a new child.', (WidgetTester tester) async {
final UniqueKey containerOne = UniqueKey();
final UniqueKey containerTwo = UniqueKey();
final UniqueKey containerThree = UniqueKey();
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerOne, color: const Color(0x00000000)),
),
);
Map<Key, double> primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne], tester);
Map<Key, double> secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne], tester);
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerOne], equals(0.0));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerTwo, color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 40));
primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne, containerTwo], tester);
secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne, containerTwo], tester);
// Secondary is running for outgoing widget.
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerOne], moreOrLessEquals(0.4));
// Primary is running for incoming widget.
expect(primaryAnimation[containerTwo], moreOrLessEquals(0.4));
expect(secondaryAnimation[containerTwo], equals(0.0));
// Container one is underneath container two
final Container container = tester.firstWidget(find.byType(Container));
expect(container.key, containerOne);
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerThree, color: const Color(0xffff0000)),
),
);
await tester.pump(const Duration(milliseconds: 20));
primaryAnimation = _getPrimaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
secondaryAnimation = _getSecondaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerOne], equals(0.6));
expect(primaryAnimation[containerTwo], equals(0.6));
expect(secondaryAnimation[containerTwo], moreOrLessEquals(0.2));
expect(primaryAnimation[containerThree], moreOrLessEquals(0.2));
expect(secondaryAnimation[containerThree], equals(0.0));
await tester.pumpAndSettle();
});
testWidgets('transitions in a new child in reverse.',
(WidgetTester tester) async {
final UniqueKey containerOne = UniqueKey();
final UniqueKey containerTwo = UniqueKey();
final UniqueKey containerThree = UniqueKey();
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
reverse: true,
child: Container(key: containerOne, color: const Color(0x00000000)),
),
);
Map<Key, double> primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne], tester);
Map<Key, double> secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne], tester);
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerOne], equals(0.0));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
reverse: true,
child: Container(key: containerTwo, color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 40));
primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne, containerTwo], tester);
secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne, containerTwo], tester);
// Primary is running forward for outgoing widget.
expect(primaryAnimation[containerOne], moreOrLessEquals(0.6));
expect(secondaryAnimation[containerOne], equals(0.0));
// Secondary is running forward for incoming widget.
expect(primaryAnimation[containerTwo], equals(1.0));
expect(secondaryAnimation[containerTwo], moreOrLessEquals(0.6));
// Container two two is underneath container one.
final Container container = tester.firstWidget(find.byType(Container));
expect(container.key, containerTwo);
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
reverse: true,
child: Container(key: containerThree, color: const Color(0xffff0000)),
),
);
await tester.pump(const Duration(milliseconds: 20));
primaryAnimation = _getPrimaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
secondaryAnimation = _getSecondaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
expect(primaryAnimation[containerOne], equals(0.4));
expect(secondaryAnimation[containerOne], equals(0.0));
expect(primaryAnimation[containerTwo], equals(0.8));
expect(secondaryAnimation[containerTwo], equals(0.4));
expect(primaryAnimation[containerThree], equals(1.0));
expect(secondaryAnimation[containerThree], equals(0.8));
await tester.pumpAndSettle();
});
testWidgets('switch from forward to reverse', (WidgetTester tester) async {
final UniqueKey containerOne = UniqueKey();
final UniqueKey containerTwo = UniqueKey();
final UniqueKey containerThree = UniqueKey();
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerOne, color: const Color(0x00000000)),
),
);
Map<Key, double> primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne], tester);
Map<Key, double> secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne], tester);
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerOne], equals(0.0));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerTwo, color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 40));
primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne, containerTwo], tester);
secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne, containerTwo], tester);
expect(secondaryAnimation[containerOne], moreOrLessEquals(0.4));
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerTwo], equals(0.0));
expect(primaryAnimation[containerTwo], moreOrLessEquals(0.4));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
reverse: true,
child: Container(key: containerThree, color: const Color(0xffff0000)),
),
);
await tester.pump(const Duration(milliseconds: 20));
primaryAnimation = _getPrimaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
secondaryAnimation = _getSecondaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
expect(secondaryAnimation[containerOne], equals(0.6));
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerTwo], equals(0.0));
expect(primaryAnimation[containerTwo], moreOrLessEquals(0.2));
expect(secondaryAnimation[containerThree], equals(0.8));
expect(primaryAnimation[containerThree], equals(1.0));
await tester.pumpAndSettle();
});
testWidgets('switch from reverse to forward.', (WidgetTester tester) async {
final UniqueKey containerOne = UniqueKey();
final UniqueKey containerTwo = UniqueKey();
final UniqueKey containerThree = UniqueKey();
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
reverse: true,
child: Container(key: containerOne, color: const Color(0x00000000)),
),
);
Map<Key, double> primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne], tester);
Map<Key, double> secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne], tester);
expect(primaryAnimation[containerOne], equals(1.0));
expect(secondaryAnimation[containerOne], equals(0.0));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
reverse: true,
child: Container(key: containerTwo, color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 40));
primaryAnimation =
_getPrimaryAnimation(<Key>[containerOne, containerTwo], tester);
secondaryAnimation =
_getSecondaryAnimation(<Key>[containerOne, containerTwo], tester);
// Primary is running in reverse for outgoing widget.
expect(primaryAnimation[containerOne], moreOrLessEquals(0.6));
expect(secondaryAnimation[containerOne], equals(0.0));
// Secondary is running in reverse for incoming widget.
expect(primaryAnimation[containerTwo], equals(1.0));
expect(secondaryAnimation[containerTwo], moreOrLessEquals(0.6));
// Container two is underneath container one.
final Container container = tester.firstWidget(find.byType(Container));
expect(container.key, containerTwo);
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerThree, color: const Color(0xffff0000)),
),
);
await tester.pump(const Duration(milliseconds: 20));
// Container one is expected to continue running its primary animation in
// reverse since it is exiting. Container two's secondary animation switches
// from running its secondary animation in reverse to running forwards since
// it should now be exiting underneath container three. Container three's
// primary animation should be running forwards since it is entering above
// container two.
primaryAnimation = _getPrimaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
secondaryAnimation = _getSecondaryAnimation(
<Key>[containerOne, containerTwo, containerThree], tester);
expect(primaryAnimation[containerOne], equals(0.4));
expect(secondaryAnimation[containerOne], equals(0.0));
expect(primaryAnimation[containerTwo], equals(1.0));
expect(secondaryAnimation[containerTwo], equals(0.8));
expect(primaryAnimation[containerThree], moreOrLessEquals(0.2));
expect(secondaryAnimation[containerThree], equals(0.0));
await tester.pumpAndSettle();
});
testWidgets('using custom layout', (WidgetTester tester) async {
Widget newLayoutBuilder(List<Widget> activeEntries) {
return Column(
children: activeEntries,
);
}
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
layoutBuilder: newLayoutBuilder,
child: Container(color: const Color(0x00000000)),
),
);
expect(find.byType(Column), findsOneWidget);
});
testWidgets("doesn't transition in a new child of the same type.",
(WidgetTester tester) async {
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(color: const Color(0x00000000)),
),
);
expect(find.byType(FadeTransition), findsOneWidget);
expect(find.byType(ScaleTransition), findsOneWidget);
FadeTransition fade = tester.firstWidget(find.byType(FadeTransition));
ScaleTransition scale = tester.firstWidget(find.byType(ScaleTransition));
expect(fade.opacity.value, equals(1.0));
expect(scale.scale.value, equals(1.0));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 50));
expect(find.byType(FadeTransition), findsOneWidget);
expect(find.byType(ScaleTransition), findsOneWidget);
fade = tester.firstWidget(find.byType(FadeTransition));
scale = tester.firstWidget(find.byType(ScaleTransition));
expect(fade.opacity.value, equals(1.0));
expect(scale.scale.value, equals(1.0));
await tester.pumpAndSettle();
});
testWidgets('handles null children.', (WidgetTester tester) async {
await tester.pumpWidget(
const PageTransitionSwitcher(
duration: Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
),
);
expect(find.byType(FadeTransition), findsNothing);
expect(find.byType(ScaleTransition), findsNothing);
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 40));
expect(find.byType(FadeTransition), findsOneWidget);
expect(find.byType(ScaleTransition), findsOneWidget);
FadeTransition fade = tester.firstWidget(find.byType(FadeTransition));
ScaleTransition scale = tester.firstWidget(find.byType(ScaleTransition));
expect(fade.opacity.value, equals(1.0));
expect(scale.scale.value, moreOrLessEquals(0.4));
await tester.pumpAndSettle(); // finish transitions.
await tester.pumpWidget(
const PageTransitionSwitcher(
duration: Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
),
);
await tester.pump(const Duration(milliseconds: 50));
expect(find.byType(FadeTransition), findsOneWidget);
expect(find.byType(ScaleTransition), findsOneWidget);
fade = tester.firstWidget(find.byType(FadeTransition));
scale = tester.firstWidget(find.byType(ScaleTransition));
expect(fade.opacity.value, equals(0.5));
expect(scale.scale.value, equals(1.0));
await tester.pumpAndSettle();
});
testWidgets("doesn't start any animations after dispose.",
(WidgetTester tester) async {
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: UniqueKey(), color: const Color(0xff000000)),
),
);
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: UniqueKey(), color: const Color(0xff000000)),
),
);
await tester.pump(const Duration(milliseconds: 50));
expect(find.byType(FadeTransition), findsNWidgets(2));
expect(find.byType(ScaleTransition), findsNWidgets(2));
final FadeTransition fade = tester.firstWidget(find.byType(FadeTransition));
final ScaleTransition scale =
tester.firstWidget(find.byType(ScaleTransition));
expect(fade.opacity.value, equals(0.5));
expect(scale.scale.value, equals(1.0));
// Change the widget tree in the middle of the animation.
await tester.pumpWidget(Container(color: const Color(0xffff0000)));
expect(await tester.pumpAndSettle(), equals(1));
});
testWidgets("doesn't reset state of the children in transitions.",
(WidgetTester tester) async {
final UniqueKey statefulOne = UniqueKey();
final UniqueKey statefulTwo = UniqueKey();
final UniqueKey statefulThree = UniqueKey();
StatefulTestWidgetState.generation = 0;
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: StatefulTestWidget(key: statefulOne),
),
);
Map<Key, double> primaryAnimation =
_getPrimaryAnimation(<Key>[statefulOne], tester);
Map<Key, double> secondaryAnimation =
_getSecondaryAnimation(<Key>[statefulOne], tester);
expect(primaryAnimation[statefulOne], equals(1.0));
expect(secondaryAnimation[statefulOne], equals(0.0));
expect(StatefulTestWidgetState.generation, equals(1));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: StatefulTestWidget(key: statefulTwo),
),
);
await tester.pump(const Duration(milliseconds: 50));
expect(find.byType(FadeTransition), findsNWidgets(2));
primaryAnimation =
_getPrimaryAnimation(<Key>[statefulOne, statefulTwo], tester);
secondaryAnimation =
_getSecondaryAnimation(<Key>[statefulOne, statefulTwo], tester);
expect(primaryAnimation[statefulTwo], equals(0.5));
expect(secondaryAnimation[statefulTwo], equals(0.0));
expect(StatefulTestWidgetState.generation, equals(2));
await tester.pumpWidget(
PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: StatefulTestWidget(key: statefulThree),
),
);
await tester.pump(const Duration(milliseconds: 10));
expect(StatefulTestWidgetState.generation, equals(3));
await tester.pumpAndSettle();
expect(StatefulTestWidgetState.generation, equals(3));
});
testWidgets('updates widgets without animating if they are isomorphic.',
(WidgetTester tester) async {
Future<void> pumpChild(Widget child) async {
return tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: child,
),
),
);
}
await pumpChild(const Text('1'));
await tester.pump(const Duration(milliseconds: 10));
FadeTransition fade = tester.widget(find.byType(FadeTransition));
ScaleTransition scale = tester.widget(find.byType(ScaleTransition));
expect(fade.opacity.value, equals(1.0));
expect(scale.scale.value, equals(1.0));
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsNothing);
await pumpChild(const Text('2'));
fade = tester.widget(find.byType(FadeTransition));
scale = tester.widget(find.byType(ScaleTransition));
await tester.pump(const Duration(milliseconds: 20));
expect(fade.opacity.value, equals(1.0));
expect(scale.scale.value, equals(1.0));
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsOneWidget);
});
testWidgets(
'updates previous child transitions if the transitionBuilder changes.',
(WidgetTester tester) async {
final UniqueKey containerOne = UniqueKey();
final UniqueKey containerTwo = UniqueKey();
final UniqueKey containerThree = UniqueKey();
// Insert three unique children so that we have some previous children.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerOne, color: const Color(0xFFFF0000)),
),
),
);
await tester.pump(const Duration(milliseconds: 10));
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerTwo, color: const Color(0xFF00FF00)),
),
),
);
await tester.pump(const Duration(milliseconds: 10));
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: _transitionBuilder,
child: Container(key: containerThree, color: const Color(0xFF0000FF)),
),
),
);
await tester.pump(const Duration(milliseconds: 10));
expect(find.byType(FadeTransition), findsNWidgets(3));
expect(find.byType(ScaleTransition), findsNWidgets(3));
expect(find.byType(SlideTransition), findsNothing);
expect(find.byType(SizeTransition), findsNothing);
Widget newTransitionBuilder(
Widget child, Animation<double> primary, Animation<double> secondary) {
return SlideTransition(
position: Tween<Offset>(begin: Offset.zero, end: const Offset(20, 30))
.animate(primary),
child: SizeTransition(
sizeFactor: Tween<double>(begin: 10, end: 0.0).animate(secondary),
child: child,
),
);
}
// Now set a new transition builder and make sure all the previous
// transitions are replaced.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: PageTransitionSwitcher(
duration: const Duration(milliseconds: 100),
transitionBuilder: newTransitionBuilder,
child: Container(key: containerThree, color: const Color(0x00000000)),
),
),
);
await tester.pump(const Duration(milliseconds: 10));
expect(find.byType(FadeTransition), findsNothing);
expect(find.byType(ScaleTransition), findsNothing);
expect(find.byType(SlideTransition), findsNWidgets(3));
expect(find.byType(SizeTransition), findsNWidgets(3));
});
}
class StatefulTestWidget extends StatefulWidget {
const StatefulTestWidget({super.key});
@override
StatefulTestWidgetState createState() => StatefulTestWidgetState();
}
class StatefulTestWidgetState extends State<StatefulTestWidget> {
StatefulTestWidgetState();
static int generation = 0;
@override
void initState() {
super.initState();
generation++;
}
@override
Widget build(BuildContext context) => Container();
}
Widget _transitionBuilder(
Widget child, Animation<double> primary, Animation<double> secondary) {
return ScaleTransition(
scale: Tween<double>(begin: 0.0, end: 1.0).animate(primary),
child: FadeTransition(
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(secondary),
child: child,
),
);
}
Map<Key, double> _getSecondaryAnimation(List<Key> keys, WidgetTester tester) {
expect(find.byType(FadeTransition), findsNWidgets(keys.length));
final Map<Key, double> result = <Key, double>{};
for (final Key key in keys) {
final FadeTransition transition = tester.firstWidget(
find.ancestor(
of: find.byKey(key),
matching: find.byType(FadeTransition),
),
);
result[key] = 1.0 - transition.opacity.value;
}
return result;
}
Map<Key, double> _getPrimaryAnimation(List<Key> keys, WidgetTester tester) {
expect(find.byType(ScaleTransition), findsNWidgets(keys.length));
final Map<Key, double> result = <Key, double>{};
for (final Key key in keys) {
final ScaleTransition transition = tester.firstWidget(
find.ancestor(
of: find.byKey(key),
matching: find.byType(ScaleTransition),
),
);
result[key] = transition.scale.value;
}
return result;
}