| // 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/material.dart'; |
| import 'package:flutter/scheduler.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| testWidgets('AnimatedCrossFade test', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| firstChild: SizedBox( |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showFirst, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.byType(FadeTransition), findsNWidgets(2)); |
| RenderBox box = tester.renderObject(find.byType(AnimatedCrossFade)); |
| expect(box.size.width, equals(100.0)); |
| expect(box.size.height, equals(100.0)); |
| |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| firstChild: SizedBox( |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showSecond, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| expect(find.byType(FadeTransition), findsNWidgets(2)); |
| box = tester.renderObject(find.byType(AnimatedCrossFade)); |
| expect(box.size.width, equals(150.0)); |
| expect(box.size.height, equals(150.0)); |
| }); |
| |
| testWidgets('AnimatedCrossFade test showSecond', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| firstChild: SizedBox( |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showSecond, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.byType(FadeTransition), findsNWidgets(2)); |
| final RenderBox box = tester.renderObject(find.byType(AnimatedCrossFade)); |
| expect(box.size.width, equals(200.0)); |
| expect(box.size.height, equals(200.0)); |
| }); |
| |
| testWidgets('AnimatedCrossFade alignment (VISUAL)', (WidgetTester tester) async { |
| final Key firstKey = UniqueKey(); |
| final Key secondKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| alignment: Alignment.bottomRight, |
| firstChild: SizedBox( |
| key: firstKey, |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| key: secondKey, |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: const Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showFirst, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| alignment: Alignment.bottomRight, |
| firstChild: SizedBox( |
| key: firstKey, |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| key: secondKey, |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: const Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showSecond, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| final RenderBox box1 = tester.renderObject(find.byKey(firstKey)); |
| final RenderBox box2 = tester.renderObject(find.byKey(secondKey)); |
| expect(box1.localToGlobal(Offset.zero), const Offset(275.0, 175.0)); |
| expect(box2.localToGlobal(Offset.zero), const Offset(275.0, 175.0)); |
| }); |
| |
| testWidgets('AnimatedCrossFade alignment (LTR)', (WidgetTester tester) async { |
| final Key firstKey = UniqueKey(); |
| final Key secondKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| alignment: AlignmentDirectional.bottomEnd, |
| firstChild: SizedBox( |
| key: firstKey, |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| key: secondKey, |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: const Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showFirst, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: AnimatedCrossFade( |
| alignment: AlignmentDirectional.bottomEnd, |
| firstChild: SizedBox( |
| key: firstKey, |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| key: secondKey, |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: const Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showSecond, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| final RenderBox box1 = tester.renderObject(find.byKey(firstKey)); |
| final RenderBox box2 = tester.renderObject(find.byKey(secondKey)); |
| expect(box1.localToGlobal(Offset.zero), const Offset(275.0, 175.0)); |
| expect(box2.localToGlobal(Offset.zero), const Offset(275.0, 175.0)); |
| }); |
| |
| testWidgets('AnimatedCrossFade alignment (RTL)', (WidgetTester tester) async { |
| final Key firstKey = UniqueKey(); |
| final Key secondKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.rtl, |
| child: Center( |
| child: AnimatedCrossFade( |
| alignment: AlignmentDirectional.bottomEnd, |
| firstChild: SizedBox( |
| key: firstKey, |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| key: secondKey, |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: const Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showFirst, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.rtl, |
| child: Center( |
| child: AnimatedCrossFade( |
| alignment: AlignmentDirectional.bottomEnd, |
| firstChild: SizedBox( |
| key: firstKey, |
| width: 100.0, |
| height: 100.0, |
| ), |
| secondChild: SizedBox( |
| key: secondKey, |
| width: 200.0, |
| height: 200.0, |
| ), |
| duration: const Duration(milliseconds: 200), |
| crossFadeState: CrossFadeState.showSecond, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| final RenderBox box1 = tester.renderObject(find.byKey(firstKey)); |
| final RenderBox box2 = tester.renderObject(find.byKey(secondKey)); |
| expect(box1.localToGlobal(Offset.zero), const Offset(325.0, 175.0)); |
| expect(box2.localToGlobal(Offset.zero), const Offset(325.0, 175.0)); |
| }); |
| |
| Widget crossFadeWithWatcher({ bool towardsSecond = false }) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: AnimatedCrossFade( |
| firstChild: const _TickerWatchingWidget(), |
| secondChild: Container(), |
| crossFadeState: towardsSecond ? CrossFadeState.showSecond : CrossFadeState.showFirst, |
| duration: const Duration(milliseconds: 50), |
| ), |
| ); |
| } |
| |
| testWidgets('AnimatedCrossFade preserves widget state', (WidgetTester tester) async { |
| await tester.pumpWidget(crossFadeWithWatcher()); |
| |
| _TickerWatchingWidgetState findState() => tester.state(find.byType(_TickerWatchingWidget)); |
| final _TickerWatchingWidgetState state = findState(); |
| |
| await tester.pumpWidget(crossFadeWithWatcher(towardsSecond: true)); |
| for (int i = 0; i < 3; i += 1) { |
| await tester.pump(const Duration(milliseconds: 25)); |
| expect(findState(), same(state)); |
| } |
| }); |
| |
| testWidgets('AnimatedCrossFade switches off TickerMode and semantics on faded out widget', (WidgetTester tester) async { |
| ExcludeSemantics findSemantics() { |
| return tester.widget(find.descendant( |
| of: find.byKey(const ValueKey<CrossFadeState>(CrossFadeState.showFirst)), |
| matching: find.byType(ExcludeSemantics), |
| )); |
| } |
| |
| await tester.pumpWidget(crossFadeWithWatcher()); |
| |
| final _TickerWatchingWidgetState state = tester.state(find.byType(_TickerWatchingWidget)); |
| expect(state.ticker.muted, false); |
| expect(findSemantics().excluding, false); |
| |
| await tester.pumpWidget(crossFadeWithWatcher(towardsSecond: true)); |
| for (int i = 0; i < 2; i += 1) { |
| await tester.pump(const Duration(milliseconds: 25)); |
| // Animations are kept alive in the middle of cross-fade |
| expect(state.ticker.muted, false); |
| // Semantics are turned off immediately on the widget that's fading out |
| expect(findSemantics().excluding, true); |
| } |
| |
| // In the final state both animations and semantics should be off on the |
| // widget that's faded out. |
| await tester.pump(const Duration(milliseconds: 25)); |
| expect(state.ticker.muted, true); |
| expect(findSemantics().excluding, true); |
| }); |
| |
| testWidgets('AnimatedCrossFade.layoutBuilder', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: AnimatedCrossFade( |
| firstChild: Text('AAA', textDirection: TextDirection.ltr), |
| secondChild: Text('BBB', textDirection: TextDirection.ltr), |
| crossFadeState: CrossFadeState.showFirst, |
| duration: Duration(milliseconds: 50), |
| ), |
| ), |
| ); |
| expect(find.text('AAA'), findsOneWidget); |
| expect(find.text('BBB'), findsOneWidget); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: AnimatedCrossFade( |
| firstChild: const Text('AAA', textDirection: TextDirection.ltr), |
| secondChild: const Text('BBB', textDirection: TextDirection.ltr), |
| crossFadeState: CrossFadeState.showFirst, |
| duration: const Duration(milliseconds: 50), |
| layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a, |
| ), |
| ), |
| ); |
| expect(find.text('AAA'), findsOneWidget); |
| expect(find.text('BBB'), findsNothing); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: AnimatedCrossFade( |
| firstChild: const Text('AAA', textDirection: TextDirection.ltr), |
| secondChild: const Text('BBB', textDirection: TextDirection.ltr), |
| crossFadeState: CrossFadeState.showSecond, |
| duration: const Duration(milliseconds: 50), |
| layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a, |
| ), |
| ), |
| ); |
| expect(find.text('BBB'), findsOneWidget); |
| expect(find.text('AAA'), findsNothing); |
| }); |
| |
| testWidgets('AnimatedCrossFade test focus', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: AnimatedCrossFade( |
| firstChild: TextButton(onPressed: () {}, child: const Text('AAA')), |
| secondChild: TextButton(onPressed: () {}, child: const Text('BBB')), |
| crossFadeState: CrossFadeState.showFirst, |
| duration: const Duration(milliseconds: 50), |
| ), |
| ), |
| ); |
| |
| final FocusNode visibleNode = Focus.of(tester.element(find.text('AAA')), scopeOk: true); |
| visibleNode.requestFocus(); |
| await tester.pump(); |
| expect(visibleNode.hasPrimaryFocus, isTrue); |
| |
| final FocusNode hiddenNode = Focus.of(tester.element(find.text('BBB')), scopeOk: true); |
| hiddenNode.requestFocus(); |
| await tester.pump(); |
| expect(hiddenNode.hasPrimaryFocus, isFalse); |
| }); |
| |
| testWidgets('AnimatedCrossFade second child do not receive touch events', |
| (WidgetTester tester) async { |
| int numberOfTouchEventNoticed = 0; |
| |
| Future<void> buildAnimatedFrame(CrossFadeState crossFadeState) { |
| return tester.pumpWidget( |
| SizedBox( |
| width: 300, |
| height: 600, |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: AnimatedCrossFade( |
| firstChild: const Text('AAA'), |
| secondChild: TextButton( |
| style: TextButton.styleFrom(minimumSize: const Size(double.infinity, 600)), |
| onPressed: () { |
| numberOfTouchEventNoticed++; |
| }, |
| child: const Text('BBB'), |
| ), |
| crossFadeState: crossFadeState, |
| duration: const Duration(milliseconds: 50), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| Future<void> touchSecondButton() async { |
| final TestGesture gestureTouchSecondButton = await tester |
| .startGesture(const Offset(150, 300)); |
| |
| return gestureTouchSecondButton.up(); |
| } |
| |
| await buildAnimatedFrame(CrossFadeState.showSecond); |
| |
| await touchSecondButton(); |
| expect(numberOfTouchEventNoticed, 1); |
| |
| await buildAnimatedFrame(CrossFadeState.showFirst); |
| await touchSecondButton(); |
| await touchSecondButton(); |
| |
| expect(numberOfTouchEventNoticed, 1); |
| }); |
| } |
| |
| class _TickerWatchingWidget extends StatefulWidget { |
| const _TickerWatchingWidget(); |
| |
| @override |
| State<StatefulWidget> createState() => _TickerWatchingWidgetState(); |
| } |
| |
| class _TickerWatchingWidgetState extends State<_TickerWatchingWidget> with SingleTickerProviderStateMixin { |
| late Ticker ticker; |
| |
| @override |
| void initState() { |
| super.initState(); |
| ticker = createTicker((_) { })..start(); |
| } |
| |
| @override |
| Widget build(BuildContext context) => Container(); |
| |
| @override |
| void dispose() { |
| ticker.dispose(); |
| super.dispose(); |
| } |
| } |