Commit 2e414833 authored by Brian Egan's avatar Brian Egan Committed by Kate Lovett

Add "navigator" option to "showDialog" and "showGeneralDialog" (#42842)

parent 694b2d89
...@@ -864,6 +864,10 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -864,6 +864,10 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
/// It is only used when the method is called. Its corresponding widget can be /// It is only used when the method is called. Its corresponding widget can be
/// safely removed from the tree before the popup is closed. /// safely removed from the tree before the popup is closed.
/// ///
/// The `useRootNavigator` argument is used to determine whether to push the
/// popup to the [Navigator] furthest from or nearest to the given `context`. It
/// is `false` by default.
///
/// The `builder` argument typically builds a [CupertinoActionSheet] widget. /// The `builder` argument typically builds a [CupertinoActionSheet] widget.
/// Content below the widget is dimmed with a [ModalBarrier]. The widget built /// Content below the widget is dimmed with a [ModalBarrier]. The widget built
/// by the `builder` does not share a context with the location that /// by the `builder` does not share a context with the location that
...@@ -882,8 +886,10 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -882,8 +886,10 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
Future<T> showCupertinoModalPopup<T>({ Future<T> showCupertinoModalPopup<T>({
@required BuildContext context, @required BuildContext context,
@required WidgetBuilder builder, @required WidgetBuilder builder,
bool useRootNavigator = true,
}) { }) {
return Navigator.of(context, rootNavigator: true).push( assert(useRootNavigator != null);
return Navigator.of(context, rootNavigator: useRootNavigator).push(
_CupertinoModalPopupRoute<T>( _CupertinoModalPopupRoute<T>(
builder: builder, builder: builder,
barrierLabel: 'Dismiss', barrierLabel: 'Dismiss',
...@@ -933,14 +939,18 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> ...@@ -933,14 +939,18 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
/// It is only used when the method is called. Its corresponding widget can /// It is only used when the method is called. Its corresponding widget can
/// be safely removed from the tree before the dialog is closed. /// be safely removed from the tree before the dialog is closed.
/// ///
/// Returns a [Future] that resolves to the value (if any) that was passed to /// The `useRootNavigator` argument is used to determine whether to push the
/// [Navigator.pop] when the dialog was closed. /// dialog to the [Navigator] furthest from or nearest to the given `context`.
/// By default, `useRootNavigator` is `true` and the dialog route created by
/// this method is pushed to the root navigator.
/// ///
/// The dialog route created by this method is pushed to the root navigator.
/// If the application has multiple [Navigator] objects, it may be necessary to /// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the /// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`. /// dialog rather than just `Navigator.pop(context, result)`.
/// ///
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// See also: /// See also:
/// ///
/// * [CupertinoDialog], an iOS-style dialog. /// * [CupertinoDialog], an iOS-style dialog.
...@@ -951,8 +961,10 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> ...@@ -951,8 +961,10 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
Future<T> showCupertinoDialog<T>({ Future<T> showCupertinoDialog<T>({
@required BuildContext context, @required BuildContext context,
@required WidgetBuilder builder, @required WidgetBuilder builder,
bool useRootNavigator = true,
}) { }) {
assert(builder != null); assert(builder != null);
assert(useRootNavigator != null);
return showGeneralDialog( return showGeneralDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
...@@ -963,5 +975,6 @@ Future<T> showCupertinoDialog<T>({ ...@@ -963,5 +975,6 @@ Future<T> showCupertinoDialog<T>({
return builder(context); return builder(context);
}, },
transitionBuilder: _buildCupertinoDialogTransitions, transitionBuilder: _buildCupertinoDialogTransitions,
useRootNavigator: useRootNavigator,
); );
} }
...@@ -223,8 +223,8 @@ class AboutListTile extends StatelessWidget { ...@@ -223,8 +223,8 @@ class AboutListTile extends StatelessWidget {
/// The licenses shown on the [LicensePage] are those returned by the /// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list. /// [LicenseRegistry] API, which can be used to add more licenses to the list.
/// ///
/// The `context` argument is passed to [showDialog], the documentation for /// The [context] and [useRootNavigator] arguments are passed to [showDialog],
/// which discusses how it is used. /// the documentation for which discusses how it is used.
void showAboutDialog({ void showAboutDialog({
@required BuildContext context, @required BuildContext context,
String applicationName, String applicationName,
...@@ -232,10 +232,13 @@ void showAboutDialog({ ...@@ -232,10 +232,13 @@ void showAboutDialog({
Widget applicationIcon, Widget applicationIcon,
String applicationLegalese, String applicationLegalese,
List<Widget> children, List<Widget> children,
bool useRootNavigator = true,
}) { }) {
assert(context != null); assert(context != null);
assert(useRootNavigator != null);
showDialog<void>( showDialog<void>(
context: context, context: context,
useRootNavigator: useRootNavigator,
builder: (BuildContext context) { builder: (BuildContext context) {
return AboutDialog( return AboutDialog(
applicationName: applicationName, applicationName: applicationName,
...@@ -251,7 +254,13 @@ void showAboutDialog({ ...@@ -251,7 +254,13 @@ void showAboutDialog({
/// Displays a [LicensePage], which shows licenses for software used by the /// Displays a [LicensePage], which shows licenses for software used by the
/// application. /// application.
/// ///
/// The arguments correspond to the properties on [LicensePage]. /// The application arguments correspond to the properties on [LicensePage].
///
/// The `context` argument is used to look up the [Navigator] for the page.
///
/// The `useRootNavigator` argument is used to determine whether to push the
/// page to the [Navigator] furthest from or nearest to the given `context`. It
/// is `false` by default.
/// ///
/// If the application has a [Drawer], consider using [AboutListTile] instead /// If the application has a [Drawer], consider using [AboutListTile] instead
/// of calling this directly. /// of calling this directly.
...@@ -267,9 +276,11 @@ void showLicensePage({ ...@@ -267,9 +276,11 @@ void showLicensePage({
String applicationVersion, String applicationVersion,
Widget applicationIcon, Widget applicationIcon,
String applicationLegalese, String applicationLegalese,
bool useRootNavigator = false,
}) { }) {
assert(context != null); assert(context != null);
Navigator.push(context, MaterialPageRoute<void>( assert(useRootNavigator != null);
Navigator.of(context, rootNavigator: useRootNavigator).push(MaterialPageRoute<void>(
builder: (BuildContext context) => LicensePage( builder: (BuildContext context) => LicensePage(
applicationName: applicationName, applicationName: applicationName,
applicationVersion: applicationVersion, applicationVersion: applicationVersion,
......
...@@ -1085,8 +1085,8 @@ typedef SelectableDayPredicate = bool Function(DateTime day); ...@@ -1085,8 +1085,8 @@ typedef SelectableDayPredicate = bool Function(DateTime day);
/// provided by [Directionality]. If both [locale] and [textDirection] are not /// provided by [Directionality]. If both [locale] and [textDirection] are not
/// null, [textDirection] overrides the direction chosen for the [locale]. /// null, [textDirection] overrides the direction chosen for the [locale].
/// ///
/// The [context] argument is passed to [showDialog], the documentation for /// The [context] and [useRootNavigator] arguments are passed to [showDialog],
/// which discusses how it is used. /// the documentation for which discusses how it is used.
/// ///
/// The [builder] parameter can be used to wrap the dialog widget /// The [builder] parameter can be used to wrap the dialog widget
/// to add inherited widgets like [Theme]. /// to add inherited widgets like [Theme].
...@@ -1133,10 +1133,12 @@ Future<DateTime> showDatePicker({ ...@@ -1133,10 +1133,12 @@ Future<DateTime> showDatePicker({
Locale locale, Locale locale,
TextDirection textDirection, TextDirection textDirection,
TransitionBuilder builder, TransitionBuilder builder,
bool useRootNavigator = true,
}) async { }) async {
assert(initialDate != null); assert(initialDate != null);
assert(firstDate != null); assert(firstDate != null);
assert(lastDate != null); assert(lastDate != null);
assert(useRootNavigator != null);
assert(!initialDate.isBefore(firstDate), 'initialDate must be on or after firstDate'); assert(!initialDate.isBefore(firstDate), 'initialDate must be on or after firstDate');
assert(!initialDate.isAfter(lastDate), 'initialDate must be on or before lastDate'); assert(!initialDate.isAfter(lastDate), 'initialDate must be on or before lastDate');
assert(!firstDate.isAfter(lastDate), 'lastDate must be on or after firstDate'); assert(!firstDate.isAfter(lastDate), 'lastDate must be on or after firstDate');
...@@ -1173,6 +1175,7 @@ Future<DateTime> showDatePicker({ ...@@ -1173,6 +1175,7 @@ Future<DateTime> showDatePicker({
return await showDialog<DateTime>( return await showDialog<DateTime>(
context: context, context: context,
useRootNavigator: useRootNavigator,
builder: (BuildContext context) { builder: (BuildContext context) {
return builder == null ? child : builder(context, child); return builder == null ? child : builder(context, child);
}, },
......
...@@ -655,20 +655,24 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a ...@@ -655,20 +655,24 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a /// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically. /// custom [StatefulWidget] if the dialog needs to update dynamically.
/// ///
/// The `child` argument is deprecated, and should be replaced with `builder`.
///
/// The `context` argument is used to look up the [Navigator] and [Theme] for /// The `context` argument is used to look up the [Navigator] and [Theme] for
/// the dialog. It is only used when the method is called. Its corresponding /// the dialog. It is only used when the method is called. Its corresponding
/// widget can be safely removed from the tree before the dialog is closed. /// widget can be safely removed from the tree before the dialog is closed.
/// ///
/// The `child` argument is deprecated, and should be replaced with `builder`. /// The `useRootNavigator` argument is used to determine whether to push the
/// dialog to the [Navigator] furthest from or nearest to the given `context`.
/// By default, `useRootNavigator` is `true` and the dialog route created by
/// this method is pushed to the root navigator.
/// ///
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// The dialog route created by this method is pushed to the root navigator.
/// If the application has multiple [Navigator] objects, it may be necessary to /// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the /// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`. /// dialog rather than just `Navigator.pop(context, result)`.
/// ///
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// See also: /// See also:
/// ///
/// * [AlertDialog], for dialogs that have a row of buttons below a body. /// * [AlertDialog], for dialogs that have a row of buttons below a body.
...@@ -687,8 +691,10 @@ Future<T> showDialog<T>({ ...@@ -687,8 +691,10 @@ Future<T> showDialog<T>({
'is appropriate for widgets built in the dialog.' 'is appropriate for widgets built in the dialog.'
) Widget child, ) Widget child,
WidgetBuilder builder, WidgetBuilder builder,
bool useRootNavigator = true,
}) { }) {
assert(child == null || builder == null); assert(child == null || builder == null);
assert(useRootNavigator != null);
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context, shadowThemeOnly: true); final ThemeData theme = Theme.of(context, shadowThemeOnly: true);
...@@ -711,5 +717,6 @@ Future<T> showDialog<T>({ ...@@ -711,5 +717,6 @@ Future<T> showDialog<T>({
barrierColor: Colors.black54, barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 150), transitionDuration: const Duration(milliseconds: 150),
transitionBuilder: _buildMaterialDialogTransitions, transitionBuilder: _buildMaterialDialogTransitions,
useRootNavigator: useRootNavigator,
); );
} }
...@@ -790,6 +790,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -790,6 +790,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
/// the menu. It is only used when the method is called. Its corresponding /// the menu. It is only used when the method is called. Its corresponding
/// widget can be safely removed from the tree before the popup menu is closed. /// widget can be safely removed from the tree before the popup menu is closed.
/// ///
/// The `useRootNavigator` argument is used to determine whether to push the
/// menu to the [Navigator] furthest from or nearest to the given `context`. It
/// is `false` by default.
///
/// The `semanticLabel` argument is used by accessibility frameworks to /// The `semanticLabel` argument is used by accessibility frameworks to
/// announce screen transitions when the menu is opened and closed. If this /// announce screen transitions when the menu is opened and closed. If this
/// label is not provided, it will default to /// label is not provided, it will default to
...@@ -814,9 +818,11 @@ Future<T> showMenu<T>({ ...@@ -814,9 +818,11 @@ Future<T> showMenu<T>({
ShapeBorder shape, ShapeBorder shape,
Color color, Color color,
bool captureInheritedThemes = true, bool captureInheritedThemes = true,
bool useRootNavigator = false,
}) { }) {
assert(context != null); assert(context != null);
assert(position != null); assert(position != null);
assert(useRootNavigator != null);
assert(items != null && items.isNotEmpty); assert(items != null && items.isNotEmpty);
assert(captureInheritedThemes != null); assert(captureInheritedThemes != null);
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
...@@ -831,7 +837,7 @@ Future<T> showMenu<T>({ ...@@ -831,7 +837,7 @@ Future<T> showMenu<T>({
label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel; label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
} }
return Navigator.push(context, _PopupMenuRoute<T>( return Navigator.of(context, rootNavigator: useRootNavigator).push(_PopupMenuRoute<T>(
position: position, position: position,
items: items, items: items,
initialValue: initialValue, initialValue: initialValue,
......
...@@ -1731,8 +1731,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { ...@@ -1731,8 +1731,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
/// ``` /// ```
/// {@end-tool} /// {@end-tool}
/// ///
/// The [context] argument is passed to [showDialog], the documentation for /// The [context] and [useRootNavigator] arguments are passed to [showDialog],
/// which discusses how it is used. /// the documentation for which discusses how it is used.
/// ///
/// The [builder] parameter can be used to wrap the dialog widget /// The [builder] parameter can be used to wrap the dialog widget
/// to add inherited widgets like [Localizations.override], /// to add inherited widgets like [Localizations.override],
...@@ -1780,14 +1780,17 @@ Future<TimeOfDay> showTimePicker({ ...@@ -1780,14 +1780,17 @@ Future<TimeOfDay> showTimePicker({
@required BuildContext context, @required BuildContext context,
@required TimeOfDay initialTime, @required TimeOfDay initialTime,
TransitionBuilder builder, TransitionBuilder builder,
bool useRootNavigator = true,
}) async { }) async {
assert(context != null); assert(context != null);
assert(initialTime != null); assert(initialTime != null);
assert(useRootNavigator != null);
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
final Widget dialog = _TimePickerDialog(initialTime: initialTime); final Widget dialog = _TimePickerDialog(initialTime: initialTime);
return await showDialog<TimeOfDay>( return await showDialog<TimeOfDay>(
context: context, context: context,
useRootNavigator: useRootNavigator,
builder: (BuildContext context) { builder: (BuildContext context) {
return builder == null ? dialog : builder(context, dialog); return builder == null ? dialog : builder(context, dialog);
}, },
......
...@@ -1560,9 +1560,18 @@ class _DialogRoute<T> extends PopupRoute<T> { ...@@ -1560,9 +1560,18 @@ class _DialogRoute<T> extends PopupRoute<T> {
/// [StatefulWidget] if the dialog needs to update dynamically. The /// [StatefulWidget] if the dialog needs to update dynamically. The
/// `pageBuilder` argument can not be null. /// `pageBuilder` argument can not be null.
/// ///
/// The `context` argument is used to look up the [Navigator] for the dialog. /// The `context` argument is used to look up the [Navigator] for the
/// It is only used when the method is called. Its corresponding widget can /// dialog. It is only used when the method is called. Its corresponding widget
/// be safely removed from the tree before the dialog is closed. /// can be safely removed from the tree before the dialog is closed.
///
/// The `useRootNavigator` argument is used to determine whether to push the
/// dialog to the [Navigator] furthest from or nearest to the given `context`.
/// By default, `useRootNavigator` is `true` and the dialog route created by
/// this method is pushed to the root navigator.
///
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
/// ///
/// The `barrierDismissible` argument is used to determine whether this route /// The `barrierDismissible` argument is used to determine whether this route
/// can be dismissed by tapping the modal barrier. This argument defaults /// can be dismissed by tapping the modal barrier. This argument defaults
...@@ -1586,11 +1595,6 @@ class _DialogRoute<T> extends PopupRoute<T> { ...@@ -1586,11 +1595,6 @@ class _DialogRoute<T> extends PopupRoute<T> {
/// Returns a [Future] that resolves to the value (if any) that was passed to /// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed. /// [Navigator.pop] when the dialog was closed.
/// ///
/// The dialog route created by this method is pushed to the root navigator.
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
///
/// See also: /// See also:
/// ///
/// * [showDialog], which displays a Material-style dialog. /// * [showDialog], which displays a Material-style dialog.
...@@ -1603,10 +1607,12 @@ Future<T> showGeneralDialog<T>({ ...@@ -1603,10 +1607,12 @@ Future<T> showGeneralDialog<T>({
Color barrierColor, Color barrierColor,
Duration transitionDuration, Duration transitionDuration,
RouteTransitionsBuilder transitionBuilder, RouteTransitionsBuilder transitionBuilder,
bool useRootNavigator = true,
}) { }) {
assert(pageBuilder != null); assert(pageBuilder != null);
assert(useRootNavigator != null);
assert(!barrierDismissible || barrierLabel != null); assert(!barrierDismissible || barrierLabel != null);
return Navigator.of(context, rootNavigator: true).push<T>(_DialogRoute<T>( return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>(
pageBuilder: pageBuilder, pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible, barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel, barrierLabel: barrierLabel,
......
...@@ -918,6 +918,164 @@ void main() { ...@@ -918,6 +918,164 @@ void main() {
expect(homeTapCount, 1); expect(homeTapCount, 1);
expect(pageTapCount, 1); expect(pageTapCount, 1);
}); });
testWidgets('showCupertinoModalPopup uses root navigator by default', (WidgetTester tester) async {
final PopupObserver rootObserver = PopupObserver();
final PopupObserver nestedObserver = PopupObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.popupCount, 1);
expect(nestedObserver.popupCount, 0);
});
testWidgets('showCupertinoModalPopup uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final PopupObserver rootObserver = PopupObserver();
final PopupObserver nestedObserver = PopupObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.popupCount, 0);
expect(nestedObserver.popupCount, 1);
});
testWidgets('showCupertinoDialog uses root navigator by default', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.dialogCount, 1);
expect(nestedObserver.dialogCount, 0);
});
testWidgets('showCupertinoDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoDialog<void>(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1);
});
} }
class MockNavigatorObserver extends Mock implements NavigatorObserver {} class MockNavigatorObserver extends Mock implements NavigatorObserver {}
class PopupObserver extends NavigatorObserver {
int popupCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_CupertinoModalPopupRoute')) {
popupCount++;
}
super.didPush(route, previousRoute);
}
}
class DialogObserver extends NavigatorObserver {
int dialogCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
dialogCount++;
}
super.didPush(route, previousRoute);
}
}
...@@ -305,6 +305,150 @@ void main() { ...@@ -305,6 +305,150 @@ void main() {
tileRect = tester.getRect(find.byType(AboutListTile)); tileRect = tester.getRect(find.byType(AboutListTile));
expect(tileRect.height, 48.0); expect(tileRect.height, 48.0);
}); });
testWidgets('showLicensePage uses nested navigator by default', (WidgetTester tester) async {
final LicensePageObserver rootObserver = LicensePageObserver();
final LicensePageObserver nestedObserver = LicensePageObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
initialRoute: '/',
onGenerateRoute: (_) {
return PageRouteBuilder<dynamic>(
pageBuilder: (_, __, ___) => Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, _, __) {
return RaisedButton(
onPressed: () {
showLicensePage(
context: context,
applicationName: 'A',
);
},
child: const Text('Show License Page'),
);
},
);
},
),
);
},
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.licensePageCount, 0);
expect(nestedObserver.licensePageCount, 1);
});
testWidgets('showLicensePage uses root navigator if useRootNavigator is true', (WidgetTester tester) async {
final LicensePageObserver rootObserver = LicensePageObserver();
final LicensePageObserver nestedObserver = LicensePageObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
initialRoute: '/',
onGenerateRoute: (_) {
return PageRouteBuilder<dynamic>(
pageBuilder: (_, __, ___) => Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, _, __) {
return RaisedButton(
onPressed: () {
showLicensePage(
context: context,
useRootNavigator: true,
applicationName: 'A',
);
},
child: const Text('Show License Page'),
);
},
);
},
),
);
},
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.licensePageCount, 1);
expect(nestedObserver.licensePageCount, 0);
});
testWidgets('showAboutDialog uses root navigator by default', (WidgetTester tester) async {
final AboutDialogObserver rootObserver = AboutDialogObserver();
final AboutDialogObserver nestedObserver = AboutDialogObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showAboutDialog(
context: context,
applicationName: 'A',
);
},
child: const Text('Show About Dialog'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.dialogCount, 1);
expect(nestedObserver.dialogCount, 0);
});
testWidgets('showAboutDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final AboutDialogObserver rootObserver = AboutDialogObserver();
final AboutDialogObserver nestedObserver = AboutDialogObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
);
},
child: const Text('Show About Dialog'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1);
});
} }
class FakeLicenseEntry extends LicenseEntry { class FakeLicenseEntry extends LicenseEntry {
...@@ -322,3 +466,27 @@ class FakeLicenseEntry extends LicenseEntry { ...@@ -322,3 +466,27 @@ class FakeLicenseEntry extends LicenseEntry {
return <LicenseParagraph>[]; return <LicenseParagraph>[];
} }
} }
class LicensePageObserver extends NavigatorObserver {
int licensePageCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route is MaterialPageRoute<dynamic>) {
licensePageCount++;
}
super.didPush(route, previousRoute);
}
}
class AboutDialogObserver extends NavigatorObserver {
int dialogCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
dialogCount++;
}
super.didPush(route, previousRoute);
}
}
...@@ -897,4 +897,90 @@ void _tests() { ...@@ -897,4 +897,90 @@ void _tests() {
}); });
}); });
testWidgets('uses root navigator by default', (WidgetTester tester) async {
final DatePickerObserver rootObserver = DatePickerObserver();
final DatePickerObserver nestedObserver = DatePickerObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2018),
lastDate: DateTime(2030),
builder: (BuildContext context, Widget child) {
return const SizedBox();
},
);
},
child: const Text('Show Date Picker'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.datePickerCount, 1);
expect(nestedObserver.datePickerCount, 0);
});
testWidgets('uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final DatePickerObserver rootObserver = DatePickerObserver();
final DatePickerObserver nestedObserver = DatePickerObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showDatePicker(
context: context,
useRootNavigator: false,
initialDate: DateTime.now(),
firstDate: DateTime(2018),
lastDate: DateTime(2030),
builder: (BuildContext context, Widget child) => const SizedBox(),
);
},
child: const Text('Show Date Picker'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.datePickerCount, 0);
expect(nestedObserver.datePickerCount, 1);
});
}
class DatePickerObserver extends NavigatorObserver {
int datePickerCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
datePickerCount++;
}
super.didPush(route, previousRoute);
}
} }
...@@ -646,6 +646,77 @@ void main() { ...@@ -646,6 +646,77 @@ void main() {
await tester.pump(); await tester.pump();
}); });
testWidgets('showDialog uses root navigator by default', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showDialog<void>(
context: context,
builder: (BuildContext innerContext) {
return const AlertDialog(title: Text('Title'));
},
);
},
child: const Text('Show Dialog'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.dialogCount, 1);
expect(nestedObserver.dialogCount, 0);
});
testWidgets('showDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showDialog<void>(
context: context,
useRootNavigator: false,
builder: (BuildContext innerContext) {
return const AlertDialog(title: Text('Title'));
},
);
},
child: const Text('Show Dialog'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1);
});
group('Scrollable title and content', () { group('Scrollable title and content', () {
testWidgets('Title is scrollable', (WidgetTester tester) async { testWidgets('Title is scrollable', (WidgetTester tester) async {
final Key titleKey = UniqueKey(); final Key titleKey = UniqueKey();
...@@ -723,3 +794,15 @@ void main() { ...@@ -723,3 +794,15 @@ void main() {
}); });
}); });
} }
class DialogObserver extends NavigatorObserver {
int dialogCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
dialogCount++;
}
super.didPush(route, previousRoute);
}
}
...@@ -1114,6 +1114,83 @@ void main() { ...@@ -1114,6 +1114,83 @@ void main() {
expect(find.text('PopupMenuButton icon'), findsOneWidget); expect(find.text('PopupMenuButton icon'), findsOneWidget);
}); });
testWidgets('showMenu uses nested navigator by default', (WidgetTester tester) async {
final MenuObserver rootObserver = MenuObserver();
final MenuObserver nestedObserver = MenuObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showMenu<int>(
context: context,
position: const RelativeRect.fromLTRB(0, 0, 0, 0),
items: <PopupMenuItem<int>>[
const PopupMenuItem<int>(
value: 1, child: Text('1'),
),
],
);
},
child: const Text('Show Menu'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.menuCount, 0);
expect(nestedObserver.menuCount, 1);
});
testWidgets('showMenu uses root navigator if useRootNavigator is true', (WidgetTester tester) async {
final MenuObserver rootObserver = MenuObserver();
final MenuObserver nestedObserver = MenuObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showMenu<int>(
context: context,
useRootNavigator: true,
position: const RelativeRect.fromLTRB(0, 0, 0, 0),
items: <PopupMenuItem<int>>[
const PopupMenuItem<int>(
value: 1, child: Text('1'),
),
],
);
},
child: const Text('Show Menu'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.menuCount, 1);
expect(nestedObserver.menuCount, 0);
});
} }
class TestApp extends StatefulWidget { class TestApp extends StatefulWidget {
...@@ -1153,3 +1230,15 @@ class _TestAppState extends State<TestApp> { ...@@ -1153,3 +1230,15 @@ class _TestAppState extends State<TestApp> {
); );
} }
} }
class MenuObserver extends NavigatorObserver {
int menuCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_PopupMenuRoute')) {
menuCount++;
}
super.didPush(route, previousRoute);
}
}
...@@ -622,6 +622,73 @@ void _tests() { ...@@ -622,6 +622,73 @@ void _tests() {
// button and the right edge of the 800 wide window. // button and the right edge of the 800 wide window.
expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight); expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight);
}); });
testWidgets('uses root navigator by default', (WidgetTester tester) async {
final PickerObserver rootObserver = PickerObserver();
final PickerObserver nestedObserver = PickerObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 7, minute: 0),
);
},
child: const Text('Show Picker'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.pickerCount, 1);
expect(nestedObserver.pickerCount, 0);
});
testWidgets('uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final PickerObserver rootObserver = PickerObserver();
final PickerObserver nestedObserver = PickerObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showTimePicker(
context: context,
useRootNavigator: false,
initialTime: const TimeOfDay(hour: 7, minute: 0),
);
},
child: const Text('Show Picker'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.pickerCount, 0);
expect(nestedObserver.pickerCount, 1);
});
} }
final Finder findDialPaint = find.descendant( final Finder findDialPaint = find.descendant(
...@@ -695,3 +762,15 @@ class _CustomPainterSemanticsTester { ...@@ -695,3 +762,15 @@ class _CustomPainterSemanticsTester {
expect(tester.renderObject(findDialPaint), expectedLabels); expect(tester.renderObject(findDialPaint), expectedLabels);
} }
} }
class PickerObserver extends NavigatorObserver {
int pickerCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
pickerCount++;
}
super.didPush(route, previousRoute);
}
}
...@@ -788,6 +788,81 @@ void main() { ...@@ -788,6 +788,81 @@ void main() {
expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation);
expect(trainHopper2.currentTrain, isNull); // Has been disposed. expect(trainHopper2.currentTrain, isNull); // Has been disposed.
}); });
testWidgets('showGeneralDialog uses root navigator by default', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showGeneralDialog<void>(
context: context,
barrierDismissible: false,
transitionDuration: Duration.zero,
pageBuilder: (BuildContext innerContext, _, __) {
return const SizedBox();
},
);
},
child: const Text('Show Dialog'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.dialogCount, 1);
expect(nestedObserver.dialogCount, 0);
});
testWidgets('showGeneralDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return RaisedButton(
onPressed: () {
showGeneralDialog<void>(
useRootNavigator: false,
context: context,
barrierDismissible: false,
transitionDuration: Duration.zero,
pageBuilder: (BuildContext innerContext, _, __) {
return const SizedBox();
},
);
},
child: const Text('Show Dialog'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.byType(RaisedButton));
expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1);
});
}); });
} }
...@@ -805,3 +880,15 @@ class TestPageRouteBuilder extends PageRouteBuilder<void> { ...@@ -805,3 +880,15 @@ class TestPageRouteBuilder extends PageRouteBuilder<void> {
return CurvedAnimation(parent: super.createAnimation(), curve: Curves.easeOutExpo); return CurvedAnimation(parent: super.createAnimation(), curve: Curves.easeOutExpo);
} }
} }
class DialogObserver extends NavigatorObserver {
int dialogCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
dialogCount++;
}
super.didPush(route, previousRoute);
}
}
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