| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:ui'; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../widgets/semantics_tester.dart'; |
| |
| MaterialApp _buildAppWithDialog(Widget dialog, { ThemeData? theme, double textScaleFactor = 1.0 }) { |
| return MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Center( |
| child: ElevatedButton( |
| child: const Text('X'), |
| onPressed: () { |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return MediaQuery( |
| data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor), |
| child: dialog, |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| Material _getMaterialFromDialog(WidgetTester tester) { |
| return tester.widget<Material>(find.descendant(of: find.byType(Dialog), matching: find.byType(Material))); |
| } |
| |
| RenderParagraph _getTextRenderObjectFromDialog(WidgetTester tester, String text) { |
| return tester.element<StatelessElement>(find.descendant(of: find.byType(Dialog), matching: find.text(text))).renderObject! as RenderParagraph; |
| } |
| |
| // What was the AlertDialog's ButtonBar when many of these tests were written, |
| // is now a Padding widget with an OverflowBar child. The Padding widget's size |
| // and location match the original ButtonBar's size and location. |
| Finder _findButtonBar() { |
| return find.ancestor(of: find.byType(OverflowBar), matching: find.byType(Padding)).first; |
| } |
| |
| const ShapeBorder _defaultM2DialogShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))); |
| final ShapeBorder _defaultM3DialogShape = RoundedRectangleBorder(borderRadius: BorderRadius.circular(28.0)); |
| |
| void main() { |
| |
| final ThemeData material3Theme = ThemeData(useMaterial3: true, brightness: Brightness.dark); |
| final ThemeData material2Theme = ThemeData(useMaterial3: false, brightness: Brightness.dark); |
| |
| testWidgets('Dialog is scrollable', (WidgetTester tester) async { |
| bool didPressOk = false; |
| final AlertDialog dialog = AlertDialog( |
| content: Container( |
| height: 5000.0, |
| width: 300.0, |
| color: Colors.green[500], |
| ), |
| actions: <Widget>[ |
| TextButton( |
| onPressed: () { |
| didPressOk = true; |
| }, |
| child: const Text('OK'), |
| ), |
| ], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect(didPressOk, false); |
| await tester.tap(find.text('OK')); |
| expect(didPressOk, true); |
| }); |
| |
| testWidgets('Dialog background color from AlertDialog', (WidgetTester tester) async { |
| const Color customColor = Colors.pink; |
| const AlertDialog dialog = AlertDialog( |
| backgroundColor: customColor, |
| actions: <Widget>[ ], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog, theme: ThemeData(brightness: Brightness.dark))); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.color, customColor); |
| }); |
| |
| testWidgets('Dialog Defaults', (WidgetTester tester) async { |
| const AlertDialog dialog = AlertDialog( |
| title: Text('Title'), |
| content: Text('Y'), |
| actions: <Widget>[ ], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material2Theme)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.color, Colors.grey[800]); |
| expect(materialWidget.shape, _defaultM2DialogShape); |
| expect(materialWidget.elevation, 24.0); |
| |
| final Offset bottomLeft = tester.getBottomLeft( |
| find.descendant(of: find.byType(Dialog), matching: find.byType(Material)), |
| ); |
| expect(bottomLeft.dy, 360.0); |
| |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| await tester.pumpAndSettle(); |
| |
| await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material3Theme)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material material3Widget = _getMaterialFromDialog(tester); |
| expect(material3Widget.color, material3Theme.colorScheme.surface); |
| expect(material3Widget.shape, _defaultM3DialogShape); |
| expect(material3Widget.elevation, 6.0); |
| }); |
| |
| testWidgets('Dialog.fullscreen Defaults', (WidgetTester tester) async { |
| const String dialogTextM2 = 'Fullscreen Dialog - M2'; |
| const String dialogTextM3 = 'Fullscreen Dialog - M3'; |
| |
| await tester.pumpWidget(_buildAppWithDialog( |
| theme: material2Theme, |
| const Dialog.fullscreen( |
| child: Text(dialogTextM2), |
| ), |
| )); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(dialogTextM2), findsOneWidget); |
| |
| Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.color, Colors.grey[800]); |
| |
| // Try to dismiss the fullscreen dialog with the escape key. |
| await tester.sendKeyEvent(LogicalKeyboardKey.escape); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(dialogTextM2), findsNothing); |
| |
| await tester.pumpWidget(_buildAppWithDialog( |
| theme: material3Theme, |
| const Dialog.fullscreen( |
| child: Text(dialogTextM3), |
| ), |
| )); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(dialogTextM3), findsOneWidget); |
| |
| materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.color, material3Theme.colorScheme.surface); |
| |
| // Try to dismiss the fullscreen dialog with the escape key. |
| await tester.sendKeyEvent(LogicalKeyboardKey.escape); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(dialogTextM3), findsNothing); |
| }); |
| |
| testWidgets('Custom dialog elevation', (WidgetTester tester) async { |
| const double customElevation = 12.0; |
| const Color shadowColor = Color(0xFF000001); |
| const Color surfaceTintColor = Color(0xFF000002); |
| const AlertDialog dialog = AlertDialog( |
| actions: <Widget>[ ], |
| elevation: customElevation, |
| shadowColor: shadowColor, |
| surfaceTintColor: surfaceTintColor, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.elevation, customElevation); |
| expect(materialWidget.shadowColor, shadowColor); |
| expect(materialWidget.surfaceTintColor, surfaceTintColor); |
| }); |
| |
| testWidgets('Custom Title Text Style', (WidgetTester tester) async { |
| const String titleText = 'Title'; |
| const TextStyle titleTextStyle = TextStyle(color: Colors.pink); |
| const AlertDialog dialog = AlertDialog( |
| title: Text(titleText), |
| titleTextStyle: titleTextStyle, |
| actions: <Widget>[ ], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final RenderParagraph title = _getTextRenderObjectFromDialog(tester, titleText); |
| expect(title.text.style, titleTextStyle); |
| }); |
| |
| testWidgets('Custom Content Text Style', (WidgetTester tester) async { |
| const String contentText = 'Content'; |
| const TextStyle contentTextStyle = TextStyle(color: Colors.pink); |
| const AlertDialog dialog = AlertDialog( |
| content: Text(contentText), |
| contentTextStyle: contentTextStyle, |
| actions: <Widget>[ ], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText); |
| expect(content.text.style, contentTextStyle); |
| }); |
| |
| testWidgets('AlertDialog custom clipBehavior', (WidgetTester tester) async { |
| const AlertDialog dialog = AlertDialog( |
| actions: <Widget>[], |
| clipBehavior: Clip.antiAlias, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.clipBehavior, Clip.antiAlias); |
| }); |
| |
| testWidgets('SimpleDialog custom clipBehavior', (WidgetTester tester) async { |
| const SimpleDialog dialog = SimpleDialog( |
| clipBehavior: Clip.antiAlias, |
| children: <Widget>[], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.clipBehavior, Clip.antiAlias); |
| }); |
| |
| testWidgets('Custom dialog shape', (WidgetTester tester) async { |
| const RoundedRectangleBorder customBorder = |
| RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))); |
| const AlertDialog dialog = AlertDialog( |
| actions: <Widget>[ ], |
| shape: customBorder, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.shape, customBorder); |
| }); |
| |
| testWidgets('Null dialog shape', (WidgetTester tester) async { |
| const AlertDialog dialog = AlertDialog( |
| actions: <Widget>[ ], |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.shape, _defaultM2DialogShape); |
| }); |
| |
| testWidgets('Rectangular dialog shape', (WidgetTester tester) async { |
| const ShapeBorder customBorder = Border(); |
| const AlertDialog dialog = AlertDialog( |
| actions: <Widget>[ ], |
| shape: customBorder, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Material materialWidget = _getMaterialFromDialog(tester); |
| expect(materialWidget.shape, customBorder); |
| }); |
| |
| testWidgets('Custom dialog alignment', (WidgetTester tester) async { |
| const AlertDialog dialog = AlertDialog( |
| actions: <Widget>[ ], |
| alignment: Alignment.bottomLeft, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Offset bottomLeft = tester.getBottomLeft( |
| find.descendant(of: find.byType(Dialog), matching: find.byType(Material)), |
| ); |
| expect(bottomLeft.dx, 40.0); |
| expect(bottomLeft.dy, 576.0); |
| }); |
| |
| testWidgets('Simple dialog control test', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Material( |
| child: Center( |
| child: ElevatedButton( |
| onPressed: null, |
| child: Text('Go'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final BuildContext context = tester.element(find.text('Go')); |
| |
| final Future<int?> result = showDialog<int>( |
| context: context, |
| builder: (BuildContext context) { |
| return SimpleDialog( |
| title: const Text('Title'), |
| children: <Widget>[ |
| SimpleDialogOption( |
| onPressed: () { |
| Navigator.pop(context, 42); |
| }, |
| child: const Text('First option'), |
| ), |
| const SimpleDialogOption( |
| child: Text('Second option'), |
| ), |
| ], |
| ); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(find.text('Title'), findsOneWidget); |
| await tester.tap(find.text('First option')); |
| |
| expect(await result, equals(42)); |
| }); |
| |
| testWidgets('Can show dialog using navigator global key', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| home: const Material( |
| child: Center( |
| child: Text('Go'), |
| ), |
| ), |
| ), |
| ); |
| |
| final Future<int?> result = showDialog<int>( |
| context: navigator.currentContext!, |
| builder: (BuildContext context) { |
| return SimpleDialog( |
| title: const Text('Title'), |
| children: <Widget>[ |
| SimpleDialogOption( |
| onPressed: () { |
| Navigator.pop(context, 42); |
| }, |
| child: const Text('First option'), |
| ), |
| const SimpleDialogOption( |
| child: Text('Second option'), |
| ), |
| ], |
| ); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(find.text('Title'), findsOneWidget); |
| await tester.tap(find.text('First option')); |
| |
| expect(await result, equals(42)); |
| }); |
| |
| testWidgets('Custom padding on SimpleDialogOption', (WidgetTester tester) async { |
| const EdgeInsets customPadding = EdgeInsets.fromLTRB(4, 10, 8, 6); |
| final SimpleDialog dialog = SimpleDialog( |
| title: const Text('Title'), |
| children: <Widget>[ |
| SimpleDialogOption( |
| onPressed: () {}, |
| padding: customPadding, |
| child: const Text('First option'), |
| ), |
| ], |
| ); |
| |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Rect dialogRect = tester.getRect(find.byType(SimpleDialogOption)); |
| final Rect textRect = tester.getRect(find.text('First option')); |
| |
| expect(textRect.left, dialogRect.left + customPadding.left); |
| expect(textRect.top, dialogRect.top + customPadding.top); |
| expect(textRect.right, dialogRect.right - customPadding.right); |
| expect(textRect.bottom, dialogRect.bottom - customPadding.bottom); |
| }); |
| |
| testWidgets('Barrier dismissible', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Material( |
| child: Center( |
| child: ElevatedButton( |
| onPressed: null, |
| child: Text('Go'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final BuildContext context = tester.element(find.text('Go')); |
| |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return Container( |
| width: 100.0, |
| height: 100.0, |
| alignment: Alignment.center, |
| child: const Text('Dialog1'), |
| ); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(find.text('Dialog1'), findsOneWidget); |
| |
| // Tap on the barrier. |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(find.text('Dialog1'), findsNothing); |
| |
| showDialog<void>( |
| context: context, |
| barrierDismissible: false, |
| builder: (BuildContext context) { |
| return Container( |
| width: 100.0, |
| height: 100.0, |
| alignment: Alignment.center, |
| child: const Text('Dialog2'), |
| ); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(find.text('Dialog2'), findsOneWidget); |
| |
| // Tap on the barrier, which shouldn't do anything this time. |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(find.text('Dialog2'), findsOneWidget); |
| |
| }); |
| |
| testWidgets('Barrier color', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Center(child: Text('Test')), |
| ), |
| ); |
| final BuildContext context = tester.element(find.text('Test')); |
| |
| // Test default barrier color |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Text('Dialog'); |
| }, |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.black54); |
| |
| // Dismiss it and test a custom barrier color |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Text('Dialog'); |
| }, |
| barrierColor: Colors.pink, |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.pink); |
| }); |
| |
| testWidgets('Dialog hides underlying semantics tree', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| const String buttonText = 'A button covered by dialog overlay'; |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Material( |
| child: Center( |
| child: ElevatedButton( |
| onPressed: null, |
| child: Text(buttonText), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(semantics, includesNodeWith(label: buttonText)); |
| |
| final BuildContext context = tester.element(find.text(buttonText)); |
| |
| const String alertText = 'A button in an overlay alert'; |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const AlertDialog(title: Text(alertText)); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| |
| expect(semantics, includesNodeWith(label: alertText)); |
| expect(semantics, isNot(includesNodeWith(label: buttonText))); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgets('AlertDialog.actionsPadding defaults', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| onPressed: () {}, |
| child: const Text('button'), |
| ), |
| ], |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| // The [AlertDialog] is the entire screen, since it also contains the scrim. |
| // The first [Material] child of [AlertDialog] is the actual dialog |
| // itself. |
| final Size dialogSize = tester.getSize( |
| find.descendant( |
| of: find.byType(AlertDialog), |
| matching: find.byType(Material), |
| ).first, |
| ); |
| final Size actionsSize = tester.getSize(_findButtonBar()); |
| |
| expect(actionsSize.width, dialogSize.width); |
| }); |
| |
| testWidgets('AlertDialog.actionsPadding surrounds actions with padding', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| onPressed: () {}, |
| child: const Text('button'), |
| ), |
| ], |
| // The OverflowBar is inset by the buttonPadding/2 + actionsPadding |
| buttonPadding: EdgeInsets.zero, |
| actionsPadding: const EdgeInsets.all(30.0), // custom padding value |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| // The [AlertDialog] is the entire screen, since it also contains the scrim. |
| // The first [Material] child of [AlertDialog] is the actual dialog |
| // itself. |
| final Size dialogSize = tester.getSize( |
| find.descendant( |
| of: find.byType(AlertDialog), |
| matching: find.byType(Material), |
| ).first, |
| ); |
| final Size actionsSize = tester.getSize(find.byType(OverflowBar)); |
| |
| expect(actionsSize.width, dialogSize.width - (30.0 * 2)); |
| }); |
| |
| testWidgets('AlertDialog.buttonPadding defaults', (WidgetTester tester) async { |
| final GlobalKey key1 = GlobalKey(); |
| final GlobalKey key2 = GlobalKey(); |
| |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| key: key1, |
| onPressed: () {}, |
| child: const Text('button 1'), |
| ), |
| ElevatedButton( |
| key: key2, |
| onPressed: () {}, |
| child: const Text('button 2'), |
| ), |
| ], |
| ); |
| |
| await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material2Theme)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| // Padding between both buttons |
| expect( |
| tester.getBottomLeft(find.byKey(key2)).dx, |
| tester.getBottomRight(find.byKey(key1)).dx + 8.0, |
| ); |
| |
| // Padding between button and edges of the button bar |
| // First button |
| expect( |
| tester.getTopRight(find.byKey(key1)).dy, |
| tester.getTopRight(_findButtonBar()).dy + 8.0, |
| ); // top |
| expect( |
| tester.getBottomRight(find.byKey(key1)).dy, |
| tester.getBottomRight(_findButtonBar()).dy - 8.0, |
| ); // bottom |
| |
| // Second button |
| expect( |
| tester.getTopRight(find.byKey(key2)).dy, |
| tester.getTopRight(_findButtonBar()).dy + 8.0, |
| ); // top |
| expect( |
| tester.getBottomRight(find.byKey(key2)).dy, |
| tester.getBottomRight(_findButtonBar()).dy - 8.0, |
| ); // bottom |
| expect( |
| tester.getBottomRight(find.byKey(key2)).dx, |
| tester.getBottomRight(_findButtonBar()).dx - 8.0, |
| ); // right |
| |
| // Dismiss it and test material 3 dialog |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| await tester.pumpAndSettle(); |
| |
| await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material3Theme)); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| // Padding between both buttons |
| expect( |
| tester.getBottomLeft(find.byKey(key2)).dx, |
| tester.getBottomRight(find.byKey(key1)).dx + 8.0, |
| ); |
| |
| // Padding between button and edges of the button bar |
| // First button |
| expect( |
| tester.getTopRight(find.byKey(key1)).dy, |
| tester.getTopRight(_findButtonBar()).dy, |
| ); // top |
| expect( |
| tester.getBottomRight(find.byKey(key1)).dy, |
| tester.getBottomRight(_findButtonBar()).dy - 24.0, |
| ); // bottom |
| |
| // // Second button |
| expect( |
| tester.getTopRight(find.byKey(key2)).dy, |
| tester.getTopRight(_findButtonBar()).dy, |
| ); // top |
| expect( |
| tester.getBottomRight(find.byKey(key2)).dy, |
| tester.getBottomRight(_findButtonBar()).dy - 24.0, |
| ); // bottom |
| expect( |
| tester.getBottomRight(find.byKey(key2)).dx, |
| tester.getBottomRight(_findButtonBar()).dx - 24.0, |
| ); // right |
| }); |
| |
| testWidgets('AlertDialog.buttonPadding custom values', (WidgetTester tester) async { |
| final GlobalKey key1 = GlobalKey(); |
| final GlobalKey key2 = GlobalKey(); |
| |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| key: key1, |
| onPressed: () {}, |
| child: const Text('button 1'), |
| ), |
| ElevatedButton( |
| key: key2, |
| onPressed: () {}, |
| child: const Text('button 2'), |
| ), |
| ], |
| buttonPadding: const EdgeInsets.only( |
| left: 10.0, |
| right: 20.0, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| // Padding between both buttons |
| expect( |
| tester.getBottomLeft(find.byKey(key2)).dx, |
| tester.getBottomRight(find.byKey(key1)).dx + ((10.0 + 20.0) / 2), |
| ); |
| |
| // Padding between button and edges of the button bar |
| // First button |
| expect( |
| tester.getTopRight(find.byKey(key1)).dy, |
| tester.getTopRight(_findButtonBar()).dy + ((10.0 + 20.0) / 2), |
| ); // top |
| expect( |
| tester.getBottomRight(find.byKey(key1)).dy, |
| tester.getBottomRight(_findButtonBar()).dy - ((10.0 + 20.0) / 2), |
| ); // bottom |
| |
| // Second button |
| expect( |
| tester.getTopRight(find.byKey(key2)).dy, |
| tester.getTopRight(_findButtonBar()).dy + ((10.0 + 20.0) / 2), |
| ); // top |
| expect( |
| tester.getBottomRight(find.byKey(key2)).dy, |
| tester.getBottomRight(_findButtonBar()).dy - ((10.0 + 20.0) / 2), |
| ); // bottom |
| expect( |
| tester.getBottomRight(find.byKey(key2)).dx, |
| tester.getBottomRight(_findButtonBar()).dx - ((10.0 + 20.0) / 2), |
| ); // right |
| }); |
| |
| group('Dialog children padding is correct', () { |
| final List<double> textScaleFactors = <double>[0.5, 1.0, 1.5, 2.0, 3.0]; |
| final Map<double, double> paddingScaleFactors = <double, double>{ |
| 0.5: 1.0, |
| 1.0: 1.0, |
| 1.5: 2.0 / 3.0, |
| 2.0: 1.0 / 3.0, |
| 3.0: 1.0 / 3.0, |
| }; |
| |
| final GlobalKey iconKey = GlobalKey(); |
| final GlobalKey titleKey = GlobalKey(); |
| final GlobalKey contentKey = GlobalKey(); |
| final GlobalKey childrenKey = GlobalKey(); |
| |
| final Finder dialogFinder = find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first; |
| final Finder iconFinder = find.byKey(iconKey); |
| final Finder titleFinder = find.byKey(titleKey); |
| final Finder contentFinder = find.byKey(contentKey); |
| final Finder actionsFinder = _findButtonBar(); |
| final Finder childrenFinder = find.byKey(childrenKey); |
| |
| Future<void> openDialog(WidgetTester tester, Widget dialog, double textScaleFactor, {bool isM3 = false}) async { |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog, textScaleFactor: textScaleFactor, theme: ThemeData(useMaterial3: isM3)), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| } |
| |
| void expectLeftEdgePadding( |
| WidgetTester tester, { |
| required Finder finder, |
| required double textScaleFactor, |
| required double unscaledValue, |
| }) { |
| expect( |
| tester.getTopLeft(dialogFinder).dx, |
| moreOrLessEquals(tester.getTopLeft(finder).dx - unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| expect( |
| tester.getBottomLeft(dialogFinder).dx, |
| moreOrLessEquals(tester.getBottomLeft(finder).dx - unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| } |
| |
| void expectRightEdgePadding( |
| WidgetTester tester, { |
| required Finder finder, |
| required double textScaleFactor, |
| required double unscaledValue, |
| }) { |
| expect( |
| tester.getTopRight(dialogFinder).dx, |
| moreOrLessEquals(tester.getTopRight(finder).dx + unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| expect( |
| tester.getBottomRight(dialogFinder).dx, |
| moreOrLessEquals(tester.getBottomRight(finder).dx + unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| } |
| |
| void expectTopEdgePadding( |
| WidgetTester tester, { |
| required Finder finder, |
| required double textScaleFactor, |
| required double unscaledValue, |
| }) { |
| expect( |
| tester.getTopLeft(dialogFinder).dy, |
| moreOrLessEquals(tester.getTopLeft(finder).dy - unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| expect( |
| tester.getTopRight(dialogFinder).dy, |
| moreOrLessEquals(tester.getTopRight(finder).dy - unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| } |
| |
| void expectBottomEdgePadding( |
| WidgetTester tester, { |
| required Finder finder, |
| required double textScaleFactor, |
| required double unscaledValue, |
| }) { |
| expect( |
| tester.getBottomLeft(dialogFinder).dy, |
| moreOrLessEquals(tester.getBottomRight(finder).dy + unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| expect( |
| tester.getBottomRight(dialogFinder).dy, |
| moreOrLessEquals(tester.getBottomRight(finder).dy + unscaledValue * paddingScaleFactors[textScaleFactor]!), |
| ); |
| } |
| |
| void expectVerticalInnerPadding( |
| WidgetTester tester, { |
| required Finder top, |
| required Finder bottom, |
| required double value, |
| }) { |
| expect( |
| tester.getBottomLeft(top).dy, |
| tester.getTopLeft(bottom).dy - value, |
| ); |
| expect( |
| tester.getBottomRight(top).dy, |
| tester.getTopRight(bottom).dy - value, |
| ); |
| } |
| |
| final Widget icon = Icon( |
| Icons.ac_unit, |
| key: iconKey, |
| ); |
| final Widget title = Text( |
| 'title', |
| key: titleKey, |
| ); |
| final Widget content = Text( |
| 'content', |
| key: contentKey, |
| ); |
| final List<Widget> actions = <Widget>[ |
| ElevatedButton( |
| onPressed: () {}, |
| child: const Text('button'), |
| ), |
| ]; |
| final List<Widget> children = <Widget>[ |
| SimpleDialogOption( |
| key: childrenKey, |
| child: const Text('child'), |
| onPressed: () { }, |
| ), |
| ]; |
| |
| for (final double textScaleFactor in textScaleFactors) { |
| testWidgets('AlertDialog padding is correct when only icon and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| icon: icon, |
| actions: actions, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: iconFinder, |
| bottom: actionsFinder, |
| value: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| }); |
| |
| testWidgets('AlertDialog padding is correct when only icon, title and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| icon: icon, |
| title: title, |
| actions: actions, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: iconFinder, |
| bottom: titleFinder, |
| value: 16.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: titleFinder, |
| bottom: actionsFinder, |
| value: 20.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| }); |
| |
| for (final bool isM3 in <bool>[true, false]) { |
| testWidgets('AlertDialog padding is correct when only icon, content and actions are specified [textScaleFactor]=$textScaleFactor [isM3]=$isM3', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| icon: icon, |
| content: content, |
| actions: actions, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor, isM3: isM3); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: iconFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: iconFinder, |
| bottom: contentFinder, |
| value: isM3 ? 16.0 : 20.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: contentFinder, |
| bottom: actionsFinder, |
| value: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| }); |
| } |
| |
| testWidgets('AlertDialog padding is correct when only title and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| title: title, |
| actions: actions, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: titleFinder, |
| bottom: actionsFinder, |
| value: 20.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| }); |
| |
| testWidgets('AlertDialog padding is correct when only content and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| content: content, |
| actions: actions, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 20.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: contentFinder, |
| bottom: actionsFinder, |
| value: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| }); |
| |
| testWidgets('AlertDialog padding is correct when title, content, and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final AlertDialog dialog = AlertDialog( |
| title: title, |
| content: content, |
| actions: actions, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: titleFinder, |
| bottom: contentFinder, |
| value: 20.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: contentFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: contentFinder, |
| bottom: actionsFinder, |
| value: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: actionsFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| }); |
| |
| testWidgets('SimpleDialog padding is correct when only children are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final SimpleDialog dialog = SimpleDialog( |
| children: children, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 12.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 16.0, |
| ); |
| }); |
| |
| testWidgets('SimpleDialog padding is correct when title and children are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async { |
| final SimpleDialog dialog = SimpleDialog( |
| title: title, |
| children: children, |
| ); |
| |
| await openDialog(tester, dialog, textScaleFactor); |
| |
| expectTopEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: titleFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 24.0, |
| ); |
| expectVerticalInnerPadding( |
| tester, |
| top: titleFinder, |
| bottom: childrenFinder, |
| value: 12.0, |
| ); |
| expectLeftEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectRightEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 0.0, |
| ); |
| expectBottomEdgePadding( |
| tester, |
| finder: childrenFinder, |
| textScaleFactor: textScaleFactor, |
| unscaledValue: 16.0, |
| ); |
| }); |
| } |
| }); |
| |
| testWidgets('Dialogs can set the vertical direction of overflowing actions', (WidgetTester tester) async { |
| final GlobalKey key1 = GlobalKey(); |
| final GlobalKey key2 = GlobalKey(); |
| |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| key: key1, |
| onPressed: () {}, |
| child: const Text('Looooooooooooooong button 1'), |
| ), |
| ElevatedButton( |
| key: key2, |
| onPressed: () {}, |
| child: const Text('Looooooooooooooong button 2'), |
| ), |
| ], |
| actionsOverflowDirection: VerticalDirection.up, |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
| final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
| // Second [ElevatedButton] should appear above the first. |
| expect(buttonTwoRect.bottom, lessThanOrEqualTo(buttonOneRect.top)); |
| }); |
| |
| testWidgets('Dialogs have no spacing by default for overflowing actions', (WidgetTester tester) async { |
| final GlobalKey key1 = GlobalKey(); |
| final GlobalKey key2 = GlobalKey(); |
| |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| key: key1, |
| onPressed: () {}, |
| child: const Text('Looooooooooooooong button 1'), |
| ), |
| ElevatedButton( |
| key: key2, |
| onPressed: () {}, |
| child: const Text('Looooooooooooooong button 2'), |
| ), |
| ], |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
| final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
| expect(buttonOneRect.bottom, buttonTwoRect.top); |
| }); |
| |
| testWidgets('Dialogs can set the button spacing of overflowing actions', (WidgetTester tester) async { |
| final GlobalKey key1 = GlobalKey(); |
| final GlobalKey key2 = GlobalKey(); |
| |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| key: key1, |
| onPressed: () {}, |
| child: const Text('Looooooooooooooong button 1'), |
| ), |
| ElevatedButton( |
| key: key2, |
| onPressed: () {}, |
| child: const Text('Looooooooooooooong button 2'), |
| ), |
| ], |
| actionsOverflowButtonSpacing: 10.0, |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
| final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
| expect(buttonOneRect.bottom, buttonTwoRect.top - 10.0); |
| }); |
| |
| testWidgets('Dialogs can set the alignment of the OverflowBar', (WidgetTester tester) async { |
| final GlobalKey key1 = GlobalKey(); |
| final GlobalKey key2 = GlobalKey(); |
| |
| final AlertDialog dialog = AlertDialog( |
| title: const Text('title'), |
| content: const Text('content'), |
| actions: <Widget>[ |
| ElevatedButton( |
| key: key1, |
| onPressed: () {}, |
| child: const Text('Loooooooooog button 1'), |
| ), |
| ElevatedButton( |
| key: key2, |
| onPressed: () {}, |
| child: const Text('Loooooooooooooonger button 2'), |
| ), |
| ], |
| actionsOverflowAlignment: OverflowBarAlignment.center, |
| ); |
| |
| await tester.pumpWidget( |
| _buildAppWithDialog(dialog), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
| final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
| expect(buttonOneRect.center.dx, buttonTwoRect.center.dx); |
| }); |
| |
| testWidgets('Dialogs removes MediaQuery padding and view insets', (WidgetTester tester) async { |
| late BuildContext outerContext; |
| late BuildContext routeContext; |
| late BuildContext dialogContext; |
| |
| await tester.pumpWidget(Localizations( |
| locale: const Locale('en', 'US'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.all(50.0), |
| viewInsets: EdgeInsets.only(left: 25.0, bottom: 75.0), |
| ), |
| child: Navigator( |
| onGenerateRoute: (_) { |
| return PageRouteBuilder<void>( |
| pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| outerContext = context; |
| return Container(); |
| }, |
| ); |
| }, |
| ), |
| ), |
| )); |
| |
| showDialog<void>( |
| context: outerContext, |
| barrierDismissible: false, |
| builder: (BuildContext context) { |
| routeContext = context; |
| return Dialog( |
| child: Builder( |
| builder: (BuildContext context) { |
| dialogContext = context; |
| return const Placeholder(); |
| }, |
| ), |
| ); |
| }, |
| ); |
| |
| await tester.pump(); |
| |
| expect(MediaQuery.of(outerContext).padding, const EdgeInsets.all(50.0)); |
| expect(MediaQuery.of(routeContext).padding, EdgeInsets.zero); |
| expect(MediaQuery.of(dialogContext).padding, EdgeInsets.zero); |
| expect(MediaQuery.of(outerContext).viewInsets, const EdgeInsets.only(left: 25.0, bottom: 75.0)); |
| expect(MediaQuery.of(routeContext).viewInsets, const EdgeInsets.only(left: 25.0, bottom: 75.0)); |
| expect(MediaQuery.of(dialogContext).viewInsets, EdgeInsets.zero); |
| }); |
| |
| testWidgets('Dialog widget insets by viewInsets', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MediaQuery( |
| data: MediaQueryData( |
| viewInsets: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0), |
| ), |
| child: Dialog( |
| child: Placeholder(), |
| ), |
| ), |
| ); |
| expect( |
| tester.getRect(find.byType(Placeholder)), |
| const Rect.fromLTRB(10.0 + 40.0, 20.0 + 24.0, 800.0 - (40.0 + 30.0), 600.0 - (24.0 + 40.0)), |
| ); |
| await tester.pumpWidget( |
| const MediaQuery( |
| data: MediaQueryData(), |
| child: Dialog( |
| child: Placeholder(), |
| ), |
| ), |
| ); |
| expect( // no change because this is an animation |
| tester.getRect(find.byType(Placeholder)), |
| const Rect.fromLTRB(10.0 + 40.0, 20.0 + 24.0, 800.0 - (40.0 + 30.0), 600.0 - (24.0 + 40.0)), |
| ); |
| await tester.pump(const Duration(seconds: 1)); |
| expect( // animation finished |
| tester.getRect(find.byType(Placeholder)), |
| const Rect.fromLTRB(40.0, 24.0, 800.0 - 40.0, 600.0 - 24.0), |
| ); |
| }); |
| |
| testWidgets('Dialog insetPadding added to outside of dialog', (WidgetTester tester) async { |
| // The default testing screen (800, 600) |
| const Rect screenRect = Rect.fromLTRB(0.0, 0.0, 800.0, 600.0); |
| |
| // Test with no padding |
| await tester.pumpWidget( |
| const MediaQuery( |
| data: MediaQueryData(), |
| child: Dialog( |
| insetPadding: null, |
| child: Placeholder(), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getRect(find.byType(Placeholder)), screenRect); |
| |
| // Test with an insetPadding |
| await tester.pumpWidget( |
| const MediaQuery( |
| data: MediaQueryData(), |
| child: Dialog( |
| insetPadding: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0), |
| child: Placeholder(), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byType(Placeholder)), |
| Rect.fromLTRB( |
| screenRect.left + 10.0, |
| screenRect.top + 20.0, |
| screenRect.right - 30.0, |
| screenRect.bottom - 40.0, |
| ), |
| ); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/78229. |
| testWidgets('AlertDialog has correct semantics for content in iOS', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.iOS), |
| home: const AlertDialog( |
| title: Text('title'), |
| content: Text('content'), |
| actions: <Widget>[ TextButton(onPressed: null, child: Text('action')) ], |
| ), |
| ), |
| ); |
| |
| expect(semantics, hasSemantics(TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 1, |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 2, |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 3, |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 4, |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 5, |
| label: 'title', |
| textDirection: TextDirection.ltr, |
| ), |
| // The content semantics does not merge into the semantics |
| // node 4. |
| TestSemantics( |
| id: 6, |
| label: 'content', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| id: 7, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.hasEnabledState, |
| ], |
| label: 'action', |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), ignoreTransform: true, ignoreId: true, ignoreRect: true)); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgets('AlertDialog widget always contains alert route semantics for android', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.android), |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Center( |
| child: ElevatedButton( |
| child: const Text('X'), |
| onPressed: () { |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const AlertDialog( |
| title: Text('Title'), |
| content: Text('Y'), |
| actions: <Widget>[], |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(semantics, isNot(includesNodeWith( |
| label: 'Title', |
| flags: <SemanticsFlag>[SemanticsFlag.namesRoute], |
| ))); |
| expect(semantics, isNot(includesNodeWith( |
| label: 'Alert', |
| flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute], |
| ))); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| // It does not use 'Title' as route semantics |
| expect(semantics, isNot(includesNodeWith( |
| label: 'Title', |
| flags: <SemanticsFlag>[SemanticsFlag.namesRoute], |
| ))); |
| expect(semantics, includesNodeWith( |
| label: 'Alert', |
| flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute], |
| )); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgets('SimpleDialog does not introduce additional node', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.android), |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Center( |
| child: ElevatedButton( |
| child: const Text('X'), |
| onPressed: () { |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const SimpleDialog( |
| title: Text('Title'), |
| semanticLabel: 'label', |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| // A scope route is not focusable in accessibility service. |
| expect(semantics, includesNodeWith( |
| label: 'label', |
| flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute], |
| )); |
| |
| semantics.dispose(); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/78229. |
| testWidgets('SimpleDialog has correct semantics for title in iOS', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.iOS), |
| home: const SimpleDialog( |
| title: Text('title'), |
| children: <Widget>[ |
| Text('content'), |
| TextButton(onPressed: null, child: Text('action')), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(semantics, hasSemantics(TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 1, |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 2, |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 3, |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 4, |
| children: <TestSemantics>[ |
| // Title semantics does not merge into the semantics |
| // node 4. |
| TestSemantics( |
| id: 5, |
| label: 'title', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| id: 6, |
| flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], |
| children: <TestSemantics>[ |
| TestSemantics( |
| id: 7, |
| label: 'content', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| id: 8, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.hasEnabledState, |
| ], |
| label: 'action', |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), ignoreTransform: true, ignoreId: true, ignoreRect: true)); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgets('Dismissible.confirmDismiss defers to an AlertDialog', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| final List<int> dismissedItems = <int>[]; |
| |
| // Dismiss is confirmed IFF confirmDismiss() returns true. |
| Future<bool?> confirmDismiss (DismissDirection dismissDirection) async { |
| return showDialog<bool>( |
| context: scaffoldKey.currentContext!, |
| builder: (BuildContext context) { |
| return AlertDialog( |
| actions: <Widget>[ |
| TextButton( |
| child: const Text('TRUE'), |
| onPressed: () { |
| Navigator.pop(context, true); // showDialog() returns true |
| }, |
| ), |
| TextButton( |
| child: const Text('FALSE'), |
| onPressed: () { |
| Navigator.pop(context, false); // showDialog() returns false |
| }, |
| ), |
| ], |
| ); |
| }, |
| ); |
| } |
| |
| Widget buildDismissibleItem(int item, StateSetter setState) { |
| return Dismissible( |
| key: ValueKey<int>(item), |
| confirmDismiss: confirmDismiss, |
| onDismissed: (DismissDirection direction) { |
| setState(() { |
| expect(dismissedItems.contains(item), isFalse); |
| dismissedItems.add(item); |
| }); |
| }, |
| child: SizedBox( |
| height: 100.0, |
| child: Text(item.toString()), |
| ), |
| ); |
| } |
| |
| Widget buildFrame() { |
| return MaterialApp( |
| home: StatefulBuilder( |
| builder: (BuildContext context, StateSetter setState) { |
| return Scaffold( |
| key: scaffoldKey, |
| body: Padding( |
| padding: const EdgeInsets.all(16.0), |
| child: ListView( |
| itemExtent: 100.0, |
| children: <int>[0, 1, 2, 3, 4] |
| .where((int i) => !dismissedItems.contains(i)) |
| .map<Widget>((int item) => buildDismissibleItem(item, setState)).toList(), |
| ), |
| ), |
| ); |
| }, |
| ), |
| ); |
| } |
| |
| Future<void> dismissItem(WidgetTester tester, int item) async { |
| await tester.fling(find.text(item.toString()), const Offset(300.0, 0.0), 1000.0); // fling to the right |
| await tester.pump(); // start the slide |
| await tester.pump(const Duration(seconds: 1)); // finish the slide and start shrinking... |
| await tester.pump(); // first frame of shrinking animation |
| await tester.pump(const Duration(seconds: 1)); // finish the shrinking and call the callback... |
| await tester.pump(); // rebuild after the callback removes the entry |
| } |
| |
| // Dismiss item 0 is confirmed via the AlertDialog |
| await tester.pumpWidget(buildFrame()); |
| expect(dismissedItems, isEmpty); |
| await dismissItem(tester, 0); // Causes the AlertDialog to appear per confirmDismiss |
| await tester.pumpAndSettle(); |
| await tester.tap(find.text('TRUE')); // AlertDialog action |
| await tester.pumpAndSettle(); |
| expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
| expect(find.text('FALSE'), findsNothing); |
| expect(dismissedItems, <int>[0]); |
| expect(find.text('0'), findsNothing); |
| |
| // Dismiss item 1 is not confirmed via the AlertDialog |
| await tester.pumpWidget(buildFrame()); |
| expect(dismissedItems, <int>[0]); |
| await dismissItem(tester, 1); // Causes the AlertDialog to appear per confirmDismiss |
| await tester.pumpAndSettle(); |
| await tester.tap(find.text('FALSE')); // AlertDialog action |
| await tester.pumpAndSettle(); |
| expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
| expect(find.text('FALSE'), findsNothing); |
| expect(dismissedItems, <int>[0]); |
| expect(find.text('0'), findsNothing); |
| expect(find.text('1'), findsOneWidget); |
| |
| // Dismiss item 1 is not confirmed via the AlertDialog |
| await tester.pumpWidget(buildFrame()); |
| expect(dismissedItems, <int>[0]); |
| await dismissItem(tester, 1); // Causes the AlertDialog to appear per confirmDismiss |
| await tester.pumpAndSettle(); |
| expect(find.text('FALSE'), findsOneWidget); |
| expect(find.text('TRUE'), findsOneWidget); |
| await tester.tapAt(Offset.zero); // Tap outside of the AlertDialog |
| await tester.pumpAndSettle(); |
| expect(dismissedItems, <int>[0]); |
| expect(find.text('0'), findsNothing); |
| expect(find.text('1'), findsOneWidget); |
| expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
| expect(find.text('FALSE'), findsNothing); |
| |
| // Dismiss item 1 is confirmed via the AlertDialog |
| await tester.pumpWidget(buildFrame()); |
| expect(dismissedItems, <int>[0]); |
| await dismissItem(tester, 1); // Causes the AlertDialog to appear per confirmDismiss |
| await tester.pumpAndSettle(); |
| await tester.tap(find.text('TRUE')); // AlertDialog action |
| await tester.pumpAndSettle(); |
| expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
| expect(find.text('FALSE'), findsNothing); |
| expect(dismissedItems, <int>[0, 1]); |
| expect(find.text('0'), findsNothing); |
| expect(find.text('1'), findsNothing); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/28505. |
| testWidgets('showDialog only gets Theme from context on the first call', (WidgetTester tester) async { |
| Widget buildFrame(Key builderKey) { |
| return MaterialApp( |
| home: Center( |
| child: Builder( |
| key: builderKey, |
| builder: (BuildContext outerContext) { |
| return ElevatedButton( |
| onPressed: () { |
| showDialog<void>( |
| context: outerContext, |
| builder: (BuildContext innerContext) { |
| return const AlertDialog(title: Text('Title')); |
| }, |
| ); |
| }, |
| child: const Text('Show Dialog'), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(UniqueKey())); |
| |
| // Open the dialog. |
| await tester.tap(find.byType(ElevatedButton)); |
| await tester.pumpAndSettle(); |
| |
| // Force the Builder to be recreated (new key) which causes outerContext to |
| // be deactivated. If showDialog()'s implementation were to refer to |
| // outerContext again, it would crash. |
| await tester.pumpWidget(buildFrame(UniqueKey())); |
| await tester.pump(); |
| }); |
| |
| testWidgets('showDialog safe area', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| builder: (BuildContext context, Widget? child) { |
| return MediaQuery( |
| // Set up the safe area to be 20 pixels in from each side |
| data: const MediaQueryData(padding: EdgeInsets.all(20.0)), |
| child: child!, |
| ); |
| }, |
| home: const Center(child: Text('Test')), |
| ), |
| ); |
| final BuildContext context = tester.element(find.text('Test')); |
| |
| // By default it should honor the safe area |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Placeholder(); |
| }, |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0)); |
| expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0)); |
| |
| // Dismiss it and test with useSafeArea off |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Placeholder(); |
| }, |
| useSafeArea: false, |
| ); |
| await tester.pumpAndSettle(); |
| // Should take up the whole screen |
| expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero); |
| expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); |
| }); |
| |
| 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 ElevatedButton( |
| 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(ElevatedButton)); |
| |
| 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 ElevatedButton( |
| 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(ElevatedButton)); |
| |
| expect(rootObserver.dialogCount, 0); |
| expect(nestedObserver.dialogCount, 1); |
| }); |
| |
| testWidgets('showDialog throws a friendly user message when context is not active', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/12467 |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Center(child: Text('Test')), |
| ), |
| ); |
| final BuildContext context = tester.element(find.text('Test')); |
| |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Center(), |
| ), |
| ); |
| |
| Object? error; |
| try { |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext innerContext) { |
| return const AlertDialog(title: Text('Title')); |
| }, |
| ); |
| } catch(exception) { |
| error = exception; |
| } |
| |
| expect(error, isNotNull); |
| expect(error, isFlutterError); |
| if (error is FlutterError) { |
| final ErrorSummary summary = error.diagnostics.first as ErrorSummary; |
| expect(summary.toString(), 'This BuildContext is no longer valid.'); |
| } |
| }); |
| |
| group('showDialog avoids overlapping display features', () { |
| testWidgets('positioning with anchorPoint', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| builder: (BuildContext context, Widget? child) { |
| return MediaQuery( |
| // Display has a vertical hinge down the middle |
| data: const MediaQueryData( |
| size: Size(800, 600), |
| displayFeatures: <DisplayFeature>[ |
| DisplayFeature( |
| bounds: Rect.fromLTRB(390, 0, 410, 600), |
| type: DisplayFeatureType.hinge, |
| state: DisplayFeatureState.unknown, |
| ), |
| ], |
| ), |
| child: child!, |
| ); |
| }, |
| home: const Center(child: Text('Test')), |
| ), |
| ); |
| final BuildContext context = tester.element(find.text('Test')); |
| |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Placeholder(); |
| }, |
| anchorPoint: const Offset(1000, 0), |
| ); |
| await tester.pumpAndSettle(); |
| |
| // Should take the right side of the screen |
| expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0)); |
| expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); |
| }); |
| |
| testWidgets('positioning with Directionality', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| builder: (BuildContext context, Widget? child) { |
| return MediaQuery( |
| // Display has a vertical hinge down the middle |
| data: const MediaQueryData( |
| size: Size(800, 600), |
| displayFeatures: <DisplayFeature>[ |
| DisplayFeature( |
| bounds: Rect.fromLTRB(390, 0, 410, 600), |
| type: DisplayFeatureType.hinge, |
| state: DisplayFeatureState.unknown, |
| ), |
| ], |
| ), |
| child: Directionality( |
| textDirection: TextDirection.rtl, |
| child: child!, |
| ), |
| ); |
| }, |
| home: const Center(child: Text('Test')), |
| ), |
| ); |
| final BuildContext context = tester.element(find.text('Test')); |
| |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Placeholder(); |
| }, |
| ); |
| await tester.pumpAndSettle(); |
| |
| // Since this is RTL, it should place the dialog on the right screen |
| expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0)); |
| expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); |
| }); |
| |
| testWidgets('positioning by default', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| builder: (BuildContext context, Widget? child) { |
| return MediaQuery( |
| // Display has a vertical hinge down the middle |
| data: const MediaQueryData( |
| size: Size(800, 600), |
| displayFeatures: <DisplayFeature>[ |
| DisplayFeature( |
| bounds: Rect.fromLTRB(390, 0, 410, 600), |
| type: DisplayFeatureType.hinge, |
| state: DisplayFeatureState.unknown, |
| ), |
| ], |
| ), |
| child: child!, |
| ); |
| }, |
| home: const Center(child: Text('Test')), |
| ), |
| ); |
| final BuildContext context = tester.element(find.text('Test')); |
| |
| showDialog<void>( |
| context: context, |
| builder: (BuildContext context) { |
| return const Placeholder(); |
| }, |
| ); |
| await tester.pumpAndSettle(); |
| |
| // By default it should place the dialog on the left screen |
| expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero); |
| expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(390.0, 600.0)); |
| }); |
| }); |
| |
| group('AlertDialog.scrollable: ', () { |
| testWidgets('Title is scrollable', (WidgetTester tester) async { |
| final Key titleKey = UniqueKey(); |
| final AlertDialog dialog = AlertDialog( |
| title: Container( |
| key: titleKey, |
| color: Colors.green, |
| height: 1000, |
| ), |
| scrollable: true, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final RenderBox box = tester.renderObject(find.byKey(titleKey)); |
| final Offset originalOffset = box.localToGlobal(Offset.zero); |
| await tester.drag(find.byKey(titleKey), const Offset(0.0, -200.0)); |
| expect(box.localToGlobal(Offset.zero), equals(originalOffset.translate(0.0, -200.0))); |
| }); |
| |
| testWidgets('Content is scrollable', (WidgetTester tester) async { |
| final Key contentKey = UniqueKey(); |
| final AlertDialog dialog = AlertDialog( |
| content: Container( |
| key: contentKey, |
| color: Colors.orange, |
| height: 1000, |
| ), |
| scrollable: true, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final RenderBox box = tester.renderObject(find.byKey(contentKey)); |
| final Offset originalOffset = box.localToGlobal(Offset.zero); |
| await tester.drag(find.byKey(contentKey), const Offset(0.0, -200.0)); |
| expect(box.localToGlobal(Offset.zero), equals(originalOffset.translate(0.0, -200.0))); |
| }); |
| |
| testWidgets('Title and content are scrollable', (WidgetTester tester) async { |
| final Key titleKey = UniqueKey(); |
| final Key contentKey = UniqueKey(); |
| final AlertDialog dialog = AlertDialog( |
| title: Container( |
| key: titleKey, |
| color: Colors.green, |
| height: 400, |
| ), |
| content: Container( |
| key: contentKey, |
| color: Colors.orange, |
| height: 400, |
| ), |
| scrollable: true, |
| ); |
| await tester.pumpWidget(_buildAppWithDialog(dialog)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| final RenderBox title = tester.renderObject(find.byKey(titleKey)); |
| final RenderBox content = tester.renderObject(find.byKey(contentKey)); |
| final Offset titleOriginalOffset = title.localToGlobal(Offset.zero); |
| final Offset contentOriginalOffset = content.localToGlobal(Offset.zero); |
| |
| // Dragging the title widget should scroll both the title |
| // and the content widgets. |
| await tester.drag(find.byKey(titleKey), const Offset(0.0, -200.0)); |
| expect(title.localToGlobal(Offset.zero), equals(titleOriginalOffset.translate(0.0, -200.0))); |
| expect(content.localToGlobal(Offset.zero), equals(contentOriginalOffset.translate(0.0, -200.0))); |
| |
| // Dragging the content widget should scroll both the title |
| // and the content widgets. |
| await tester.drag(find.byKey(contentKey), const Offset(0.0, 200.0)); |
| expect(title.localToGlobal(Offset.zero), equals(titleOriginalOffset)); |
| expect(content.localToGlobal(Offset.zero), equals(contentOriginalOffset)); |
| }); |
| }); |
| |
| testWidgets('Dialog with RouteSettings', (WidgetTester tester) async { |
| late RouteSettings currentRouteSetting; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorObservers: <NavigatorObserver>[ |
| _ClosureNavigatorObserver(onDidChange: (Route<dynamic> newRoute) { |
| currentRouteSetting = newRoute.settings; |
| }), |
| ], |
| home: const Material( |
| child: Center( |
| child: ElevatedButton( |
| onPressed: null, |
| child: Text('Go'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final BuildContext context = tester.element(find.text('Go')); |
| const RouteSettings exampleSetting = RouteSettings(name: 'simple'); |
| |
| final Future<int?> result = showDialog<int>( |
| context: context, |
| builder: (BuildContext context) { |
| return SimpleDialog( |
| title: const Text('Title'), |
| children: <Widget>[ |
| SimpleDialogOption( |
| child: const Text('X'), |
| onPressed: () { |
| Navigator.of(context).pop(); |
| }, |
| ), |
| ], |
| ); |
| }, |
| routeSettings: exampleSetting, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect(find.text('Title'), findsOneWidget); |
| expect(currentRouteSetting, exampleSetting); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect(await result, isNull); |
| await tester.pumpAndSettle(); |
| expect(currentRouteSetting.name, '/'); |
| }); |
| |
| testWidgets('showDialog - custom barrierLabel', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.iOS), |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Center( |
| child: ElevatedButton( |
| child: const Text('X'), |
| onPressed: () { |
| showDialog<void>( |
| context: context, |
| barrierLabel: 'Custom label', |
| builder: (BuildContext context) { |
| return const AlertDialog( |
| title: Text('Title'), |
| content: Text('Y'), |
| actions: <Widget>[], |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(semantics, isNot(includesNodeWith( |
| label: 'Custom label', |
| flags: <SemanticsFlag>[SemanticsFlag.namesRoute], |
| ))); |
| }); |
| |
| testWidgets('DialogRoute is state restorable', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| restorationScopeId: 'app', |
| home: _RestorableDialogTestWidget(), |
| ), |
| ); |
| |
| expect(find.byType(AlertDialog), findsNothing); |
| |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect(find.byType(AlertDialog), findsOneWidget); |
| final TestRestorationData restorationData = await tester.getRestorationData(); |
| |
| await tester.restartAndRestore(); |
| |
| expect(find.byType(AlertDialog), findsOneWidget); |
| |
| // Tap on the barrier. |
| await tester.tapAt(const Offset(10.0, 10.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.byType(AlertDialog), findsNothing); |
| |
| await tester.restoreFrom(restorationData); |
| expect(find.byType(AlertDialog), findsOneWidget); |
| }, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615 |
| |
| testWidgets('AlertDialog.actionsAlignment', (WidgetTester tester) async { |
| final Key actionKey = UniqueKey(); |
| |
| Widget buildFrame(MainAxisAlignment? alignment) { |
| return MaterialApp( |
| home: Scaffold( |
| body: AlertDialog( |
| content: const SizedBox(width: 800), |
| actionsAlignment: alignment, |
| actions: <Widget>[SizedBox(key: actionKey, width: 20, height: 20)], |
| buttonPadding: EdgeInsets.zero, |
| insetPadding: EdgeInsets.zero, |
| ), |
| ), |
| ); |
| } |
| |
| // Default configuration |
| await tester.pumpWidget(buildFrame(null)); |
| expect(tester.getTopLeft(find.byType(AlertDialog)).dx, 0); |
| expect(tester.getTopRight(find.byType(AlertDialog)).dx, 800); |
| expect(tester.getSize(find.byType(OverflowBar)).width, 800); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, 800 - 20); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, 800); |
| |
| // All possible alignment values |
| |
| await tester.pumpWidget(buildFrame(MainAxisAlignment.start)); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, 0); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, 20); |
| |
| await tester.pumpWidget(buildFrame(MainAxisAlignment.center)); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, (800 - 20) / 2); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, (800 - 20) / 2 + 20); |
| |
| await tester.pumpWidget(buildFrame(MainAxisAlignment.end)); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, 800 - 20); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, 800); |
| |
| await tester.pumpWidget(buildFrame(MainAxisAlignment.spaceBetween)); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, 0); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, 20); |
| |
| await tester.pumpWidget(buildFrame(MainAxisAlignment.spaceAround)); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, (800 - 20) / 2); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, (800 - 20) / 2 + 20); |
| |
| await tester.pumpWidget(buildFrame(MainAxisAlignment.spaceEvenly)); |
| expect(tester.getTopLeft(find.byKey(actionKey)).dx, (800 - 20) / 2); |
| expect(tester.getTopRight(find.byKey(actionKey)).dx, (800 - 20) / 2 + 20); |
| }); |
| } |
| |
| class _RestorableDialogTestWidget extends StatelessWidget { |
| const _RestorableDialogTestWidget(); |
| |
| static Route<Object?> _materialDialogBuilder(BuildContext context, Object? arguments) { |
| return DialogRoute<void>( |
| context: context, |
| builder: (BuildContext context) => const AlertDialog(title: Text('Material Alert!')), |
| ); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| body: Center( |
| child: OutlinedButton( |
| onPressed: () { |
| Navigator.of(context).restorablePush(_materialDialogBuilder); |
| }, |
| child: const Text('X'), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class DialogObserver extends NavigatorObserver { |
| int dialogCount = 0; |
| |
| @override |
| void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { |
| if (route is DialogRoute) { |
| dialogCount++; |
| } |
| super.didPush(route, previousRoute); |
| } |
| } |
| |
| class _ClosureNavigatorObserver extends NavigatorObserver { |
| _ClosureNavigatorObserver({required this.onDidChange}); |
| |
| final void Function(Route<dynamic> newRoute) onDidChange; |
| |
| @override |
| void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(route); |
| |
| @override |
| void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(previousRoute!); |
| |
| @override |
| void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(previousRoute!); |
| |
| @override |
| void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) => onDidChange(newRoute!); |
| } |