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.
This commit is contained in:
parent
548e8853e6
commit
db51873250
@ -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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
dismissible: dismissible,
|
dismissible: dismissible,
|
||||||
semanticsLabel: semanticsLabel,
|
semanticsLabel: semanticsLabel,
|
||||||
barrierSemanticsDismissible: barrierSemanticsDismissible,
|
barrierSemanticsDismissible: barrierSemanticsDismissible,
|
||||||
|
onDismiss: onDismiss,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
|
|
||||||
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('ModalBarrier uses default mouse cursor', (WidgetTester tester) async {
|
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(
|
||||||
|
'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() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user