Unverified Commit db518732 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add onDismiss to AnimatedModalBarrier, update tests (#100162)

I noticed that AnimatedModalBarrier didn't have the onDismiss callback that ModalBarrier does, and so I added that, and while I was at it, I updated the unit tests to perform all of the tests that are done on ModalBarrier also on AnimatedModalBarrier. The tests are unchanged, other than using AnimatedModalBarrier instead.
parent 548e8853
...@@ -60,6 +60,7 @@ class ModalBarrier extends StatelessWidget { ...@@ -60,6 +60,7 @@ class ModalBarrier extends StatelessWidget {
/// [ModalBarrier] built by [ModalRoute] pages. /// [ModalBarrier] built by [ModalRoute] pages.
final bool dismissible; final bool dismissible;
/// {@template flutter.widgets.ModalBarrier.onDismiss}
/// Called when the barrier is being dismissed. /// Called when the barrier is being dismissed.
/// ///
/// If non-null [onDismiss] will be called in place of popping the current /// If non-null [onDismiss] will be called in place of popping the current
...@@ -68,6 +69,7 @@ class ModalBarrier extends StatelessWidget { ...@@ -68,6 +69,7 @@ class ModalBarrier extends StatelessWidget {
/// If null, the ambient [Navigator]'s current route will be popped. /// If null, the ambient [Navigator]'s current route will be popped.
/// ///
/// This field is ignored if [dismissible] is false. /// This field is ignored if [dismissible] is false.
/// {@endtemplate}
final VoidCallback? onDismiss; final VoidCallback? onDismiss;
/// Whether the modal barrier semantics are included in the semantics tree. /// Whether the modal barrier semantics are included in the semantics tree.
...@@ -172,6 +174,7 @@ class AnimatedModalBarrier extends AnimatedWidget { ...@@ -172,6 +174,7 @@ class AnimatedModalBarrier extends AnimatedWidget {
this.dismissible = true, this.dismissible = true,
this.semanticsLabel, this.semanticsLabel,
this.barrierSemanticsDismissible, this.barrierSemanticsDismissible,
this.onDismiss,
}) : super(key: key, listenable: color); }) : super(key: key, listenable: color);
/// If non-null, fill the barrier with this color. /// If non-null, fill the barrier with this color.
...@@ -208,6 +211,9 @@ class AnimatedModalBarrier extends AnimatedWidget { ...@@ -208,6 +211,9 @@ class AnimatedModalBarrier extends AnimatedWidget {
/// the [ModalBarrier] built by [ModalRoute] pages. /// the [ModalBarrier] built by [ModalRoute] pages.
final bool? barrierSemanticsDismissible; final bool? barrierSemanticsDismissible;
/// {@macro flutter.widgets.ModalBarrier.onDismiss}
final VoidCallback? onDismiss;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ModalBarrier( return ModalBarrier(
...@@ -215,6 +221,7 @@ class AnimatedModalBarrier extends AnimatedWidget { ...@@ -215,6 +221,7 @@ class AnimatedModalBarrier extends AnimatedWidget {
dismissible: dismissible, dismissible: dismissible,
semanticsLabel: semanticsLabel, semanticsLabel: semanticsLabel,
barrierSemanticsDismissible: barrierSemanticsDismissible, barrierSemanticsDismissible: barrierSemanticsDismissible,
onDismiss: onDismiss,
); );
} }
} }
......
...@@ -15,9 +15,11 @@ void main() { ...@@ -15,9 +15,11 @@ void main() {
late bool hovered; late bool hovered;
late Widget tapTarget; late Widget tapTarget;
late Widget hoverTarget; late Widget hoverTarget;
late Animation<Color?> colorAnimation;
setUp(() { setUp(() {
tapped = false; tapped = false;
colorAnimation = const AlwaysStoppedAnimation<Color?>(Colors.red);
tapTarget = GestureDetector( tapTarget = GestureDetector(
onTap: () { onTap: () {
tapped = true; tapped = true;
...@@ -42,7 +44,8 @@ void main() { ...@@ -42,7 +44,8 @@ void main() {
); );
}); });
testWidgets('ModalBarrier prevents interactions with widgets behind it', (WidgetTester tester) async { group('ModalBarrier', () {
testWidgets('prevents interactions with widgets behind it', (WidgetTester tester) async {
final Widget subject = Stack( final Widget subject = Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
...@@ -57,7 +60,7 @@ void main() { ...@@ -57,7 +60,7 @@ void main() {
expect(tapped, isFalse, reason: 'because the tap is not prevented by ModalBarrier'); expect(tapped, isFalse, reason: 'because the tap is not prevented by ModalBarrier');
}); });
testWidgets('ModalBarrier prevents hover interactions with widgets behind it', (WidgetTester tester) async { testWidgets('prevents hover interactions with widgets behind it', (WidgetTester tester) async {
final Widget subject = Stack( final Widget subject = Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
...@@ -85,7 +88,7 @@ void main() { ...@@ -85,7 +88,7 @@ void main() {
expect(hovered, isFalse, reason: 'because the hover is not prevented by ModalBarrier'); expect(hovered, isFalse, reason: 'because the hover is not prevented by ModalBarrier');
}); });
testWidgets('ModalBarrier does not prevent interactions with widgets in front of it', (WidgetTester tester) async { testWidgets('does not prevent interactions with widgets in front of it', (WidgetTester tester) async {
final Widget subject = Stack( final Widget subject = Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
...@@ -100,7 +103,7 @@ void main() { ...@@ -100,7 +103,7 @@ void main() {
expect(tapped, isTrue, reason: 'because the tap is prevented by ModalBarrier'); expect(tapped, isTrue, reason: 'because the tap is prevented by ModalBarrier');
}); });
testWidgets('ModalBarrier does not prevent interactions with translucent widgets in front of it', (WidgetTester tester) async { testWidgets('does not prevent interactions with translucent widgets in front of it', (WidgetTester tester) async {
bool dragged = false; bool dragged = false;
final Widget subject = Stack( final Widget subject = Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -127,7 +130,7 @@ void main() { ...@@ -127,7 +130,7 @@ void main() {
expect(dragged, isTrue, reason: 'because the drag is prevented by ModalBarrier'); expect(dragged, isTrue, reason: 'because the drag is prevented by ModalBarrier');
}); });
testWidgets('ModalBarrier does not prevent hover interactions with widgets in front of it', (WidgetTester tester) async { testWidgets('does not prevent hover interactions with widgets in front of it', (WidgetTester tester) async {
final Widget subject = Stack( final Widget subject = Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
...@@ -156,10 +159,11 @@ void main() { ...@@ -156,10 +159,11 @@ void main() {
hovered = false; hovered = false;
}); });
testWidgets('ModalBarrier plays system alert sound when user tries to dismiss it', (WidgetTester tester) async { testWidgets('plays system alert sound when user tries to dismiss it', (WidgetTester tester) async {
final List<String> playedSystemSounds = <String>[]; final List<String> playedSystemSounds = <String>[];
try { try {
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform, (MethodCall methodCall) async {
if (methodCall.method == 'SystemSound.play') { if (methodCall.method == 'SystemSound.play') {
playedSystemSounds.add(methodCall.arguments as String); playedSystemSounds.add(methodCall.arguments as String);
} }
...@@ -184,7 +188,7 @@ void main() { ...@@ -184,7 +188,7 @@ void main() {
expect(playedSystemSounds[0], SystemSoundType.alert.toString()); expect(playedSystemSounds[0], SystemSoundType.alert.toString());
}); });
testWidgets('ModalBarrier pops the Navigator when dismissed by primary tap', (WidgetTester tester) async { testWidgets('pops the Navigator when dismissed by primary tap', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const SecondWidget(), '/modal': (BuildContext context) => const SecondWidget(),
...@@ -217,7 +221,7 @@ void main() { ...@@ -217,7 +221,7 @@ void main() {
); );
}); });
testWidgets('ModalBarrier pops the Navigator when dismissed by non-primary tap', (WidgetTester tester) async { testWidgets('pops the Navigator when dismissed by non-primary tap', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const SecondWidget(), '/modal': (BuildContext context) => const SecondWidget(),
...@@ -251,7 +255,7 @@ void main() { ...@@ -251,7 +255,7 @@ void main() {
); );
}); });
testWidgets('ModalBarrier may pop the Navigator when competing with other gestures', (WidgetTester tester) async { testWidgets('may pop the Navigator when competing with other gestures', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const SecondWidgetWithCompetence(), '/modal': (BuildContext context) => const SecondWidgetWithCompetence(),
...@@ -279,11 +283,12 @@ void main() { ...@@ -279,11 +283,12 @@ void main() {
); );
}); });
testWidgets('ModalBarrier does not pop the Navigator with a WillPopScope that returns false', (WidgetTester tester) async { testWidgets('does not pop the Navigator with a WillPopScope that returns false', (WidgetTester tester) async {
bool willPopCalled = false; bool willPopCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => Stack( '/modal': (BuildContext context) =>
Stack(
children: <Widget>[ children: <Widget>[
const SecondWidget(), const SecondWidget(),
WillPopScope( WillPopScope(
...@@ -323,11 +328,12 @@ void main() { ...@@ -323,11 +328,12 @@ void main() {
expect(willPopCalled, isTrue); expect(willPopCalled, isTrue);
}); });
testWidgets('ModalBarrier pops the Navigator with a WillPopScope that returns true', (WidgetTester tester) async { testWidgets('pops the Navigator with a WillPopScope that returns true', (WidgetTester tester) async {
bool willPopCalled = false; bool willPopCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => Stack( '/modal': (BuildContext context) =>
Stack(
children: <Widget>[ children: <Widget>[
const SecondWidget(), const SecondWidget(),
WillPopScope( WillPopScope(
...@@ -367,11 +373,12 @@ void main() { ...@@ -367,11 +373,12 @@ void main() {
expect(willPopCalled, isTrue); expect(willPopCalled, isTrue);
}); });
testWidgets('ModalBarrier will call onDismiss callback', (WidgetTester tester) async { testWidgets('will call onDismiss callback', (WidgetTester tester) async {
bool dismissCallbackCalled = false; bool dismissCallbackCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => SecondWidget(onDismiss: () { '/modal': (BuildContext context) =>
SecondWidget(onDismiss: () {
dismissCallbackCalled = true; dismissCallbackCalled = true;
}), }),
}; };
...@@ -394,7 +401,7 @@ void main() { ...@@ -394,7 +401,7 @@ void main() {
expect(dismissCallbackCalled, true); expect(dismissCallbackCalled, true);
}); });
testWidgets('ModalBarrier will not pop when given an onDismiss callback', (WidgetTester tester) async { testWidgets('will not pop when given an onDismiss callback', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => SecondWidget(onDismiss: () {}), '/modal': (BuildContext context) => SecondWidget(onDismiss: () {}),
...@@ -453,9 +460,10 @@ void main() { ...@@ -453,9 +460,10 @@ void main() {
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true)); expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
semantics.dispose(); semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS}));
testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async { testWidgets(
'Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(const ModalBarrier()); await tester.pumpWidget(const ModalBarrier());
...@@ -464,8 +472,439 @@ void main() { ...@@ -464,8 +472,439 @@ void main() {
semantics.dispose(); semantics.dispose();
}); });
});
group('AnimatedModalBarrier', () {
testWidgets('prevents interactions with widgets behind it', (WidgetTester tester) async {
final Widget subject = Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
tapTarget,
AnimatedModalBarrier(dismissible: false, color: colorAnimation),
],
);
await tester.pumpWidget(subject);
await tester.tap(find.text('target'), warnIfMissed: false);
await tester.pumpWidget(subject);
expect(tapped, isFalse, reason: 'because the tap is not prevented by ModalBarrier');
});
testWidgets('prevents hover interactions with widgets behind it', (WidgetTester tester) async {
final Widget subject = Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
hoverTarget,
AnimatedModalBarrier(dismissible: false, color: colorAnimation),
],
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
// Start out of hoverTarget
await gesture.moveTo(const Offset(100, 100));
await tester.pumpWidget(subject);
// Move into hoverTarget and tap
await gesture.down(const Offset(5, 5));
await tester.pumpWidget(subject);
await gesture.up();
await tester.pumpWidget(subject);
// Move out
await gesture.moveTo(const Offset(100, 100));
await tester.pumpWidget(subject);
expect(hovered, isFalse, reason: 'because the hover is not prevented by AnimatedModalBarrier');
});
testWidgets('does not prevent interactions with widgets in front of it', (WidgetTester tester) async {
final Widget subject = Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
AnimatedModalBarrier(dismissible: false, color: colorAnimation),
tapTarget,
],
);
await tester.pumpWidget(subject);
await tester.tap(find.text('target'));
await tester.pumpWidget(subject);
expect(tapped, isTrue, reason: 'because the tap is prevented by AnimatedModalBarrier');
});
testWidgets('does not prevent interactions with translucent widgets in front of it', (WidgetTester tester) async {
bool dragged = false;
final Widget subject = Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
AnimatedModalBarrier(dismissible: false, color: colorAnimation),
GestureDetector(
behavior: HitTestBehavior.translucent,
onHorizontalDragStart: (_) {
dragged = true;
},
child: const Center(
child: Text('target', textDirection: TextDirection.ltr),
),
),
],
);
await tester.pumpWidget(subject);
await tester.dragFrom(
tester.getBottomRight(find.byType(GestureDetector)) - const Offset(10, 10),
const Offset(-20, 0),
);
await tester.pumpWidget(subject);
expect(dragged, isTrue, reason: 'because the drag is prevented by AnimatedModalBarrier');
});
testWidgets('does not prevent hover interactions with widgets in front of it', (WidgetTester tester) async {
final Widget subject = Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
AnimatedModalBarrier(dismissible: false, color: colorAnimation),
hoverTarget,
],
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
// Start out of hoverTarget
await gesture.moveTo(const Offset(100, 100));
await tester.pumpWidget(subject);
expect(hovered, isFalse);
// Move into hoverTarget
await gesture.moveTo(const Offset(5, 5));
await tester.pumpWidget(subject);
expect(hovered, isTrue, reason: 'because the hover is prevented by AnimatedModalBarrier');
hovered = false;
// Move out
await gesture.moveTo(const Offset(100, 100));
await tester.pumpWidget(subject);
expect(hovered, isTrue, reason: 'because the hover is prevented by AnimatedModalBarrier');
hovered = false;
});
testWidgets('plays system alert sound when user tries to dismiss it', (WidgetTester tester) async {
final List<String> playedSystemSounds = <String>[];
try {
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform, (MethodCall methodCall) async {
if (methodCall.method == 'SystemSound.play') {
playedSystemSounds.add(methodCall.arguments as String);
}
return null;
});
final Widget subject = Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
tapTarget,
AnimatedModalBarrier(dismissible: false, color: colorAnimation),
],
);
await tester.pumpWidget(subject);
await tester.tap(find.text('target'), warnIfMissed: false);
await tester.pumpWidget(subject);
} finally {
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null);
}
expect(playedSystemSounds, hasLength(1));
expect(playedSystemSounds[0], SystemSoundType.alert.toString());
});
testWidgets('pops the Navigator when dismissed by primary tap', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const AnimatedSecondWidget(),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
// Press the barrier; it shouldn't dismiss yet
final TestGesture gesture = await tester.press(
find.byKey(const ValueKey<String>('barrier')),
);
await tester.pumpAndSettle(); // begin transition
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
// Release the pointer; the barrier should be dismissed
await gesture.up();
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.',
);
});
testWidgets('pops the Navigator when dismissed by non-primary tap', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const AnimatedSecondWidget(),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
// Press the barrier; it shouldn't dismiss yet
final TestGesture gesture = await tester.press(
find.byKey(const ValueKey<String>('barrier')),
buttons: kSecondaryButton,
);
await tester.pumpAndSettle(); // begin transition
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
// Release the pointer; the barrier should be dismissed
await gesture.up();
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.',
);
});
testWidgets('may pop the Navigator when competing with other gestures', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const AnimatedSecondWidgetWithCompetence(),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
// Tap on the barrier to dismiss it
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.',
);
});
testWidgets('does not pop the Navigator with a WillPopScope that returns false', (WidgetTester tester) async {
bool willPopCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) =>
Stack(
children: <Widget>[
const AnimatedSecondWidget(),
WillPopScope(
child: const SizedBox(),
onWillPop: () async {
willPopCalled = true;
return false;
},
),
],
),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(willPopCalled, isFalse);
// Tap on the barrier to attempt to dismiss it
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsOneWidget,
reason: 'The route should still be present if the pop is vetoed.',
);
expect(willPopCalled, isTrue);
});
testWidgets('pops the Navigator with a WillPopScope that returns true', (WidgetTester tester) async {
bool willPopCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) =>
Stack(
children: <Widget>[
const AnimatedSecondWidget(),
WillPopScope(
child: const SizedBox(),
onWillPop: () async {
willPopCalled = true;
return true;
},
),
],
),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(willPopCalled, isFalse);
// Tap on the barrier to attempt to dismiss it
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsNothing,
reason: 'The route should not be present if the pop is permitted.',
);
expect(willPopCalled, isTrue);
});
testWidgets('will call onDismiss callback', (WidgetTester tester) async {
bool dismissCallbackCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) =>
AnimatedSecondWidget(onDismiss: () {
dismissCallbackCalled = true;
}),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
expect(dismissCallbackCalled, false);
// Tap on the barrier
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(dismissCallbackCalled, true);
});
testWidgets('will not pop when given an onDismiss callback', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => AnimatedSecondWidget(onDismiss: () {}),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
// Tap on the barrier
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsOneWidget,
reason: 'The route should not have been dismissed by tapping the barrier, as there was a onDismiss callback given.',
);
});
testWidgets('Undismissible AnimatedModalBarrier hidden in semantic tree', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(AnimatedModalBarrier(dismissible: false, color: colorAnimation));
final TestSemantics expectedSemantics = TestSemantics.root();
expect(semantics, hasSemantics(expectedSemantics));
semantics.dispose();
});
testWidgets('Dismissible AnimatedModalBarrier includes button in semantic tree on iOS', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: AnimatedModalBarrier(
semanticsLabel: 'Dismiss',
color: colorAnimation,
),
));
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
rect: TestSemantics.fullScreen,
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.dismiss],
label: 'Dismiss',
textDirection: TextDirection.ltr,
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS}));
testWidgets('ModalBarrier uses default mouse cursor', (WidgetTester tester) async { testWidgets(
'Dismissible AnimatedModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(AnimatedModalBarrier(color: colorAnimation));
final TestSemantics expectedSemantics = TestSemantics.root();
expect(semantics, hasSemantics(expectedSemantics));
semantics.dispose();
});
});
testWidgets('uses default mouse cursor', (WidgetTester tester) async {
await tester.pumpWidget(Stack( await tester.pumpWidget(Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: const <Widget>[ children: const <Widget>[
...@@ -485,7 +924,7 @@ void main() { ...@@ -485,7 +924,7 @@ void main() {
} }
class FirstWidget extends StatelessWidget { class FirstWidget extends StatelessWidget {
const FirstWidget({ Key? key }) : super(key: key); const FirstWidget({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
...@@ -498,7 +937,7 @@ class FirstWidget extends StatelessWidget { ...@@ -498,7 +937,7 @@ class FirstWidget extends StatelessWidget {
} }
class SecondWidget extends StatelessWidget { class SecondWidget extends StatelessWidget {
const SecondWidget({ Key? key, this.onDismiss }) : super(key: key); const SecondWidget({Key? key, this.onDismiss}) : super(key: key);
final VoidCallback? onDismiss; final VoidCallback? onDismiss;
...@@ -511,8 +950,23 @@ class SecondWidget extends StatelessWidget { ...@@ -511,8 +950,23 @@ class SecondWidget extends StatelessWidget {
} }
} }
class AnimatedSecondWidget extends StatelessWidget {
const AnimatedSecondWidget({Key? key, this.onDismiss}) : super(key: key);
final VoidCallback? onDismiss;
@override
Widget build(BuildContext context) {
return AnimatedModalBarrier(
key: const ValueKey<String>('barrier'),
color: const AlwaysStoppedAnimation<Color?>(Colors.red),
onDismiss: onDismiss,
);
}
}
class SecondWidgetWithCompetence extends StatelessWidget { class SecondWidgetWithCompetence extends StatelessWidget {
const SecondWidgetWithCompetence({ Key? key }) : super(key: key); const SecondWidgetWithCompetence({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
...@@ -529,3 +983,22 @@ class SecondWidgetWithCompetence extends StatelessWidget { ...@@ -529,3 +983,22 @@ class SecondWidgetWithCompetence extends StatelessWidget {
); );
} }
} }
class AnimatedSecondWidgetWithCompetence extends StatelessWidget {
const AnimatedSecondWidgetWithCompetence({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
const AnimatedModalBarrier(
key: ValueKey<String>('barrier'),
color: AlwaysStoppedAnimation<Color?>(Colors.red),
),
GestureDetector(
onVerticalDragStart: (_) {},
behavior: HitTestBehavior.translucent,
child: Container(),
),
],
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment