blob: 85c09afacab3459b1db5d1121d8042733b0c2dd8 [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/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();
}
}