1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:ui'; |
6 | |
7 | import 'package:flutter/cupertino.dart'; |
8 | import 'package:flutter/material.dart'; |
9 | import 'package:flutter/rendering.dart'; |
10 | import 'package:flutter/services.dart'; |
11 | import 'package:flutter_test/flutter_test.dart'; |
12 | import '../widgets/semantics_tester.dart'; |
13 | |
14 | MaterialApp _buildAppWithDialog( |
15 | Widget dialog, { |
16 | ThemeData? theme, |
17 | double textScaleFactor = 1.0, |
18 | TraversalEdgeBehavior? traversalEdgeBehavior, |
19 | }) { |
20 | return MaterialApp( |
21 | theme: theme, |
22 | home: Material( |
23 | child: Builder( |
24 | builder: (BuildContext context) { |
25 | return Center( |
26 | child: ElevatedButton( |
27 | child: const Text('X'), |
28 | onPressed: () { |
29 | showDialog<void>( |
30 | context: context, |
31 | traversalEdgeBehavior: traversalEdgeBehavior, |
32 | builder: (BuildContext context) { |
33 | return MediaQuery.withClampedTextScaling( |
34 | minScaleFactor: textScaleFactor, |
35 | maxScaleFactor: textScaleFactor, |
36 | child: dialog, |
37 | ); |
38 | }, |
39 | ); |
40 | }, |
41 | ), |
42 | ); |
43 | }, |
44 | ), |
45 | ), |
46 | ); |
47 | } |
48 | |
49 | Material _getMaterialFromDialog(WidgetTester tester) { |
50 | return tester.widget<Material>( |
51 | find.descendant(of: find.byType(Dialog), matching: find.byType(Material)), |
52 | ); |
53 | } |
54 | |
55 | RenderParagraph _getTextRenderObjectFromDialog(WidgetTester tester, String text) { |
56 | return tester |
57 | .element<StatelessElement>( |
58 | find.descendant(of: find.byType(Dialog), matching: find.text(text)), |
59 | ) |
60 | .renderObject! |
61 | as RenderParagraph; |
62 | } |
63 | |
64 | // What was the AlertDialog's ButtonBar when many of these tests were written, |
65 | // is now a Padding widget with an OverflowBar child. The Padding widget's size |
66 | // and location match the original ButtonBar's size and location. |
67 | Finder _findOverflowBar() { |
68 | return find.ancestor(of: find.byType(OverflowBar), matching: find.byType(Padding)).first; |
69 | } |
70 | |
71 | const ShapeBorder _defaultM2DialogShape = RoundedRectangleBorder( |
72 | borderRadius: BorderRadius.all(Radius.circular(4.0)), |
73 | ); |
74 | final ShapeBorder _defaultM3DialogShape = RoundedRectangleBorder( |
75 | borderRadius: BorderRadius.circular(28.0), |
76 | ); |
77 | |
78 | void main() { |
79 | final ThemeData material3Theme = ThemeData(brightness: Brightness.dark); |
80 | final ThemeData material2Theme = ThemeData(useMaterial3: false, brightness: Brightness.dark); |
81 | |
82 | testWidgets('Dialog is scrollable', (WidgetTester tester) async { |
83 | bool didPressOk = false; |
84 | final AlertDialog dialog = AlertDialog( |
85 | content: Container(height: 5000.0, width: 300.0, color: Colors.green[500]), |
86 | actions: <Widget>[ |
87 | TextButton( |
88 | onPressed: () { |
89 | didPressOk = true; |
90 | }, |
91 | child: const Text('OK'), |
92 | ), |
93 | ], |
94 | ); |
95 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
96 | |
97 | await tester.tap(find.text('X')); |
98 | await tester.pumpAndSettle(); |
99 | |
100 | expect(didPressOk, false); |
101 | await tester.tap(find.text('OK')); |
102 | expect(didPressOk, true); |
103 | }); |
104 | |
105 | testWidgets('Dialog background color from AlertDialog', (WidgetTester tester) async { |
106 | const Color customColor = Colors.pink; |
107 | const AlertDialog dialog = AlertDialog(backgroundColor: customColor, actions: <Widget>[]); |
108 | await tester.pumpWidget( |
109 | _buildAppWithDialog(dialog, theme: ThemeData(brightness: Brightness.dark)), |
110 | ); |
111 | |
112 | await tester.tap(find.text('X')); |
113 | await tester.pumpAndSettle(); |
114 | |
115 | final Material materialWidget = _getMaterialFromDialog(tester); |
116 | expect(materialWidget.color, customColor); |
117 | }); |
118 | |
119 | testWidgets('Dialog background defaults to ColorScheme.surfaceContainerHigh', ( |
120 | WidgetTester tester, |
121 | ) async { |
122 | final ThemeData theme = ThemeData( |
123 | colorScheme: ThemeData().colorScheme.copyWith( |
124 | surface: Colors.orange, |
125 | background: Colors.green, |
126 | surfaceContainerHigh: Colors.red, |
127 | ), |
128 | ); |
129 | const Dialog dialog = Dialog(child: SizedBox(width: 200, height: 200)); |
130 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: theme)); |
131 | |
132 | await tester.tap(find.text('X')); |
133 | await tester.pumpAndSettle(); |
134 | |
135 | final Material materialWidget = _getMaterialFromDialog(tester); |
136 | expect(materialWidget.color, theme.colorScheme.surfaceContainerHigh); |
137 | }); |
138 | |
139 | testWidgets('Material2 - Dialog Defaults', (WidgetTester tester) async { |
140 | const AlertDialog dialog = AlertDialog( |
141 | title: Text('Title'), |
142 | content: Text('Y'), |
143 | actions: <Widget>[], |
144 | ); |
145 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material2Theme)); |
146 | |
147 | await tester.tap(find.text('X')); |
148 | await tester.pumpAndSettle(); |
149 | |
150 | final Material materialWidget = _getMaterialFromDialog(tester); |
151 | expect(materialWidget.color, Colors.grey[800]); |
152 | expect(materialWidget.shape, _defaultM2DialogShape); |
153 | expect(materialWidget.elevation, 24.0); |
154 | |
155 | final Offset bottomLeft = tester.getBottomLeft( |
156 | find.descendant(of: find.byType(Dialog), matching: find.byType(Material)), |
157 | ); |
158 | expect(bottomLeft.dy, 360.0); |
159 | }); |
160 | |
161 | testWidgets('Material3 - Dialog Defaults', (WidgetTester tester) async { |
162 | const AlertDialog dialog = AlertDialog( |
163 | title: Text('Title'), |
164 | content: Text('Y'), |
165 | actions: <Widget>[], |
166 | ); |
167 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material3Theme)); |
168 | |
169 | await tester.tap(find.text('X')); |
170 | await tester.pumpAndSettle(); |
171 | |
172 | final Material material3Widget = _getMaterialFromDialog(tester); |
173 | expect(material3Widget.color, material3Theme.colorScheme.surfaceContainerHigh); |
174 | expect(material3Widget.shape, _defaultM3DialogShape); |
175 | expect(material3Widget.elevation, 6.0); |
176 | }); |
177 | |
178 | testWidgets('Material2 - Dialog.fullscreen Defaults', (WidgetTester tester) async { |
179 | const String dialogTextM2 = 'Fullscreen Dialog - M2'; |
180 | |
181 | await tester.pumpWidget( |
182 | _buildAppWithDialog( |
183 | theme: material2Theme, |
184 | const Dialog.fullscreen(child: Text(dialogTextM2)), |
185 | ), |
186 | ); |
187 | |
188 | await tester.tap(find.text('X')); |
189 | await tester.pumpAndSettle(); |
190 | |
191 | expect(find.text(dialogTextM2), findsOneWidget); |
192 | |
193 | final Material materialWidget = _getMaterialFromDialog(tester); |
194 | expect(materialWidget.color, Colors.grey[800]); |
195 | |
196 | // Try to dismiss the fullscreen dialog with the escape key. |
197 | await tester.sendKeyEvent(LogicalKeyboardKey.escape); |
198 | await tester.pumpAndSettle(); |
199 | |
200 | expect(find.text(dialogTextM2), findsNothing); |
201 | }); |
202 | |
203 | testWidgets('Material3 - Dialog.fullscreen Defaults', (WidgetTester tester) async { |
204 | const String dialogTextM3 = 'Fullscreen Dialog - M3'; |
205 | |
206 | await tester.pumpWidget( |
207 | _buildAppWithDialog( |
208 | theme: material3Theme, |
209 | const Dialog.fullscreen(child: Text(dialogTextM3)), |
210 | ), |
211 | ); |
212 | |
213 | await tester.tap(find.text('X')); |
214 | await tester.pumpAndSettle(); |
215 | |
216 | expect(find.text(dialogTextM3), findsOneWidget); |
217 | |
218 | final Material materialWidget = _getMaterialFromDialog(tester); |
219 | expect(materialWidget.color, material3Theme.colorScheme.surface); |
220 | |
221 | // Try to dismiss the fullscreen dialog with the escape key. |
222 | await tester.sendKeyEvent(LogicalKeyboardKey.escape); |
223 | await tester.pumpAndSettle(); |
224 | |
225 | expect(find.text(dialogTextM3), findsNothing); |
226 | }); |
227 | |
228 | testWidgets('Custom dialog elevation', (WidgetTester tester) async { |
229 | const double customElevation = 12.0; |
230 | const Color shadowColor = Color(0xFF000001); |
231 | const Color surfaceTintColor = Color(0xFF000002); |
232 | const AlertDialog dialog = AlertDialog( |
233 | actions: <Widget>[], |
234 | elevation: customElevation, |
235 | shadowColor: shadowColor, |
236 | surfaceTintColor: surfaceTintColor, |
237 | ); |
238 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
239 | |
240 | await tester.tap(find.text('X')); |
241 | await tester.pumpAndSettle(); |
242 | |
243 | final Material materialWidget = _getMaterialFromDialog(tester); |
244 | expect(materialWidget.elevation, customElevation); |
245 | expect(materialWidget.shadowColor, shadowColor); |
246 | expect(materialWidget.surfaceTintColor, surfaceTintColor); |
247 | }); |
248 | |
249 | testWidgets('Custom Title Text Style', (WidgetTester tester) async { |
250 | const String titleText = 'Title'; |
251 | const TextStyle titleTextStyle = TextStyle(color: Colors.pink); |
252 | const AlertDialog dialog = AlertDialog( |
253 | title: Text(titleText), |
254 | titleTextStyle: titleTextStyle, |
255 | actions: <Widget>[], |
256 | ); |
257 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
258 | |
259 | await tester.tap(find.text('X')); |
260 | await tester.pumpAndSettle(); |
261 | |
262 | final RenderParagraph title = _getTextRenderObjectFromDialog(tester, titleText); |
263 | expect(title.text.style, titleTextStyle); |
264 | }); |
265 | |
266 | testWidgets('Custom Content Text Style', (WidgetTester tester) async { |
267 | const String contentText = 'Content'; |
268 | const TextStyle contentTextStyle = TextStyle(color: Colors.pink); |
269 | const AlertDialog dialog = AlertDialog( |
270 | content: Text(contentText), |
271 | contentTextStyle: contentTextStyle, |
272 | actions: <Widget>[], |
273 | ); |
274 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
275 | |
276 | await tester.tap(find.text('X')); |
277 | await tester.pumpAndSettle(); |
278 | |
279 | final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText); |
280 | expect(content.text.style, contentTextStyle); |
281 | }); |
282 | |
283 | testWidgets('AlertDialog custom clipBehavior', (WidgetTester tester) async { |
284 | const AlertDialog dialog = AlertDialog(actions: <Widget>[], clipBehavior: Clip.antiAlias); |
285 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
286 | |
287 | await tester.tap(find.text('X')); |
288 | await tester.pumpAndSettle(); |
289 | |
290 | final Material materialWidget = _getMaterialFromDialog(tester); |
291 | expect(materialWidget.clipBehavior, Clip.antiAlias); |
292 | }); |
293 | |
294 | testWidgets('SimpleDialog custom clipBehavior', (WidgetTester tester) async { |
295 | const SimpleDialog dialog = SimpleDialog(clipBehavior: Clip.antiAlias, children: <Widget>[]); |
296 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
297 | |
298 | await tester.tap(find.text('X')); |
299 | await tester.pumpAndSettle(); |
300 | |
301 | final Material materialWidget = _getMaterialFromDialog(tester); |
302 | expect(materialWidget.clipBehavior, Clip.antiAlias); |
303 | }); |
304 | |
305 | testWidgets('Custom dialog shape', (WidgetTester tester) async { |
306 | const RoundedRectangleBorder customBorder = RoundedRectangleBorder( |
307 | borderRadius: BorderRadius.all(Radius.circular(16.0)), |
308 | ); |
309 | const AlertDialog dialog = AlertDialog(actions: <Widget>[], shape: customBorder); |
310 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
311 | |
312 | await tester.tap(find.text('X')); |
313 | await tester.pumpAndSettle(); |
314 | |
315 | final Material materialWidget = _getMaterialFromDialog(tester); |
316 | expect(materialWidget.shape, customBorder); |
317 | }); |
318 | |
319 | testWidgets('Null dialog shape', (WidgetTester tester) async { |
320 | final ThemeData theme = ThemeData(); |
321 | const AlertDialog dialog = AlertDialog(actions: <Widget>[]); |
322 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: theme)); |
323 | |
324 | await tester.tap(find.text('X')); |
325 | await tester.pumpAndSettle(); |
326 | |
327 | final Material materialWidget = _getMaterialFromDialog(tester); |
328 | expect( |
329 | materialWidget.shape, |
330 | theme.useMaterial3 ? _defaultM3DialogShape : _defaultM2DialogShape, |
331 | ); |
332 | }); |
333 | |
334 | testWidgets('Rectangular dialog shape', (WidgetTester tester) async { |
335 | const ShapeBorder customBorder = Border(); |
336 | const AlertDialog dialog = AlertDialog(actions: <Widget>[], shape: customBorder); |
337 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
338 | |
339 | await tester.tap(find.text('X')); |
340 | await tester.pumpAndSettle(); |
341 | |
342 | final Material materialWidget = _getMaterialFromDialog(tester); |
343 | expect(materialWidget.shape, customBorder); |
344 | }); |
345 | |
346 | testWidgets('Custom dialog alignment', (WidgetTester tester) async { |
347 | const AlertDialog dialog = AlertDialog(actions: <Widget>[], alignment: Alignment.bottomLeft); |
348 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
349 | |
350 | await tester.tap(find.text('X')); |
351 | await tester.pumpAndSettle(); |
352 | |
353 | final Offset bottomLeft = tester.getBottomLeft( |
354 | find.descendant(of: find.byType(Dialog), matching: find.byType(Material)), |
355 | ); |
356 | expect(bottomLeft.dx, 40.0); |
357 | expect(bottomLeft.dy, 576.0); |
358 | }); |
359 | |
360 | testWidgets('Simple dialog control test', (WidgetTester tester) async { |
361 | await tester.pumpWidget( |
362 | const MaterialApp( |
363 | home: Material(child: Center(child: ElevatedButton(onPressed: null, child: Text('Go')))), |
364 | ), |
365 | ); |
366 | |
367 | final BuildContext context = tester.element(find.text('Go')); |
368 | |
369 | final Future<int?> result = showDialog<int>( |
370 | context: context, |
371 | builder: (BuildContext context) { |
372 | return SimpleDialog( |
373 | title: const Text('Title'), |
374 | children: <Widget>[ |
375 | SimpleDialogOption( |
376 | onPressed: () { |
377 | Navigator.pop(context, 42); |
378 | }, |
379 | child: const Text('First option'), |
380 | ), |
381 | const SimpleDialogOption(child: Text('Second option')), |
382 | ], |
383 | ); |
384 | }, |
385 | ); |
386 | |
387 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
388 | expect(find.text('Title'), findsOneWidget); |
389 | await tester.tap(find.text('First option')); |
390 | |
391 | expect(await result, equals(42)); |
392 | }); |
393 | |
394 | testWidgets('Can show dialog using navigator global key', (WidgetTester tester) async { |
395 | final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
396 | await tester.pumpWidget( |
397 | MaterialApp(navigatorKey: navigator, home: const Material(child: Center(child: Text('Go')))), |
398 | ); |
399 | |
400 | final Future<int?> result = showDialog<int>( |
401 | context: navigator.currentContext!, |
402 | builder: (BuildContext context) { |
403 | return SimpleDialog( |
404 | title: const Text('Title'), |
405 | children: <Widget>[ |
406 | SimpleDialogOption( |
407 | onPressed: () { |
408 | Navigator.pop(context, 42); |
409 | }, |
410 | child: const Text('First option'), |
411 | ), |
412 | const SimpleDialogOption(child: Text('Second option')), |
413 | ], |
414 | ); |
415 | }, |
416 | ); |
417 | |
418 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
419 | expect(find.text('Title'), findsOneWidget); |
420 | await tester.tap(find.text('First option')); |
421 | |
422 | expect(await result, equals(42)); |
423 | }); |
424 | |
425 | testWidgets('Custom padding on SimpleDialogOption', (WidgetTester tester) async { |
426 | const EdgeInsets customPadding = EdgeInsets.fromLTRB(4, 10, 8, 6); |
427 | final SimpleDialog dialog = SimpleDialog( |
428 | title: const Text('Title'), |
429 | children: <Widget>[ |
430 | SimpleDialogOption( |
431 | onPressed: () {}, |
432 | padding: customPadding, |
433 | child: const Text('First option'), |
434 | ), |
435 | ], |
436 | ); |
437 | |
438 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
439 | await tester.tap(find.text('X')); |
440 | await tester.pumpAndSettle(); |
441 | |
442 | final Rect dialogRect = tester.getRect(find.byType(SimpleDialogOption)); |
443 | final Rect textRect = tester.getRect(find.text('First option')); |
444 | |
445 | expect(textRect.left, dialogRect.left + customPadding.left); |
446 | expect(textRect.top, dialogRect.top + customPadding.top); |
447 | expect(textRect.right, dialogRect.right - customPadding.right); |
448 | expect(textRect.bottom, dialogRect.bottom - customPadding.bottom); |
449 | }); |
450 | |
451 | testWidgets('Barrier dismissible', (WidgetTester tester) async { |
452 | await tester.pumpWidget( |
453 | const MaterialApp( |
454 | home: Material(child: Center(child: ElevatedButton(onPressed: null, child: Text('Go')))), |
455 | ), |
456 | ); |
457 | |
458 | final BuildContext context = tester.element(find.text('Go')); |
459 | |
460 | showDialog<void>( |
461 | context: context, |
462 | builder: (BuildContext context) { |
463 | return Container( |
464 | width: 100.0, |
465 | height: 100.0, |
466 | alignment: Alignment.center, |
467 | child: const Text('Dialog1'), |
468 | ); |
469 | }, |
470 | ); |
471 | |
472 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
473 | expect(find.text('Dialog1'), findsOneWidget); |
474 | |
475 | // Tap on the barrier. |
476 | await tester.tapAt(const Offset(10.0, 10.0)); |
477 | |
478 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
479 | expect(find.text('Dialog1'), findsNothing); |
480 | |
481 | showDialog<void>( |
482 | context: context, |
483 | barrierDismissible: false, |
484 | builder: (BuildContext context) { |
485 | return Container( |
486 | width: 100.0, |
487 | height: 100.0, |
488 | alignment: Alignment.center, |
489 | child: const Text('Dialog2'), |
490 | ); |
491 | }, |
492 | ); |
493 | |
494 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
495 | expect(find.text('Dialog2'), findsOneWidget); |
496 | |
497 | // Tap on the barrier, which shouldn't do anything this time. |
498 | await tester.tapAt(const Offset(10.0, 10.0)); |
499 | |
500 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
501 | expect(find.text('Dialog2'), findsOneWidget); |
502 | }); |
503 | |
504 | testWidgets('Barrier color', (WidgetTester tester) async { |
505 | await tester.pumpWidget(const MaterialApp(home: Center(child: Text('Test')))); |
506 | final BuildContext context = tester.element(find.text('Test')); |
507 | |
508 | // Test default barrier color |
509 | showDialog<void>( |
510 | context: context, |
511 | builder: (BuildContext context) { |
512 | return const Text('Dialog'); |
513 | }, |
514 | ); |
515 | await tester.pumpAndSettle(); |
516 | expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.black54); |
517 | |
518 | // Dismiss it and test a custom barrier color |
519 | await tester.tapAt(const Offset(10.0, 10.0)); |
520 | showDialog<void>( |
521 | context: context, |
522 | builder: (BuildContext context) { |
523 | return const Text('Dialog'); |
524 | }, |
525 | barrierColor: Colors.pink, |
526 | ); |
527 | await tester.pumpAndSettle(); |
528 | expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.pink); |
529 | }); |
530 | |
531 | testWidgets('Dialog hides underlying semantics tree', (WidgetTester tester) async { |
532 | final SemanticsTester semantics = SemanticsTester(tester); |
533 | const String buttonText = 'A button covered by dialog overlay'; |
534 | await tester.pumpWidget( |
535 | const MaterialApp( |
536 | home: Material( |
537 | child: Center(child: ElevatedButton(onPressed: null, child: Text(buttonText))), |
538 | ), |
539 | ), |
540 | ); |
541 | |
542 | expect(semantics, includesNodeWith(label: buttonText)); |
543 | |
544 | final BuildContext context = tester.element(find.text(buttonText)); |
545 | |
546 | const String alertText = 'A button in an overlay alert'; |
547 | showDialog<void>( |
548 | context: context, |
549 | builder: (BuildContext context) { |
550 | return const AlertDialog(title: Text(alertText)); |
551 | }, |
552 | ); |
553 | |
554 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
555 | |
556 | expect(semantics, includesNodeWith(label: alertText)); |
557 | expect(semantics, isNot(includesNodeWith(label: buttonText))); |
558 | |
559 | semantics.dispose(); |
560 | }); |
561 | |
562 | testWidgets('AlertDialog.actionsPadding defaults', (WidgetTester tester) async { |
563 | final AlertDialog dialog = AlertDialog( |
564 | title: const Text('title'), |
565 | content: const Text('content'), |
566 | actions: <Widget>[ElevatedButton(onPressed: () {}, child: const Text('button'))], |
567 | ); |
568 | |
569 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
570 | |
571 | await tester.tap(find.text('X')); |
572 | await tester.pumpAndSettle(); |
573 | |
574 | // The [AlertDialog] is the entire screen, since it also contains the scrim. |
575 | // The first [Material] child of [AlertDialog] is the actual dialog |
576 | // itself. |
577 | final Size dialogSize = tester.getSize( |
578 | find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material)).first, |
579 | ); |
580 | final Size actionsSize = tester.getSize(_findOverflowBar()); |
581 | |
582 | expect(actionsSize.width, dialogSize.width); |
583 | }); |
584 | |
585 | testWidgets('AlertDialog.actionsPadding surrounds actions with padding', ( |
586 | WidgetTester tester, |
587 | ) async { |
588 | final AlertDialog dialog = AlertDialog( |
589 | title: const Text('title'), |
590 | content: const Text('content'), |
591 | actions: <Widget>[ElevatedButton(onPressed: () {}, child: const Text('button'))], |
592 | // The OverflowBar is inset by the buttonPadding/2 + actionsPadding |
593 | buttonPadding: EdgeInsets.zero, |
594 | actionsPadding: const EdgeInsets.all(30.0), // custom padding value |
595 | ); |
596 | |
597 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
598 | |
599 | await tester.tap(find.text('X')); |
600 | await tester.pumpAndSettle(); |
601 | |
602 | // The [AlertDialog] is the entire screen, since it also contains the scrim. |
603 | // The first [Material] child of [AlertDialog] is the actual dialog |
604 | // itself. |
605 | final Size dialogSize = tester.getSize( |
606 | find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material)).first, |
607 | ); |
608 | final Size actionsSize = tester.getSize(find.byType(OverflowBar)); |
609 | |
610 | expect(actionsSize.width, dialogSize.width - (30.0 * 2)); |
611 | }); |
612 | |
613 | testWidgets('Material2 - AlertDialog.buttonPadding defaults', (WidgetTester tester) async { |
614 | final GlobalKey key1 = GlobalKey(); |
615 | final GlobalKey key2 = GlobalKey(); |
616 | |
617 | final AlertDialog dialog = AlertDialog( |
618 | title: const Text('title'), |
619 | content: const Text('content'), |
620 | actions: <Widget>[ |
621 | ElevatedButton(key: key1, onPressed: () {}, child: const Text('button 1')), |
622 | ElevatedButton(key: key2, onPressed: () {}, child: const Text('button 2')), |
623 | ], |
624 | ); |
625 | |
626 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material2Theme)); |
627 | |
628 | await tester.tap(find.text('X')); |
629 | await tester.pumpAndSettle(); |
630 | |
631 | // Padding between both buttons |
632 | expect( |
633 | tester.getBottomLeft(find.byKey(key2)).dx, |
634 | tester.getBottomRight(find.byKey(key1)).dx + 8.0, |
635 | ); |
636 | |
637 | // Padding between button and edges of the button bar |
638 | // First button |
639 | expect( |
640 | tester.getTopRight(find.byKey(key1)).dy, |
641 | tester.getTopRight(_findOverflowBar()).dy + 8.0, |
642 | ); // top |
643 | expect( |
644 | tester.getBottomRight(find.byKey(key1)).dy, |
645 | tester.getBottomRight(_findOverflowBar()).dy - 8.0, |
646 | ); // bottom |
647 | |
648 | // Second button |
649 | expect( |
650 | tester.getTopRight(find.byKey(key2)).dy, |
651 | tester.getTopRight(_findOverflowBar()).dy + 8.0, |
652 | ); // top |
653 | expect( |
654 | tester.getBottomRight(find.byKey(key2)).dy, |
655 | tester.getBottomRight(_findOverflowBar()).dy - 8.0, |
656 | ); // bottom |
657 | expect( |
658 | tester.getBottomRight(find.byKey(key2)).dx, |
659 | tester.getBottomRight(_findOverflowBar()).dx - 8.0, |
660 | ); // right |
661 | }); |
662 | |
663 | testWidgets('Material3 - AlertDialog.buttonPadding defaults', (WidgetTester tester) async { |
664 | final GlobalKey key1 = GlobalKey(); |
665 | final GlobalKey key2 = GlobalKey(); |
666 | |
667 | final AlertDialog dialog = AlertDialog( |
668 | title: const Text('title'), |
669 | content: const Text('content'), |
670 | actions: <Widget>[ |
671 | ElevatedButton(key: key1, onPressed: () {}, child: const Text('button 1')), |
672 | ElevatedButton(key: key2, onPressed: () {}, child: const Text('button 2')), |
673 | ], |
674 | ); |
675 | |
676 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: material3Theme)); |
677 | |
678 | await tester.tap(find.text('X')); |
679 | await tester.pumpAndSettle(); |
680 | |
681 | // Padding between both buttons |
682 | expect( |
683 | tester.getBottomLeft(find.byKey(key2)).dx, |
684 | tester.getBottomRight(find.byKey(key1)).dx + 8.0, |
685 | ); |
686 | |
687 | // Padding between button and edges of the button bar |
688 | // First button |
689 | expect( |
690 | tester.getTopRight(find.byKey(key1)).dy, |
691 | tester.getTopRight(_findOverflowBar()).dy, |
692 | ); // top |
693 | expect( |
694 | tester.getBottomRight(find.byKey(key1)).dy, |
695 | tester.getBottomRight(_findOverflowBar()).dy - 24.0, |
696 | ); // bottom |
697 | |
698 | // // Second button |
699 | expect( |
700 | tester.getTopRight(find.byKey(key2)).dy, |
701 | tester.getTopRight(_findOverflowBar()).dy, |
702 | ); // top |
703 | expect( |
704 | tester.getBottomRight(find.byKey(key2)).dy, |
705 | tester.getBottomRight(_findOverflowBar()).dy - 24.0, |
706 | ); // bottom |
707 | expect( |
708 | tester.getBottomRight(find.byKey(key2)).dx, |
709 | tester.getBottomRight(_findOverflowBar()).dx - 24.0, |
710 | ); // right |
711 | }); |
712 | |
713 | testWidgets('AlertDialog.buttonPadding custom values', (WidgetTester tester) async { |
714 | final GlobalKey key1 = GlobalKey(); |
715 | final GlobalKey key2 = GlobalKey(); |
716 | |
717 | final AlertDialog dialog = AlertDialog( |
718 | title: const Text('title'), |
719 | content: const Text('content'), |
720 | actions: <Widget>[ |
721 | ElevatedButton(key: key1, onPressed: () {}, child: const Text('button 1')), |
722 | ElevatedButton(key: key2, onPressed: () {}, child: const Text('button 2')), |
723 | ], |
724 | buttonPadding: const EdgeInsets.only(left: 10.0, right: 20.0), |
725 | ); |
726 | |
727 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: ThemeData(useMaterial3: false))); |
728 | |
729 | await tester.tap(find.text('X')); |
730 | await tester.pumpAndSettle(); |
731 | |
732 | // Padding between both buttons |
733 | expect( |
734 | tester.getBottomLeft(find.byKey(key2)).dx, |
735 | tester.getBottomRight(find.byKey(key1)).dx + ((10.0 + 20.0) / 2), |
736 | ); |
737 | |
738 | // Padding between button and edges of the button bar |
739 | // First button |
740 | expect( |
741 | tester.getTopRight(find.byKey(key1)).dy, |
742 | tester.getTopRight(_findOverflowBar()).dy + ((10.0 + 20.0) / 2), |
743 | ); // top |
744 | expect( |
745 | tester.getBottomRight(find.byKey(key1)).dy, |
746 | tester.getBottomRight(_findOverflowBar()).dy - ((10.0 + 20.0) / 2), |
747 | ); // bottom |
748 | |
749 | // Second button |
750 | expect( |
751 | tester.getTopRight(find.byKey(key2)).dy, |
752 | tester.getTopRight(_findOverflowBar()).dy + ((10.0 + 20.0) / 2), |
753 | ); // top |
754 | expect( |
755 | tester.getBottomRight(find.byKey(key2)).dy, |
756 | tester.getBottomRight(_findOverflowBar()).dy - ((10.0 + 20.0) / 2), |
757 | ); // bottom |
758 | expect( |
759 | tester.getBottomRight(find.byKey(key2)).dx, |
760 | tester.getBottomRight(_findOverflowBar()).dx - ((10.0 + 20.0) / 2), |
761 | ); // right |
762 | }); |
763 | |
764 | group('Dialog children padding is correct', () { |
765 | final List<double> textScaleFactors = <double>[0.5, 1.0, 1.5, 2.0, 3.0]; |
766 | final Map<double, double> paddingScaleFactors = <double, double>{ |
767 | 0.5: 1.0, |
768 | 1.0: 1.0, |
769 | 1.5: 2.0 / 3.0, |
770 | 2.0: 1.0 / 3.0, |
771 | 3.0: 1.0 / 3.0, |
772 | }; |
773 | |
774 | final GlobalKey iconKey = GlobalKey(); |
775 | final GlobalKey titleKey = GlobalKey(); |
776 | final GlobalKey contentKey = GlobalKey(); |
777 | final GlobalKey childrenKey = GlobalKey(); |
778 | |
779 | final Finder dialogFinder = |
780 | find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first; |
781 | final Finder iconFinder = find.byKey(iconKey); |
782 | final Finder titleFinder = find.byKey(titleKey); |
783 | final Finder contentFinder = find.byKey(contentKey); |
784 | final Finder actionsFinder = _findOverflowBar(); |
785 | final Finder childrenFinder = find.byKey(childrenKey); |
786 | |
787 | Future<void> openDialog( |
788 | WidgetTester tester, |
789 | Widget dialog, |
790 | double textScaleFactor, { |
791 | bool isM3 = false, |
792 | }) async { |
793 | await tester.pumpWidget( |
794 | _buildAppWithDialog( |
795 | dialog, |
796 | textScaleFactor: textScaleFactor, |
797 | theme: ThemeData(useMaterial3: isM3), |
798 | ), |
799 | ); |
800 | |
801 | await tester.tap(find.text('X')); |
802 | await tester.pumpAndSettle(); |
803 | } |
804 | |
805 | void expectLeftEdgePadding( |
806 | WidgetTester tester, { |
807 | required Finder finder, |
808 | required double textScaleFactor, |
809 | required double unscaledValue, |
810 | }) { |
811 | expect( |
812 | tester.getTopLeft(dialogFinder).dx, |
813 | moreOrLessEquals( |
814 | tester.getTopLeft(finder).dx - unscaledValue * paddingScaleFactors[textScaleFactor]!, |
815 | ), |
816 | ); |
817 | expect( |
818 | tester.getBottomLeft(dialogFinder).dx, |
819 | moreOrLessEquals( |
820 | tester.getBottomLeft(finder).dx - unscaledValue * paddingScaleFactors[textScaleFactor]!, |
821 | ), |
822 | ); |
823 | } |
824 | |
825 | void expectRightEdgePadding( |
826 | WidgetTester tester, { |
827 | required Finder finder, |
828 | required double textScaleFactor, |
829 | required double unscaledValue, |
830 | }) { |
831 | expect( |
832 | tester.getTopRight(dialogFinder).dx, |
833 | moreOrLessEquals( |
834 | tester.getTopRight(finder).dx + unscaledValue * paddingScaleFactors[textScaleFactor]!, |
835 | ), |
836 | ); |
837 | expect( |
838 | tester.getBottomRight(dialogFinder).dx, |
839 | moreOrLessEquals( |
840 | tester.getBottomRight(finder).dx + unscaledValue * paddingScaleFactors[textScaleFactor]!, |
841 | ), |
842 | ); |
843 | } |
844 | |
845 | void expectTopEdgePadding( |
846 | WidgetTester tester, { |
847 | required Finder finder, |
848 | required double textScaleFactor, |
849 | required double unscaledValue, |
850 | }) { |
851 | expect( |
852 | tester.getTopLeft(dialogFinder).dy, |
853 | moreOrLessEquals( |
854 | tester.getTopLeft(finder).dy - unscaledValue * paddingScaleFactors[textScaleFactor]!, |
855 | ), |
856 | ); |
857 | expect( |
858 | tester.getTopRight(dialogFinder).dy, |
859 | moreOrLessEquals( |
860 | tester.getTopRight(finder).dy - unscaledValue * paddingScaleFactors[textScaleFactor]!, |
861 | ), |
862 | ); |
863 | } |
864 | |
865 | void expectBottomEdgePadding( |
866 | WidgetTester tester, { |
867 | required Finder finder, |
868 | required double textScaleFactor, |
869 | required double unscaledValue, |
870 | }) { |
871 | expect( |
872 | tester.getBottomLeft(dialogFinder).dy, |
873 | moreOrLessEquals( |
874 | tester.getBottomRight(finder).dy + unscaledValue * paddingScaleFactors[textScaleFactor]!, |
875 | ), |
876 | ); |
877 | expect( |
878 | tester.getBottomRight(dialogFinder).dy, |
879 | moreOrLessEquals( |
880 | tester.getBottomRight(finder).dy + unscaledValue * paddingScaleFactors[textScaleFactor]!, |
881 | ), |
882 | ); |
883 | } |
884 | |
885 | void expectVerticalInnerPadding( |
886 | WidgetTester tester, { |
887 | required Finder top, |
888 | required Finder bottom, |
889 | required double value, |
890 | }) { |
891 | expect(tester.getBottomLeft(top).dy, tester.getTopLeft(bottom).dy - value); |
892 | expect(tester.getBottomRight(top).dy, tester.getTopRight(bottom).dy - value); |
893 | } |
894 | |
895 | final Widget icon = Icon(Icons.ac_unit, key: iconKey); |
896 | final Widget title = Text('title', key: titleKey); |
897 | final Widget content = Text('content', key: contentKey); |
898 | final List<Widget> actions = <Widget>[ |
899 | ElevatedButton(onPressed: () {}, child: const Text('button')), |
900 | ]; |
901 | final List<Widget> children = <Widget>[ |
902 | SimpleDialogOption(key: childrenKey, child: const Text('child'), onPressed: () {}), |
903 | ]; |
904 | |
905 | for (final double textScaleFactor in textScaleFactors) { |
906 | testWidgets( |
907 | 'AlertDialog padding is correct when only icon and actions are specified [textScaleFactor]=$textScaleFactor ', |
908 | (WidgetTester tester) async { |
909 | final AlertDialog dialog = AlertDialog(icon: icon, actions: actions); |
910 | |
911 | await openDialog(tester, dialog, textScaleFactor); |
912 | |
913 | expectTopEdgePadding( |
914 | tester, |
915 | finder: iconFinder, |
916 | textScaleFactor: textScaleFactor, |
917 | unscaledValue: 24.0, |
918 | ); |
919 | expectLeftEdgePadding( |
920 | tester, |
921 | finder: iconFinder, |
922 | textScaleFactor: textScaleFactor, |
923 | unscaledValue: 24.0, |
924 | ); |
925 | expectRightEdgePadding( |
926 | tester, |
927 | finder: iconFinder, |
928 | textScaleFactor: textScaleFactor, |
929 | unscaledValue: 24.0, |
930 | ); |
931 | expectVerticalInnerPadding(tester, top: iconFinder, bottom: actionsFinder, value: 24.0); |
932 | expectLeftEdgePadding( |
933 | tester, |
934 | finder: actionsFinder, |
935 | textScaleFactor: textScaleFactor, |
936 | unscaledValue: 0.0, |
937 | ); |
938 | expectRightEdgePadding( |
939 | tester, |
940 | finder: actionsFinder, |
941 | textScaleFactor: textScaleFactor, |
942 | unscaledValue: 0.0, |
943 | ); |
944 | expectBottomEdgePadding( |
945 | tester, |
946 | finder: actionsFinder, |
947 | textScaleFactor: textScaleFactor, |
948 | unscaledValue: 0.0, |
949 | ); |
950 | }, |
951 | ); |
952 | |
953 | testWidgets( |
954 | 'AlertDialog padding is correct when only icon, title and actions are specified [textScaleFactor]=$textScaleFactor ', |
955 | (WidgetTester tester) async { |
956 | final AlertDialog dialog = AlertDialog(icon: icon, title: title, actions: actions); |
957 | |
958 | await openDialog(tester, dialog, textScaleFactor); |
959 | |
960 | expectTopEdgePadding( |
961 | tester, |
962 | finder: iconFinder, |
963 | textScaleFactor: textScaleFactor, |
964 | unscaledValue: 24.0, |
965 | ); |
966 | expectLeftEdgePadding( |
967 | tester, |
968 | finder: iconFinder, |
969 | textScaleFactor: textScaleFactor, |
970 | unscaledValue: 24.0, |
971 | ); |
972 | expectRightEdgePadding( |
973 | tester, |
974 | finder: iconFinder, |
975 | textScaleFactor: textScaleFactor, |
976 | unscaledValue: 24.0, |
977 | ); |
978 | expectVerticalInnerPadding(tester, top: iconFinder, bottom: titleFinder, value: 16.0); |
979 | expectLeftEdgePadding( |
980 | tester, |
981 | finder: titleFinder, |
982 | textScaleFactor: textScaleFactor, |
983 | unscaledValue: 24.0, |
984 | ); |
985 | expectRightEdgePadding( |
986 | tester, |
987 | finder: titleFinder, |
988 | textScaleFactor: textScaleFactor, |
989 | unscaledValue: 24.0, |
990 | ); |
991 | expectVerticalInnerPadding(tester, top: titleFinder, bottom: actionsFinder, value: 20.0); |
992 | expectLeftEdgePadding( |
993 | tester, |
994 | finder: actionsFinder, |
995 | textScaleFactor: textScaleFactor, |
996 | unscaledValue: 0.0, |
997 | ); |
998 | expectRightEdgePadding( |
999 | tester, |
1000 | finder: actionsFinder, |
1001 | textScaleFactor: textScaleFactor, |
1002 | unscaledValue: 0.0, |
1003 | ); |
1004 | expectBottomEdgePadding( |
1005 | tester, |
1006 | finder: actionsFinder, |
1007 | textScaleFactor: textScaleFactor, |
1008 | unscaledValue: 0.0, |
1009 | ); |
1010 | }, |
1011 | ); |
1012 | |
1013 | for (final bool isM3 in <bool>[true, false]) { |
1014 | testWidgets( |
1015 | 'AlertDialog padding is correct when only icon, content and actions are specified [textScaleFactor]=$textScaleFactor [isM3]=$isM3 ', |
1016 | (WidgetTester tester) async { |
1017 | final AlertDialog dialog = AlertDialog(icon: icon, content: content, actions: actions); |
1018 | |
1019 | await openDialog(tester, dialog, textScaleFactor, isM3: isM3); |
1020 | |
1021 | expectTopEdgePadding( |
1022 | tester, |
1023 | finder: iconFinder, |
1024 | textScaleFactor: textScaleFactor, |
1025 | unscaledValue: 24.0, |
1026 | ); |
1027 | expectLeftEdgePadding( |
1028 | tester, |
1029 | finder: iconFinder, |
1030 | textScaleFactor: textScaleFactor, |
1031 | unscaledValue: 24.0, |
1032 | ); |
1033 | expectRightEdgePadding( |
1034 | tester, |
1035 | finder: iconFinder, |
1036 | textScaleFactor: textScaleFactor, |
1037 | unscaledValue: 24.0, |
1038 | ); |
1039 | expectVerticalInnerPadding( |
1040 | tester, |
1041 | top: iconFinder, |
1042 | bottom: contentFinder, |
1043 | value: isM3 ? 16.0 : 20.0, |
1044 | ); |
1045 | expectLeftEdgePadding( |
1046 | tester, |
1047 | finder: contentFinder, |
1048 | textScaleFactor: textScaleFactor, |
1049 | unscaledValue: 24.0, |
1050 | ); |
1051 | expectRightEdgePadding( |
1052 | tester, |
1053 | finder: contentFinder, |
1054 | textScaleFactor: textScaleFactor, |
1055 | unscaledValue: 24.0, |
1056 | ); |
1057 | expectVerticalInnerPadding( |
1058 | tester, |
1059 | top: contentFinder, |
1060 | bottom: actionsFinder, |
1061 | value: 24.0, |
1062 | ); |
1063 | expectLeftEdgePadding( |
1064 | tester, |
1065 | finder: actionsFinder, |
1066 | textScaleFactor: textScaleFactor, |
1067 | unscaledValue: 0.0, |
1068 | ); |
1069 | expectRightEdgePadding( |
1070 | tester, |
1071 | finder: actionsFinder, |
1072 | textScaleFactor: textScaleFactor, |
1073 | unscaledValue: 0.0, |
1074 | ); |
1075 | expectBottomEdgePadding( |
1076 | tester, |
1077 | finder: actionsFinder, |
1078 | textScaleFactor: textScaleFactor, |
1079 | unscaledValue: 0.0, |
1080 | ); |
1081 | }, |
1082 | ); |
1083 | } |
1084 | |
1085 | testWidgets( |
1086 | 'AlertDialog padding is correct when only title and actions are specified [textScaleFactor]=$textScaleFactor ', |
1087 | (WidgetTester tester) async { |
1088 | final AlertDialog dialog = AlertDialog(title: title, actions: actions); |
1089 | |
1090 | await openDialog(tester, dialog, textScaleFactor); |
1091 | |
1092 | expectTopEdgePadding( |
1093 | tester, |
1094 | finder: titleFinder, |
1095 | textScaleFactor: textScaleFactor, |
1096 | unscaledValue: 24.0, |
1097 | ); |
1098 | expectLeftEdgePadding( |
1099 | tester, |
1100 | finder: titleFinder, |
1101 | textScaleFactor: textScaleFactor, |
1102 | unscaledValue: 24.0, |
1103 | ); |
1104 | expectRightEdgePadding( |
1105 | tester, |
1106 | finder: titleFinder, |
1107 | textScaleFactor: textScaleFactor, |
1108 | unscaledValue: 24.0, |
1109 | ); |
1110 | expectVerticalInnerPadding(tester, top: titleFinder, bottom: actionsFinder, value: 20.0); |
1111 | expectLeftEdgePadding( |
1112 | tester, |
1113 | finder: actionsFinder, |
1114 | textScaleFactor: textScaleFactor, |
1115 | unscaledValue: 0.0, |
1116 | ); |
1117 | expectRightEdgePadding( |
1118 | tester, |
1119 | finder: actionsFinder, |
1120 | textScaleFactor: textScaleFactor, |
1121 | unscaledValue: 0.0, |
1122 | ); |
1123 | expectBottomEdgePadding( |
1124 | tester, |
1125 | finder: actionsFinder, |
1126 | textScaleFactor: textScaleFactor, |
1127 | unscaledValue: 0.0, |
1128 | ); |
1129 | }, |
1130 | ); |
1131 | |
1132 | testWidgets( |
1133 | 'AlertDialog padding is correct when only content and actions are specified [textScaleFactor]=$textScaleFactor ', |
1134 | (WidgetTester tester) async { |
1135 | final AlertDialog dialog = AlertDialog(content: content, actions: actions); |
1136 | |
1137 | await openDialog(tester, dialog, textScaleFactor); |
1138 | |
1139 | expectTopEdgePadding( |
1140 | tester, |
1141 | finder: contentFinder, |
1142 | textScaleFactor: textScaleFactor, |
1143 | unscaledValue: 20.0, |
1144 | ); |
1145 | expectLeftEdgePadding( |
1146 | tester, |
1147 | finder: contentFinder, |
1148 | textScaleFactor: textScaleFactor, |
1149 | unscaledValue: 24.0, |
1150 | ); |
1151 | expectRightEdgePadding( |
1152 | tester, |
1153 | finder: contentFinder, |
1154 | textScaleFactor: textScaleFactor, |
1155 | unscaledValue: 24.0, |
1156 | ); |
1157 | expectVerticalInnerPadding( |
1158 | tester, |
1159 | top: contentFinder, |
1160 | bottom: actionsFinder, |
1161 | value: 24.0, |
1162 | ); |
1163 | expectLeftEdgePadding( |
1164 | tester, |
1165 | finder: actionsFinder, |
1166 | textScaleFactor: textScaleFactor, |
1167 | unscaledValue: 0.0, |
1168 | ); |
1169 | expectRightEdgePadding( |
1170 | tester, |
1171 | finder: actionsFinder, |
1172 | textScaleFactor: textScaleFactor, |
1173 | unscaledValue: 0.0, |
1174 | ); |
1175 | expectBottomEdgePadding( |
1176 | tester, |
1177 | finder: actionsFinder, |
1178 | textScaleFactor: textScaleFactor, |
1179 | unscaledValue: 0.0, |
1180 | ); |
1181 | }, |
1182 | ); |
1183 | |
1184 | testWidgets( |
1185 | 'AlertDialog padding is correct when title, content, and actions are specified [textScaleFactor]=$textScaleFactor ', |
1186 | (WidgetTester tester) async { |
1187 | final AlertDialog dialog = AlertDialog(title: title, content: content, actions: actions); |
1188 | |
1189 | await openDialog(tester, dialog, textScaleFactor); |
1190 | |
1191 | expectTopEdgePadding( |
1192 | tester, |
1193 | finder: titleFinder, |
1194 | textScaleFactor: textScaleFactor, |
1195 | unscaledValue: 24.0, |
1196 | ); |
1197 | expectLeftEdgePadding( |
1198 | tester, |
1199 | finder: titleFinder, |
1200 | textScaleFactor: textScaleFactor, |
1201 | unscaledValue: 24.0, |
1202 | ); |
1203 | expectRightEdgePadding( |
1204 | tester, |
1205 | finder: titleFinder, |
1206 | textScaleFactor: textScaleFactor, |
1207 | unscaledValue: 24.0, |
1208 | ); |
1209 | expectVerticalInnerPadding(tester, top: titleFinder, bottom: contentFinder, value: 20.0); |
1210 | expectLeftEdgePadding( |
1211 | tester, |
1212 | finder: contentFinder, |
1213 | textScaleFactor: textScaleFactor, |
1214 | unscaledValue: 24.0, |
1215 | ); |
1216 | expectRightEdgePadding( |
1217 | tester, |
1218 | finder: contentFinder, |
1219 | textScaleFactor: textScaleFactor, |
1220 | unscaledValue: 24.0, |
1221 | ); |
1222 | expectVerticalInnerPadding( |
1223 | tester, |
1224 | top: contentFinder, |
1225 | bottom: actionsFinder, |
1226 | value: 24.0, |
1227 | ); |
1228 | expectLeftEdgePadding( |
1229 | tester, |
1230 | finder: actionsFinder, |
1231 | textScaleFactor: textScaleFactor, |
1232 | unscaledValue: 0.0, |
1233 | ); |
1234 | expectRightEdgePadding( |
1235 | tester, |
1236 | finder: actionsFinder, |
1237 | textScaleFactor: textScaleFactor, |
1238 | unscaledValue: 0.0, |
1239 | ); |
1240 | expectBottomEdgePadding( |
1241 | tester, |
1242 | finder: actionsFinder, |
1243 | textScaleFactor: textScaleFactor, |
1244 | unscaledValue: 0.0, |
1245 | ); |
1246 | }, |
1247 | ); |
1248 | |
1249 | testWidgets( |
1250 | 'SimpleDialog padding is correct when only children are specified [textScaleFactor]=$textScaleFactor ', |
1251 | (WidgetTester tester) async { |
1252 | final SimpleDialog dialog = SimpleDialog(children: children); |
1253 | |
1254 | await openDialog(tester, dialog, textScaleFactor); |
1255 | |
1256 | expectTopEdgePadding( |
1257 | tester, |
1258 | finder: childrenFinder, |
1259 | textScaleFactor: textScaleFactor, |
1260 | unscaledValue: 12.0, |
1261 | ); |
1262 | expectLeftEdgePadding( |
1263 | tester, |
1264 | finder: childrenFinder, |
1265 | textScaleFactor: textScaleFactor, |
1266 | unscaledValue: 0.0, |
1267 | ); |
1268 | expectRightEdgePadding( |
1269 | tester, |
1270 | finder: childrenFinder, |
1271 | textScaleFactor: textScaleFactor, |
1272 | unscaledValue: 0.0, |
1273 | ); |
1274 | expectBottomEdgePadding( |
1275 | tester, |
1276 | finder: childrenFinder, |
1277 | textScaleFactor: textScaleFactor, |
1278 | unscaledValue: 16.0, |
1279 | ); |
1280 | }, |
1281 | ); |
1282 | |
1283 | testWidgets( |
1284 | 'SimpleDialog padding is correct when title and children are specified [textScaleFactor]=$textScaleFactor ', |
1285 | (WidgetTester tester) async { |
1286 | final SimpleDialog dialog = SimpleDialog(title: title, children: children); |
1287 | |
1288 | await openDialog(tester, dialog, textScaleFactor); |
1289 | |
1290 | expectTopEdgePadding( |
1291 | tester, |
1292 | finder: titleFinder, |
1293 | textScaleFactor: textScaleFactor, |
1294 | unscaledValue: 24.0, |
1295 | ); |
1296 | expectLeftEdgePadding( |
1297 | tester, |
1298 | finder: titleFinder, |
1299 | textScaleFactor: textScaleFactor, |
1300 | unscaledValue: 24.0, |
1301 | ); |
1302 | expectRightEdgePadding( |
1303 | tester, |
1304 | finder: titleFinder, |
1305 | textScaleFactor: textScaleFactor, |
1306 | unscaledValue: 24.0, |
1307 | ); |
1308 | expectVerticalInnerPadding(tester, top: titleFinder, bottom: childrenFinder, value: 12.0); |
1309 | expectLeftEdgePadding( |
1310 | tester, |
1311 | finder: childrenFinder, |
1312 | textScaleFactor: textScaleFactor, |
1313 | unscaledValue: 0.0, |
1314 | ); |
1315 | expectRightEdgePadding( |
1316 | tester, |
1317 | finder: childrenFinder, |
1318 | textScaleFactor: textScaleFactor, |
1319 | unscaledValue: 0.0, |
1320 | ); |
1321 | expectBottomEdgePadding( |
1322 | tester, |
1323 | finder: childrenFinder, |
1324 | textScaleFactor: textScaleFactor, |
1325 | unscaledValue: 16.0, |
1326 | ); |
1327 | }, |
1328 | ); |
1329 | } |
1330 | }); |
1331 | |
1332 | testWidgets('Dialogs can set the vertical direction of overflowing actions', ( |
1333 | WidgetTester tester, |
1334 | ) async { |
1335 | final GlobalKey key1 = GlobalKey(); |
1336 | final GlobalKey key2 = GlobalKey(); |
1337 | |
1338 | final AlertDialog dialog = AlertDialog( |
1339 | title: const Text('title'), |
1340 | content: const Text('content'), |
1341 | actions: <Widget>[ |
1342 | ElevatedButton( |
1343 | key: key1, |
1344 | onPressed: () {}, |
1345 | child: const Text('Looooooooooooooong button 1'), |
1346 | ), |
1347 | ElevatedButton( |
1348 | key: key2, |
1349 | onPressed: () {}, |
1350 | child: const Text('Looooooooooooooong button 2'), |
1351 | ), |
1352 | ], |
1353 | actionsOverflowDirection: VerticalDirection.up, |
1354 | ); |
1355 | |
1356 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
1357 | |
1358 | await tester.tap(find.text('X')); |
1359 | await tester.pumpAndSettle(); |
1360 | |
1361 | final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
1362 | final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
1363 | // Second [ElevatedButton] should appear above the first. |
1364 | expect(buttonTwoRect.bottom, lessThanOrEqualTo(buttonOneRect.top)); |
1365 | }); |
1366 | |
1367 | testWidgets('Dialogs have no spacing by default for overflowing actions', ( |
1368 | WidgetTester tester, |
1369 | ) async { |
1370 | final GlobalKey key1 = GlobalKey(); |
1371 | final GlobalKey key2 = GlobalKey(); |
1372 | |
1373 | final AlertDialog dialog = AlertDialog( |
1374 | title: const Text('title'), |
1375 | content: const Text('content'), |
1376 | actions: <Widget>[ |
1377 | ElevatedButton( |
1378 | key: key1, |
1379 | onPressed: () {}, |
1380 | child: const Text('Looooooooooooooong button 1'), |
1381 | ), |
1382 | ElevatedButton( |
1383 | key: key2, |
1384 | onPressed: () {}, |
1385 | child: const Text('Looooooooooooooong button 2'), |
1386 | ), |
1387 | ], |
1388 | ); |
1389 | |
1390 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
1391 | |
1392 | await tester.tap(find.text('X')); |
1393 | await tester.pumpAndSettle(); |
1394 | |
1395 | final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
1396 | final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
1397 | expect(buttonOneRect.bottom, buttonTwoRect.top); |
1398 | }); |
1399 | |
1400 | testWidgets('Dialogs can set the button spacing of overflowing actions', ( |
1401 | WidgetTester tester, |
1402 | ) async { |
1403 | final GlobalKey key1 = GlobalKey(); |
1404 | final GlobalKey key2 = GlobalKey(); |
1405 | |
1406 | final AlertDialog dialog = AlertDialog( |
1407 | title: const Text('title'), |
1408 | content: const Text('content'), |
1409 | actions: <Widget>[ |
1410 | ElevatedButton( |
1411 | key: key1, |
1412 | onPressed: () {}, |
1413 | child: const Text('Looooooooooooooong button 1'), |
1414 | ), |
1415 | ElevatedButton( |
1416 | key: key2, |
1417 | onPressed: () {}, |
1418 | child: const Text('Looooooooooooooong button 2'), |
1419 | ), |
1420 | ], |
1421 | actionsOverflowButtonSpacing: 10.0, |
1422 | ); |
1423 | |
1424 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
1425 | |
1426 | await tester.tap(find.text('X')); |
1427 | await tester.pumpAndSettle(); |
1428 | |
1429 | final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
1430 | final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
1431 | expect(buttonOneRect.bottom, buttonTwoRect.top - 10.0); |
1432 | }); |
1433 | |
1434 | testWidgets('Dialogs can set the alignment of the OverflowBar', (WidgetTester tester) async { |
1435 | final GlobalKey key1 = GlobalKey(); |
1436 | final GlobalKey key2 = GlobalKey(); |
1437 | |
1438 | final AlertDialog dialog = AlertDialog( |
1439 | title: const Text('title'), |
1440 | content: const Text('content'), |
1441 | actions: <Widget>[ |
1442 | ElevatedButton(key: key1, onPressed: () {}, child: const Text('Loooooooooong button 1')), |
1443 | ElevatedButton( |
1444 | key: key2, |
1445 | onPressed: () {}, |
1446 | child: const Text('Loooooooooooooonger button 2'), |
1447 | ), |
1448 | ], |
1449 | actionsOverflowAlignment: OverflowBarAlignment.center, |
1450 | ); |
1451 | |
1452 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
1453 | |
1454 | await tester.tap(find.text('X')); |
1455 | await tester.pumpAndSettle(); |
1456 | |
1457 | final Rect buttonOneRect = tester.getRect(find.byKey(key1)); |
1458 | final Rect buttonTwoRect = tester.getRect(find.byKey(key2)); |
1459 | expect(buttonOneRect.center.dx, buttonTwoRect.center.dx); |
1460 | }); |
1461 | |
1462 | testWidgets('Dialogs removes MediaQuery padding and view insets', (WidgetTester tester) async { |
1463 | late BuildContext outerContext; |
1464 | late BuildContext routeContext; |
1465 | late BuildContext dialogContext; |
1466 | |
1467 | await tester.pumpWidget( |
1468 | Localizations( |
1469 | locale: const Locale('en', 'US'), |
1470 | delegates: const <LocalizationsDelegate<dynamic>>[ |
1471 | DefaultWidgetsLocalizations.delegate, |
1472 | DefaultMaterialLocalizations.delegate, |
1473 | ], |
1474 | child: MediaQuery( |
1475 | data: const MediaQueryData( |
1476 | padding: EdgeInsets.all(50.0), |
1477 | viewInsets: EdgeInsets.only(left: 25.0, bottom: 75.0), |
1478 | ), |
1479 | child: Navigator( |
1480 | onGenerateRoute: (_) { |
1481 | return PageRouteBuilder<void>( |
1482 | pageBuilder: ( |
1483 | BuildContext context, |
1484 | Animation<double> animation, |
1485 | Animation<double> secondaryAnimation, |
1486 | ) { |
1487 | outerContext = context; |
1488 | return Container(); |
1489 | }, |
1490 | ); |
1491 | }, |
1492 | ), |
1493 | ), |
1494 | ), |
1495 | ); |
1496 | |
1497 | showDialog<void>( |
1498 | context: outerContext, |
1499 | barrierDismissible: false, |
1500 | builder: (BuildContext context) { |
1501 | routeContext = context; |
1502 | return Dialog( |
1503 | child: Builder( |
1504 | builder: (BuildContext context) { |
1505 | dialogContext = context; |
1506 | return const Placeholder(); |
1507 | }, |
1508 | ), |
1509 | ); |
1510 | }, |
1511 | ); |
1512 | |
1513 | await tester.pump(); |
1514 | |
1515 | expect(MediaQuery.of(outerContext).padding, const EdgeInsets.all(50.0)); |
1516 | expect(MediaQuery.of(routeContext).padding, EdgeInsets.zero); |
1517 | expect(MediaQuery.of(dialogContext).padding, EdgeInsets.zero); |
1518 | expect(MediaQuery.of(outerContext).viewInsets, const EdgeInsets.only(left: 25.0, bottom: 75.0)); |
1519 | expect(MediaQuery.of(routeContext).viewInsets, const EdgeInsets.only(left: 25.0, bottom: 75.0)); |
1520 | expect(MediaQuery.of(dialogContext).viewInsets, EdgeInsets.zero); |
1521 | }); |
1522 | |
1523 | testWidgets('Dialog widget insets by viewInsets', (WidgetTester tester) async { |
1524 | await tester.pumpWidget( |
1525 | const MediaQuery( |
1526 | data: MediaQueryData(viewInsets: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0)), |
1527 | child: Dialog(child: Placeholder()), |
1528 | ), |
1529 | ); |
1530 | expect( |
1531 | tester.getRect(find.byType(Placeholder)), |
1532 | const Rect.fromLTRB(10.0 + 40.0, 20.0 + 24.0, 800.0 - (40.0 + 30.0), 600.0 - (24.0 + 40.0)), |
1533 | ); |
1534 | await tester.pumpWidget( |
1535 | const MediaQuery(data: MediaQueryData(), child: Dialog(child: Placeholder())), |
1536 | ); |
1537 | expect( |
1538 | // no change because this is an animation |
1539 | tester.getRect(find.byType(Placeholder)), |
1540 | const Rect.fromLTRB(10.0 + 40.0, 20.0 + 24.0, 800.0 - (40.0 + 30.0), 600.0 - (24.0 + 40.0)), |
1541 | ); |
1542 | await tester.pump(const Duration(seconds: 1)); |
1543 | expect( |
1544 | // animation finished |
1545 | tester.getRect(find.byType(Placeholder)), |
1546 | const Rect.fromLTRB(40.0, 24.0, 800.0 - 40.0, 600.0 - 24.0), |
1547 | ); |
1548 | }); |
1549 | |
1550 | testWidgets('Dialog insetPadding added to outside of dialog', (WidgetTester tester) async { |
1551 | // The default testing screen (800, 600) |
1552 | const Rect screenRect = Rect.fromLTRB(0.0, 0.0, 800.0, 600.0); |
1553 | |
1554 | // Test with no padding. |
1555 | await tester.pumpWidget( |
1556 | const MediaQuery( |
1557 | data: MediaQueryData(), |
1558 | child: Dialog(insetPadding: EdgeInsets.zero, child: Placeholder()), |
1559 | ), |
1560 | ); |
1561 | await tester.pumpAndSettle(); |
1562 | expect(tester.getRect(find.byType(Placeholder)), screenRect); |
1563 | |
1564 | // Test with an insetPadding. |
1565 | await tester.pumpWidget( |
1566 | const MediaQuery( |
1567 | data: MediaQueryData(), |
1568 | child: Dialog( |
1569 | insetPadding: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0), |
1570 | child: Placeholder(), |
1571 | ), |
1572 | ), |
1573 | ); |
1574 | await tester.pumpAndSettle(); |
1575 | expect( |
1576 | tester.getRect(find.byType(Placeholder)), |
1577 | Rect.fromLTRB( |
1578 | screenRect.left + 10.0, |
1579 | screenRect.top + 20.0, |
1580 | screenRect.right - 30.0, |
1581 | screenRect.bottom - 40.0, |
1582 | ), |
1583 | ); |
1584 | }); |
1585 | |
1586 | // Regression test for https://github.com/flutter/flutter/issues/78229. |
1587 | testWidgets('AlertDialog has correct semantics for content in iOS', (WidgetTester tester) async { |
1588 | final SemanticsTester semantics = SemanticsTester(tester); |
1589 | |
1590 | await tester.pumpWidget( |
1591 | MaterialApp( |
1592 | theme: ThemeData(platform: TargetPlatform.iOS), |
1593 | home: const AlertDialog( |
1594 | title: Text('title'), |
1595 | content: Column(children: <Widget>[Text('some content'), Text( 'more content')]), |
1596 | actions: <Widget>[TextButton(onPressed: null, child: Text('action'))], |
1597 | ), |
1598 | ), |
1599 | ); |
1600 | |
1601 | expect( |
1602 | semantics, |
1603 | hasSemantics( |
1604 | TestSemantics.root( |
1605 | children: <TestSemantics>[ |
1606 | TestSemantics( |
1607 | id: 1, |
1608 | textDirection: TextDirection.ltr, |
1609 | children: <TestSemantics>[ |
1610 | TestSemantics( |
1611 | id: 2, |
1612 | children: <TestSemantics>[ |
1613 | TestSemantics( |
1614 | id: 3, |
1615 | flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
1616 | children: <TestSemantics>[ |
1617 | TestSemantics( |
1618 | id: 4, |
1619 | role: SemanticsRole.alertDialog, |
1620 | children: <TestSemantics>[ |
1621 | TestSemantics(id: 5, label: 'title', textDirection: TextDirection.ltr), |
1622 | // The content semantics does not merge into the semantics |
1623 | // node 4. |
1624 | TestSemantics( |
1625 | id: 6, |
1626 | children: <TestSemantics>[ |
1627 | TestSemantics( |
1628 | id: 7, |
1629 | label: 'some content', |
1630 | textDirection: TextDirection.ltr, |
1631 | ), |
1632 | TestSemantics( |
1633 | id: 8, |
1634 | label: 'more content', |
1635 | textDirection: TextDirection.ltr, |
1636 | ), |
1637 | ], |
1638 | ), |
1639 | TestSemantics( |
1640 | id: 9, |
1641 | flags: <SemanticsFlag>[ |
1642 | SemanticsFlag.isButton, |
1643 | SemanticsFlag.hasEnabledState, |
1644 | ], |
1645 | label: 'action', |
1646 | textDirection: TextDirection.ltr, |
1647 | ), |
1648 | ], |
1649 | ), |
1650 | ], |
1651 | ), |
1652 | ], |
1653 | ), |
1654 | ], |
1655 | ), |
1656 | ], |
1657 | ), |
1658 | ignoreTransform: true, |
1659 | ignoreId: true, |
1660 | ignoreRect: true, |
1661 | ), |
1662 | ); |
1663 | |
1664 | semantics.dispose(); |
1665 | }); |
1666 | |
1667 | testWidgets('AlertDialog widget always contains alert route semantics for android', ( |
1668 | WidgetTester tester, |
1669 | ) async { |
1670 | final SemanticsTester semantics = SemanticsTester(tester); |
1671 | |
1672 | await tester.pumpWidget( |
1673 | MaterialApp( |
1674 | theme: ThemeData(platform: TargetPlatform.android), |
1675 | home: Material( |
1676 | child: Builder( |
1677 | builder: (BuildContext context) { |
1678 | return Center( |
1679 | child: ElevatedButton( |
1680 | child: const Text('X'), |
1681 | onPressed: () { |
1682 | showDialog<void>( |
1683 | context: context, |
1684 | builder: (BuildContext context) { |
1685 | return const AlertDialog( |
1686 | title: Text('Title'), |
1687 | content: Text('Y'), |
1688 | actions: <Widget>[], |
1689 | ); |
1690 | }, |
1691 | ); |
1692 | }, |
1693 | ), |
1694 | ); |
1695 | }, |
1696 | ), |
1697 | ), |
1698 | ), |
1699 | ); |
1700 | |
1701 | expect( |
1702 | semantics, |
1703 | isNot(includesNodeWith(label: 'Title', flags: <SemanticsFlag>[SemanticsFlag.namesRoute])), |
1704 | ); |
1705 | expect( |
1706 | semantics, |
1707 | isNot( |
1708 | includesNodeWith( |
1709 | label: 'Alert', |
1710 | flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute], |
1711 | ), |
1712 | ), |
1713 | ); |
1714 | |
1715 | await tester.tap(find.text('X')); |
1716 | await tester.pumpAndSettle(); |
1717 | // It does not use 'Title' as route semantics |
1718 | expect( |
1719 | semantics, |
1720 | isNot(includesNodeWith(label: 'Title', flags: <SemanticsFlag>[SemanticsFlag.namesRoute])), |
1721 | ); |
1722 | expect( |
1723 | semantics, |
1724 | includesNodeWith( |
1725 | label: 'Alert', |
1726 | flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute], |
1727 | ), |
1728 | ); |
1729 | |
1730 | semantics.dispose(); |
1731 | }); |
1732 | |
1733 | testWidgets('SimpleDialog does not introduce additional node', (WidgetTester tester) async { |
1734 | final SemanticsTester semantics = SemanticsTester(tester); |
1735 | |
1736 | await tester.pumpWidget( |
1737 | MaterialApp( |
1738 | theme: ThemeData(platform: TargetPlatform.android), |
1739 | home: Material( |
1740 | child: Builder( |
1741 | builder: (BuildContext context) { |
1742 | return Center( |
1743 | child: ElevatedButton( |
1744 | child: const Text('X'), |
1745 | onPressed: () { |
1746 | showDialog<void>( |
1747 | context: context, |
1748 | builder: (BuildContext context) { |
1749 | return const SimpleDialog(title: Text('Title'), semanticLabel: 'label'); |
1750 | }, |
1751 | ); |
1752 | }, |
1753 | ), |
1754 | ); |
1755 | }, |
1756 | ), |
1757 | ), |
1758 | ), |
1759 | ); |
1760 | |
1761 | await tester.tap(find.text('X')); |
1762 | await tester.pumpAndSettle(); |
1763 | // A scope route is not focusable in accessibility service. |
1764 | expect( |
1765 | semantics, |
1766 | includesNodeWith( |
1767 | label: 'label', |
1768 | flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute], |
1769 | ), |
1770 | ); |
1771 | |
1772 | semantics.dispose(); |
1773 | }); |
1774 | |
1775 | // Regression test for https://github.com/flutter/flutter/issues/78229. |
1776 | testWidgets('SimpleDialog has correct semantics for title in iOS', (WidgetTester tester) async { |
1777 | final SemanticsTester semantics = SemanticsTester(tester); |
1778 | |
1779 | await tester.pumpWidget( |
1780 | MaterialApp( |
1781 | theme: ThemeData(platform: TargetPlatform.iOS), |
1782 | home: const SimpleDialog( |
1783 | title: Text('title'), |
1784 | children: <Widget>[Text('content'), TextButton(onPressed: null, child: Text( 'action'))], |
1785 | ), |
1786 | ), |
1787 | ); |
1788 | |
1789 | expect( |
1790 | semantics, |
1791 | hasSemantics( |
1792 | TestSemantics.root( |
1793 | children: <TestSemantics>[ |
1794 | TestSemantics( |
1795 | id: 1, |
1796 | textDirection: TextDirection.ltr, |
1797 | children: <TestSemantics>[ |
1798 | TestSemantics( |
1799 | id: 2, |
1800 | children: <TestSemantics>[ |
1801 | TestSemantics( |
1802 | id: 3, |
1803 | flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
1804 | children: <TestSemantics>[ |
1805 | TestSemantics( |
1806 | id: 4, |
1807 | role: SemanticsRole.dialog, |
1808 | children: <TestSemantics>[ |
1809 | // Title semantics does not merge into the semantics |
1810 | // node 4. |
1811 | TestSemantics(id: 5, label: 'title', textDirection: TextDirection.ltr), |
1812 | TestSemantics( |
1813 | id: 6, |
1814 | flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], |
1815 | children: <TestSemantics>[ |
1816 | TestSemantics( |
1817 | id: 7, |
1818 | label: 'content', |
1819 | textDirection: TextDirection.ltr, |
1820 | ), |
1821 | TestSemantics( |
1822 | id: 8, |
1823 | flags: <SemanticsFlag>[ |
1824 | SemanticsFlag.isButton, |
1825 | SemanticsFlag.hasEnabledState, |
1826 | ], |
1827 | label: 'action', |
1828 | textDirection: TextDirection.ltr, |
1829 | ), |
1830 | ], |
1831 | ), |
1832 | ], |
1833 | ), |
1834 | ], |
1835 | ), |
1836 | ], |
1837 | ), |
1838 | ], |
1839 | ), |
1840 | ], |
1841 | ), |
1842 | ignoreTransform: true, |
1843 | ignoreId: true, |
1844 | ignoreRect: true, |
1845 | ), |
1846 | ); |
1847 | |
1848 | semantics.dispose(); |
1849 | }); |
1850 | |
1851 | testWidgets('Dismissible.confirmDismiss defers to an AlertDialog', (WidgetTester tester) async { |
1852 | final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
1853 | final List<int> dismissedItems = <int>[]; |
1854 | |
1855 | // Dismiss is confirmed IFF confirmDismiss() returns true. |
1856 | Future<bool?> confirmDismiss(DismissDirection dismissDirection) async { |
1857 | return showDialog<bool>( |
1858 | context: scaffoldKey.currentContext!, |
1859 | builder: (BuildContext context) { |
1860 | return AlertDialog( |
1861 | actions: <Widget>[ |
1862 | TextButton( |
1863 | child: const Text('TRUE'), |
1864 | onPressed: () { |
1865 | Navigator.pop(context, true); // showDialog() returns true |
1866 | }, |
1867 | ), |
1868 | TextButton( |
1869 | child: const Text('FALSE'), |
1870 | onPressed: () { |
1871 | Navigator.pop(context, false); // showDialog() returns false |
1872 | }, |
1873 | ), |
1874 | ], |
1875 | ); |
1876 | }, |
1877 | ); |
1878 | } |
1879 | |
1880 | Widget buildDismissibleItem(int item, StateSetter setState) { |
1881 | return Dismissible( |
1882 | key: ValueKey<int>(item), |
1883 | confirmDismiss: confirmDismiss, |
1884 | onDismissed: (DismissDirection direction) { |
1885 | setState(() { |
1886 | expect(dismissedItems.contains(item), isFalse); |
1887 | dismissedItems.add(item); |
1888 | }); |
1889 | }, |
1890 | child: SizedBox(height: 100.0, child: Text(item.toString())), |
1891 | ); |
1892 | } |
1893 | |
1894 | Widget buildFrame() { |
1895 | return MaterialApp( |
1896 | home: StatefulBuilder( |
1897 | builder: (BuildContext context, StateSetter setState) { |
1898 | return Scaffold( |
1899 | key: scaffoldKey, |
1900 | body: Padding( |
1901 | padding: const EdgeInsets.all(16.0), |
1902 | child: ListView( |
1903 | itemExtent: 100.0, |
1904 | children: |
1905 | <int>[0, 1, 2, 3, 4] |
1906 | .where((int i) => !dismissedItems.contains(i)) |
1907 | .map<Widget>((int item) => buildDismissibleItem(item, setState)) |
1908 | .toList(), |
1909 | ), |
1910 | ), |
1911 | ); |
1912 | }, |
1913 | ), |
1914 | ); |
1915 | } |
1916 | |
1917 | Future<void> dismissItem(WidgetTester tester, int item) async { |
1918 | await tester.fling( |
1919 | find.text(item.toString()), |
1920 | const Offset(300.0, 0.0), |
1921 | 1000.0, |
1922 | ); // fling to the right |
1923 | await tester.pump(); // start the slide |
1924 | await tester.pump(const Duration(seconds: 1)); // finish the slide and start shrinking... |
1925 | await tester.pump(); // first frame of shrinking animation |
1926 | await tester.pump( |
1927 | const Duration(seconds: 1), |
1928 | ); // finish the shrinking and call the callback... |
1929 | await tester.pump(); // rebuild after the callback removes the entry |
1930 | } |
1931 | |
1932 | // Dismiss item 0 is confirmed via the AlertDialog |
1933 | await tester.pumpWidget(buildFrame()); |
1934 | expect(dismissedItems, isEmpty); |
1935 | await dismissItem(tester, 0); // Causes the AlertDialog to appear per confirmDismiss |
1936 | await tester.pumpAndSettle(); |
1937 | await tester.tap(find.text('TRUE')); // AlertDialog action |
1938 | await tester.pumpAndSettle(); |
1939 | expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
1940 | expect(find.text('FALSE'), findsNothing); |
1941 | expect(dismissedItems, <int>[0]); |
1942 | expect(find.text('0'), findsNothing); |
1943 | |
1944 | // Dismiss item 1 is not confirmed via the AlertDialog |
1945 | await tester.pumpWidget(buildFrame()); |
1946 | expect(dismissedItems, <int>[0]); |
1947 | await dismissItem(tester, 1); // Causes the AlertDialog to appear per confirmDismiss |
1948 | await tester.pumpAndSettle(); |
1949 | await tester.tap(find.text('FALSE')); // AlertDialog action |
1950 | await tester.pumpAndSettle(); |
1951 | expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
1952 | expect(find.text('FALSE'), findsNothing); |
1953 | expect(dismissedItems, <int>[0]); |
1954 | expect(find.text('0'), findsNothing); |
1955 | expect(find.text('1'), findsOneWidget); |
1956 | |
1957 | // Dismiss item 1 is not confirmed via the AlertDialog |
1958 | await tester.pumpWidget(buildFrame()); |
1959 | expect(dismissedItems, <int>[0]); |
1960 | await dismissItem(tester, 1); // Causes the AlertDialog to appear per confirmDismiss |
1961 | await tester.pumpAndSettle(); |
1962 | expect(find.text('FALSE'), findsOneWidget); |
1963 | expect(find.text('TRUE'), findsOneWidget); |
1964 | await tester.tapAt(Offset.zero); // Tap outside of the AlertDialog |
1965 | await tester.pumpAndSettle(); |
1966 | expect(dismissedItems, <int>[0]); |
1967 | expect(find.text('0'), findsNothing); |
1968 | expect(find.text('1'), findsOneWidget); |
1969 | expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
1970 | expect(find.text('FALSE'), findsNothing); |
1971 | |
1972 | // Dismiss item 1 is confirmed via the AlertDialog |
1973 | await tester.pumpWidget(buildFrame()); |
1974 | expect(dismissedItems, <int>[0]); |
1975 | await dismissItem(tester, 1); // Causes the AlertDialog to appear per confirmDismiss |
1976 | await tester.pumpAndSettle(); |
1977 | await tester.tap(find.text('TRUE')); // AlertDialog action |
1978 | await tester.pumpAndSettle(); |
1979 | expect(find.text('TRUE'), findsNothing); // Dialog was dismissed |
1980 | expect(find.text('FALSE'), findsNothing); |
1981 | expect(dismissedItems, <int>[0, 1]); |
1982 | expect(find.text('0'), findsNothing); |
1983 | expect(find.text('1'), findsNothing); |
1984 | }); |
1985 | |
1986 | // Regression test for https://github.com/flutter/flutter/issues/28505. |
1987 | testWidgets('showDialog only gets Theme from context on the first call', ( |
1988 | WidgetTester tester, |
1989 | ) async { |
1990 | Widget buildFrame(Key builderKey) { |
1991 | return MaterialApp( |
1992 | home: Center( |
1993 | child: Builder( |
1994 | key: builderKey, |
1995 | builder: (BuildContext outerContext) { |
1996 | return ElevatedButton( |
1997 | onPressed: () { |
1998 | showDialog<void>( |
1999 | context: outerContext, |
2000 | builder: (BuildContext innerContext) { |
2001 | return const AlertDialog(title: Text('Title')); |
2002 | }, |
2003 | ); |
2004 | }, |
2005 | child: const Text('Show Dialog'), |
2006 | ); |
2007 | }, |
2008 | ), |
2009 | ), |
2010 | ); |
2011 | } |
2012 | |
2013 | await tester.pumpWidget(buildFrame(UniqueKey())); |
2014 | |
2015 | // Open the dialog. |
2016 | await tester.tap(find.byType(ElevatedButton)); |
2017 | await tester.pumpAndSettle(); |
2018 | |
2019 | // Force the Builder to be recreated (new key) which causes outerContext to |
2020 | // be deactivated. If showDialog()'s implementation were to refer to |
2021 | // outerContext again, it would crash. |
2022 | await tester.pumpWidget(buildFrame(UniqueKey())); |
2023 | await tester.pump(); |
2024 | }); |
2025 | |
2026 | testWidgets('showDialog safe area', (WidgetTester tester) async { |
2027 | await tester.pumpWidget( |
2028 | MaterialApp( |
2029 | builder: (BuildContext context, Widget? child) { |
2030 | return MediaQuery( |
2031 | // Set up the safe area to be 20 pixels in from each side |
2032 | data: const MediaQueryData(padding: EdgeInsets.all(20.0)), |
2033 | child: child!, |
2034 | ); |
2035 | }, |
2036 | home: const Center(child: Text('Test')), |
2037 | ), |
2038 | ); |
2039 | final BuildContext context = tester.element(find.text('Test')); |
2040 | |
2041 | // By default it should honor the safe area |
2042 | showDialog<void>( |
2043 | context: context, |
2044 | builder: (BuildContext context) { |
2045 | return const Placeholder(); |
2046 | }, |
2047 | ); |
2048 | await tester.pumpAndSettle(); |
2049 | expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0)); |
2050 | expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0)); |
2051 | |
2052 | // Dismiss it and test with useSafeArea off |
2053 | await tester.tapAt(const Offset(10.0, 10.0)); |
2054 | showDialog<void>( |
2055 | context: context, |
2056 | builder: (BuildContext context) { |
2057 | return const Placeholder(); |
2058 | }, |
2059 | useSafeArea: false, |
2060 | ); |
2061 | await tester.pumpAndSettle(); |
2062 | // Should take up the whole screen |
2063 | expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero); |
2064 | expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); |
2065 | }); |
2066 | |
2067 | testWidgets('showDialog uses root navigator by default', (WidgetTester tester) async { |
2068 | final DialogObserver rootObserver = DialogObserver(); |
2069 | final DialogObserver nestedObserver = DialogObserver(); |
2070 | |
2071 | await tester.pumpWidget( |
2072 | MaterialApp( |
2073 | navigatorObservers: <NavigatorObserver>[rootObserver], |
2074 | home: Navigator( |
2075 | observers: <NavigatorObserver>[nestedObserver], |
2076 | onGenerateRoute: (RouteSettings settings) { |
2077 | return MaterialPageRoute<dynamic>( |
2078 | builder: (BuildContext context) { |
2079 | return ElevatedButton( |
2080 | onPressed: () { |
2081 | showDialog<void>( |
2082 | context: context, |
2083 | builder: (BuildContext innerContext) { |
2084 | return const AlertDialog(title: Text('Title')); |
2085 | }, |
2086 | ); |
2087 | }, |
2088 | child: const Text('Show Dialog'), |
2089 | ); |
2090 | }, |
2091 | ); |
2092 | }, |
2093 | ), |
2094 | ), |
2095 | ); |
2096 | |
2097 | // Open the dialog. |
2098 | await tester.tap(find.byType(ElevatedButton)); |
2099 | |
2100 | expect(rootObserver.dialogCount, 1); |
2101 | expect(nestedObserver.dialogCount, 0); |
2102 | }); |
2103 | |
2104 | testWidgets('showDialog uses nested navigator if useRootNavigator is false', ( |
2105 | WidgetTester tester, |
2106 | ) async { |
2107 | final DialogObserver rootObserver = DialogObserver(); |
2108 | final DialogObserver nestedObserver = DialogObserver(); |
2109 | |
2110 | await tester.pumpWidget( |
2111 | MaterialApp( |
2112 | navigatorObservers: <NavigatorObserver>[rootObserver], |
2113 | home: Navigator( |
2114 | observers: <NavigatorObserver>[nestedObserver], |
2115 | onGenerateRoute: (RouteSettings settings) { |
2116 | return MaterialPageRoute<dynamic>( |
2117 | builder: (BuildContext context) { |
2118 | return ElevatedButton( |
2119 | onPressed: () { |
2120 | showDialog<void>( |
2121 | context: context, |
2122 | useRootNavigator: false, |
2123 | builder: (BuildContext innerContext) { |
2124 | return const AlertDialog(title: Text('Title')); |
2125 | }, |
2126 | ); |
2127 | }, |
2128 | child: const Text('Show Dialog'), |
2129 | ); |
2130 | }, |
2131 | ); |
2132 | }, |
2133 | ), |
2134 | ), |
2135 | ); |
2136 | |
2137 | // Open the dialog. |
2138 | await tester.tap(find.byType(ElevatedButton)); |
2139 | |
2140 | expect(rootObserver.dialogCount, 0); |
2141 | expect(nestedObserver.dialogCount, 1); |
2142 | }); |
2143 | |
2144 | testWidgets('showDialog throws a friendly user message when context is not active', ( |
2145 | WidgetTester tester, |
2146 | ) async { |
2147 | // Regression test for https://github.com/flutter/flutter/issues/12467 |
2148 | await tester.pumpWidget(const MaterialApp(home: Center(child: Text('Test')))); |
2149 | final BuildContext context = tester.element(find.text('Test')); |
2150 | |
2151 | await tester.pumpWidget(const MaterialApp(home: Center())); |
2152 | |
2153 | Object? error; |
2154 | try { |
2155 | showDialog<void>( |
2156 | context: context, |
2157 | builder: (BuildContext innerContext) { |
2158 | return const AlertDialog(title: Text('Title')); |
2159 | }, |
2160 | ); |
2161 | } catch (exception) { |
2162 | error = exception; |
2163 | } |
2164 | |
2165 | expect(error, isNotNull); |
2166 | expect(error, isFlutterError); |
2167 | if (error is FlutterError) { |
2168 | final ErrorSummary summary = error.diagnostics.first as ErrorSummary; |
2169 | expect(summary.toString(), 'This BuildContext is no longer valid.'); |
2170 | } |
2171 | }); |
2172 | |
2173 | group('showDialog avoids overlapping display features', () { |
2174 | testWidgets('positioning with anchorPoint', (WidgetTester tester) async { |
2175 | await tester.pumpWidget( |
2176 | MaterialApp( |
2177 | builder: (BuildContext context, Widget? child) { |
2178 | return MediaQuery( |
2179 | // Display has a vertical hinge down the middle |
2180 | data: const MediaQueryData( |
2181 | size: Size(800, 600), |
2182 | displayFeatures: <DisplayFeature>[ |
2183 | DisplayFeature( |
2184 | bounds: Rect.fromLTRB(390, 0, 410, 600), |
2185 | type: DisplayFeatureType.hinge, |
2186 | state: DisplayFeatureState.unknown, |
2187 | ), |
2188 | ], |
2189 | ), |
2190 | child: child!, |
2191 | ); |
2192 | }, |
2193 | home: const Center(child: Text('Test')), |
2194 | ), |
2195 | ); |
2196 | final BuildContext context = tester.element(find.text('Test')); |
2197 | |
2198 | showDialog<void>( |
2199 | context: context, |
2200 | builder: (BuildContext context) { |
2201 | return const Placeholder(); |
2202 | }, |
2203 | anchorPoint: const Offset(1000, 0), |
2204 | ); |
2205 | await tester.pumpAndSettle(); |
2206 | |
2207 | // Should take the right side of the screen |
2208 | expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0)); |
2209 | expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); |
2210 | }); |
2211 | |
2212 | testWidgets('positioning with Directionality', (WidgetTester tester) async { |
2213 | await tester.pumpWidget( |
2214 | MaterialApp( |
2215 | builder: (BuildContext context, Widget? child) { |
2216 | return MediaQuery( |
2217 | // Display has a vertical hinge down the middle |
2218 | data: const MediaQueryData( |
2219 | size: Size(800, 600), |
2220 | displayFeatures: <DisplayFeature>[ |
2221 | DisplayFeature( |
2222 | bounds: Rect.fromLTRB(390, 0, 410, 600), |
2223 | type: DisplayFeatureType.hinge, |
2224 | state: DisplayFeatureState.unknown, |
2225 | ), |
2226 | ], |
2227 | ), |
2228 | child: Directionality(textDirection: TextDirection.rtl, child: child!), |
2229 | ); |
2230 | }, |
2231 | home: const Center(child: Text('Test')), |
2232 | ), |
2233 | ); |
2234 | final BuildContext context = tester.element(find.text('Test')); |
2235 | |
2236 | showDialog<void>( |
2237 | context: context, |
2238 | builder: (BuildContext context) { |
2239 | return const Placeholder(); |
2240 | }, |
2241 | ); |
2242 | await tester.pumpAndSettle(); |
2243 | |
2244 | // Since this is RTL, it should place the dialog on the right screen |
2245 | expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0)); |
2246 | expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); |
2247 | }); |
2248 | |
2249 | testWidgets('positioning by default', (WidgetTester tester) async { |
2250 | await tester.pumpWidget( |
2251 | MaterialApp( |
2252 | builder: (BuildContext context, Widget? child) { |
2253 | return MediaQuery( |
2254 | // Display has a vertical hinge down the middle |
2255 | data: const MediaQueryData( |
2256 | size: Size(800, 600), |
2257 | displayFeatures: <DisplayFeature>[ |
2258 | DisplayFeature( |
2259 | bounds: Rect.fromLTRB(390, 0, 410, 600), |
2260 | type: DisplayFeatureType.hinge, |
2261 | state: DisplayFeatureState.unknown, |
2262 | ), |
2263 | ], |
2264 | ), |
2265 | child: child!, |
2266 | ); |
2267 | }, |
2268 | home: const Center(child: Text('Test')), |
2269 | ), |
2270 | ); |
2271 | final BuildContext context = tester.element(find.text('Test')); |
2272 | |
2273 | showDialog<void>( |
2274 | context: context, |
2275 | builder: (BuildContext context) { |
2276 | return const Placeholder(); |
2277 | }, |
2278 | ); |
2279 | await tester.pumpAndSettle(); |
2280 | |
2281 | // By default it should place the dialog on the left screen |
2282 | expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero); |
2283 | expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(390.0, 600.0)); |
2284 | }); |
2285 | }); |
2286 | |
2287 | group('AlertDialog.scrollable: ', () { |
2288 | testWidgets('Title is scrollable', (WidgetTester tester) async { |
2289 | final Key titleKey = UniqueKey(); |
2290 | final AlertDialog dialog = AlertDialog( |
2291 | title: Container(key: titleKey, color: Colors.green, height: 1000), |
2292 | scrollable: true, |
2293 | ); |
2294 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
2295 | await tester.tap(find.text('X')); |
2296 | await tester.pumpAndSettle(); |
2297 | |
2298 | final RenderBox box = tester.renderObject(find.byKey(titleKey)); |
2299 | final Offset originalOffset = box.localToGlobal(Offset.zero); |
2300 | await tester.drag(find.byKey(titleKey), const Offset(0.0, -200.0)); |
2301 | expect(box.localToGlobal(Offset.zero), equals(originalOffset.translate(0.0, -200.0))); |
2302 | }); |
2303 | |
2304 | testWidgets('Content is scrollable', (WidgetTester tester) async { |
2305 | final Key contentKey = UniqueKey(); |
2306 | final AlertDialog dialog = AlertDialog( |
2307 | content: Container(key: contentKey, color: Colors.orange, height: 1000), |
2308 | scrollable: true, |
2309 | ); |
2310 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
2311 | await tester.tap(find.text('X')); |
2312 | await tester.pumpAndSettle(); |
2313 | |
2314 | final RenderBox box = tester.renderObject(find.byKey(contentKey)); |
2315 | final Offset originalOffset = box.localToGlobal(Offset.zero); |
2316 | await tester.drag(find.byKey(contentKey), const Offset(0.0, -200.0)); |
2317 | expect(box.localToGlobal(Offset.zero), equals(originalOffset.translate(0.0, -200.0))); |
2318 | }); |
2319 | |
2320 | testWidgets('Title and content are scrollable', (WidgetTester tester) async { |
2321 | final Key titleKey = UniqueKey(); |
2322 | final Key contentKey = UniqueKey(); |
2323 | final AlertDialog dialog = AlertDialog( |
2324 | title: Container(key: titleKey, color: Colors.green, height: 400), |
2325 | content: Container(key: contentKey, color: Colors.orange, height: 400), |
2326 | scrollable: true, |
2327 | ); |
2328 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
2329 | await tester.tap(find.text('X')); |
2330 | await tester.pumpAndSettle(); |
2331 | |
2332 | final RenderBox title = tester.renderObject(find.byKey(titleKey)); |
2333 | final RenderBox content = tester.renderObject(find.byKey(contentKey)); |
2334 | final Offset titleOriginalOffset = title.localToGlobal(Offset.zero); |
2335 | final Offset contentOriginalOffset = content.localToGlobal(Offset.zero); |
2336 | |
2337 | // Dragging the title widget should scroll both the title |
2338 | // and the content widgets. |
2339 | await tester.drag(find.byKey(titleKey), const Offset(0.0, -200.0)); |
2340 | expect(title.localToGlobal(Offset.zero), equals(titleOriginalOffset.translate(0.0, -200.0))); |
2341 | expect( |
2342 | content.localToGlobal(Offset.zero), |
2343 | equals(contentOriginalOffset.translate(0.0, -200.0)), |
2344 | ); |
2345 | |
2346 | // Dragging the content widget should scroll both the title |
2347 | // and the content widgets. |
2348 | await tester.drag(find.byKey(contentKey), const Offset(0.0, 200.0)); |
2349 | expect(title.localToGlobal(Offset.zero), equals(titleOriginalOffset)); |
2350 | expect(content.localToGlobal(Offset.zero), equals(contentOriginalOffset)); |
2351 | }); |
2352 | }); |
2353 | |
2354 | testWidgets('Dialog with RouteSettings', (WidgetTester tester) async { |
2355 | late RouteSettings currentRouteSetting; |
2356 | |
2357 | await tester.pumpWidget( |
2358 | MaterialApp( |
2359 | navigatorObservers: <NavigatorObserver>[ |
2360 | _ClosureNavigatorObserver( |
2361 | onDidChange: (Route<dynamic> newRoute) { |
2362 | currentRouteSetting = newRoute.settings; |
2363 | }, |
2364 | ), |
2365 | ], |
2366 | home: const Material( |
2367 | child: Center(child: ElevatedButton(onPressed: null, child: Text('Go'))), |
2368 | ), |
2369 | ), |
2370 | ); |
2371 | |
2372 | final BuildContext context = tester.element(find.text('Go')); |
2373 | const RouteSettings exampleSetting = RouteSettings(name: 'simple'); |
2374 | |
2375 | final Future<int?> result = showDialog<int>( |
2376 | context: context, |
2377 | builder: (BuildContext context) { |
2378 | return SimpleDialog( |
2379 | title: const Text('Title'), |
2380 | children: <Widget>[ |
2381 | SimpleDialogOption( |
2382 | child: const Text('X'), |
2383 | onPressed: () { |
2384 | Navigator.of(context).pop(); |
2385 | }, |
2386 | ), |
2387 | ], |
2388 | ); |
2389 | }, |
2390 | routeSettings: exampleSetting, |
2391 | ); |
2392 | |
2393 | await tester.pumpAndSettle(); |
2394 | expect(find.text('Title'), findsOneWidget); |
2395 | expect(currentRouteSetting, exampleSetting); |
2396 | |
2397 | await tester.tap(find.text('X')); |
2398 | await tester.pumpAndSettle(); |
2399 | |
2400 | expect(await result, isNull); |
2401 | await tester.pumpAndSettle(); |
2402 | expect(currentRouteSetting.name, '/'); |
2403 | }); |
2404 | |
2405 | testWidgets('showDialog - custom barrierLabel', (WidgetTester tester) async { |
2406 | final SemanticsTester semantics = SemanticsTester(tester); |
2407 | |
2408 | await tester.pumpWidget( |
2409 | MaterialApp( |
2410 | theme: ThemeData(platform: TargetPlatform.iOS), |
2411 | home: Material( |
2412 | child: Builder( |
2413 | builder: (BuildContext context) { |
2414 | return Center( |
2415 | child: ElevatedButton( |
2416 | child: const Text('X'), |
2417 | onPressed: () { |
2418 | showDialog<void>( |
2419 | context: context, |
2420 | barrierLabel: 'Custom label', |
2421 | builder: (BuildContext context) { |
2422 | return const AlertDialog( |
2423 | title: Text('Title'), |
2424 | content: Text('Y'), |
2425 | actions: <Widget>[], |
2426 | ); |
2427 | }, |
2428 | ); |
2429 | }, |
2430 | ), |
2431 | ); |
2432 | }, |
2433 | ), |
2434 | ), |
2435 | ), |
2436 | ); |
2437 | |
2438 | expect( |
2439 | semantics, |
2440 | isNot( |
2441 | includesNodeWith(label: 'Custom label', flags: <SemanticsFlag>[SemanticsFlag.namesRoute]), |
2442 | ), |
2443 | ); |
2444 | semantics.dispose(); |
2445 | }); |
2446 | |
2447 | testWidgets('DialogRoute is state restorable', (WidgetTester tester) async { |
2448 | await tester.pumpWidget( |
2449 | const MaterialApp(restorationScopeId: 'app', home: _RestorableDialogTestWidget()), |
2450 | ); |
2451 | |
2452 | expect(find.byType(AlertDialog), findsNothing); |
2453 | |
2454 | await tester.tap(find.text('X')); |
2455 | await tester.pumpAndSettle(); |
2456 | |
2457 | expect(find.byType(AlertDialog), findsOneWidget); |
2458 | final TestRestorationData restorationData = await tester.getRestorationData(); |
2459 | |
2460 | await tester.restartAndRestore(); |
2461 | |
2462 | expect(find.byType(AlertDialog), findsOneWidget); |
2463 | |
2464 | // Tap on the barrier. |
2465 | await tester.tapAt(const Offset(10.0, 10.0)); |
2466 | await tester.pumpAndSettle(); |
2467 | |
2468 | expect(find.byType(AlertDialog), findsNothing); |
2469 | |
2470 | await tester.restoreFrom(restorationData); |
2471 | expect(find.byType(AlertDialog), findsOneWidget); |
2472 | }, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615 |
2473 | |
2474 | testWidgets('AlertDialog.actionsAlignment', (WidgetTester tester) async { |
2475 | final Key actionKey = UniqueKey(); |
2476 | |
2477 | Widget buildFrame(MainAxisAlignment? alignment) { |
2478 | return MaterialApp( |
2479 | theme: ThemeData(useMaterial3: false), |
2480 | home: Scaffold( |
2481 | body: AlertDialog( |
2482 | content: const SizedBox(width: 800), |
2483 | actionsAlignment: alignment, |
2484 | actions: <Widget>[SizedBox(key: actionKey, width: 20, height: 20)], |
2485 | buttonPadding: EdgeInsets.zero, |
2486 | insetPadding: EdgeInsets.zero, |
2487 | ), |
2488 | ), |
2489 | ); |
2490 | } |
2491 | |
2492 | // Default configuration |
2493 | await tester.pumpWidget(buildFrame(null)); |
2494 | expect(tester.getTopLeft(find.byType(AlertDialog)).dx, 0); |
2495 | expect(tester.getTopRight(find.byType(AlertDialog)).dx, 800); |
2496 | expect(tester.getSize(find.byType(OverflowBar)).width, 800); |
2497 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, 800 - 20); |
2498 | expect(tester.getTopRight(find.byKey(actionKey)).dx, 800); |
2499 | |
2500 | // All possible alignment values |
2501 | |
2502 | await tester.pumpWidget(buildFrame(MainAxisAlignment.start)); |
2503 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, 0); |
2504 | expect(tester.getTopRight(find.byKey(actionKey)).dx, 20); |
2505 | |
2506 | await tester.pumpWidget(buildFrame(MainAxisAlignment.center)); |
2507 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, (800 - 20) / 2); |
2508 | expect(tester.getTopRight(find.byKey(actionKey)).dx, (800 - 20) / 2 + 20); |
2509 | |
2510 | await tester.pumpWidget(buildFrame(MainAxisAlignment.end)); |
2511 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, 800 - 20); |
2512 | expect(tester.getTopRight(find.byKey(actionKey)).dx, 800); |
2513 | |
2514 | await tester.pumpWidget(buildFrame(MainAxisAlignment.spaceBetween)); |
2515 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, 0); |
2516 | expect(tester.getTopRight(find.byKey(actionKey)).dx, 20); |
2517 | |
2518 | await tester.pumpWidget(buildFrame(MainAxisAlignment.spaceAround)); |
2519 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, (800 - 20) / 2); |
2520 | expect(tester.getTopRight(find.byKey(actionKey)).dx, (800 - 20) / 2 + 20); |
2521 | |
2522 | await tester.pumpWidget(buildFrame(MainAxisAlignment.spaceEvenly)); |
2523 | expect(tester.getTopLeft(find.byKey(actionKey)).dx, (800 - 20) / 2); |
2524 | expect(tester.getTopRight(find.byKey(actionKey)).dx, (800 - 20) / 2 + 20); |
2525 | }); |
2526 | |
2527 | testWidgets('Uses closed loop focus traversal', (WidgetTester tester) async { |
2528 | final FocusNode okNode = FocusNode(); |
2529 | final FocusNode cancelNode = FocusNode(); |
2530 | |
2531 | Future<bool> nextFocus() async { |
2532 | final bool result = Actions.invoke(primaryFocus!.context!, const NextFocusIntent())! as bool; |
2533 | await tester.pump(); |
2534 | return result; |
2535 | } |
2536 | |
2537 | Future<bool> previousFocus() async { |
2538 | final bool result = |
2539 | Actions.invoke(primaryFocus!.context!, const PreviousFocusIntent())! as bool; |
2540 | await tester.pump(); |
2541 | return result; |
2542 | } |
2543 | |
2544 | final AlertDialog dialog = AlertDialog( |
2545 | content: const Text('Test dialog'), |
2546 | actions: <Widget>[ |
2547 | TextButton(focusNode: okNode, onPressed: () {}, child: const Text('OK')), |
2548 | TextButton(focusNode: cancelNode, onPressed: () {}, child: const Text('Cancel')), |
2549 | ], |
2550 | ); |
2551 | await tester.pumpWidget(_buildAppWithDialog(dialog)); |
2552 | await tester.tap(find.text('X')); |
2553 | await tester.pumpAndSettle(); |
2554 | |
2555 | // Start at OK |
2556 | okNode.requestFocus(); |
2557 | await tester.pump(); |
2558 | expect(okNode.hasFocus, true); |
2559 | expect(cancelNode.hasFocus, false); |
2560 | |
2561 | // OK -> Cancel |
2562 | expect(await nextFocus(), true); |
2563 | expect(okNode.hasFocus, false); |
2564 | expect(cancelNode.hasFocus, true); |
2565 | |
2566 | // Cancel -> OK |
2567 | expect(await nextFocus(), true); |
2568 | expect(okNode.hasFocus, true); |
2569 | expect(cancelNode.hasFocus, false); |
2570 | |
2571 | // Cancel <- OK |
2572 | expect(await previousFocus(), true); |
2573 | expect(okNode.hasFocus, false); |
2574 | expect(cancelNode.hasFocus, true); |
2575 | |
2576 | // OK <- Cancel |
2577 | expect(await previousFocus(), true); |
2578 | expect(okNode.hasFocus, true); |
2579 | expect(cancelNode.hasFocus, false); |
2580 | |
2581 | cancelNode.dispose(); |
2582 | okNode.dispose(); |
2583 | }); |
2584 | |
2585 | testWidgets('Adaptive AlertDialog shows correct widget on each platform', ( |
2586 | WidgetTester tester, |
2587 | ) async { |
2588 | final AlertDialog dialog = AlertDialog.adaptive( |
2589 | content: Container(height: 5000.0, width: 300.0, color: Colors.green[500]), |
2590 | actions: <Widget>[TextButton(onPressed: () {}, child: const Text('OK'))], |
2591 | ); |
2592 | |
2593 | for (final TargetPlatform platform in <TargetPlatform>[ |
2594 | TargetPlatform.iOS, |
2595 | TargetPlatform.macOS, |
2596 | ]) { |
2597 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: ThemeData(platform: platform))); |
2598 | await tester.pumpAndSettle(); |
2599 | |
2600 | await tester.tap(find.text('X')); |
2601 | await tester.pumpAndSettle(); |
2602 | |
2603 | expect(find.byType(CupertinoAlertDialog), findsOneWidget); |
2604 | |
2605 | await tester.tapAt(const Offset(10.0, 10.0)); |
2606 | await tester.pumpAndSettle(); |
2607 | } |
2608 | |
2609 | for (final TargetPlatform platform in <TargetPlatform>[ |
2610 | TargetPlatform.android, |
2611 | TargetPlatform.fuchsia, |
2612 | TargetPlatform.linux, |
2613 | TargetPlatform.windows, |
2614 | ]) { |
2615 | await tester.pumpWidget(_buildAppWithDialog(dialog, theme: ThemeData(platform: platform))); |
2616 | await tester.pumpAndSettle(); |
2617 | |
2618 | await tester.tap(find.text('X')); |
2619 | await tester.pumpAndSettle(); |
2620 | |
2621 | expect(find.byType(CupertinoAlertDialog), findsNothing); |
2622 | |
2623 | await tester.tapAt(const Offset(10.0, 10.0)); |
2624 | await tester.pumpAndSettle(); |
2625 | } |
2626 | }); |
2627 | |
2628 | testWidgets('showAdaptiveDialog should not allow dismiss on barrier on iOS by default', ( |
2629 | WidgetTester tester, |
2630 | ) async { |
2631 | await tester.pumpWidget( |
2632 | MaterialApp( |
2633 | theme: ThemeData(platform: TargetPlatform.iOS), |
2634 | home: const Material( |
2635 | child: Center(child: ElevatedButton(onPressed: null, child: Text('Go'))), |
2636 | ), |
2637 | ), |
2638 | ); |
2639 | |
2640 | final BuildContext context = tester.element(find.text('Go')); |
2641 | |
2642 | showDialog<void>( |
2643 | context: context, |
2644 | builder: (BuildContext context) { |
2645 | return Container( |
2646 | width: 100.0, |
2647 | height: 100.0, |
2648 | alignment: Alignment.center, |
2649 | child: const Text('Dialog1'), |
2650 | ); |
2651 | }, |
2652 | ); |
2653 | |
2654 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
2655 | expect(find.text('Dialog1'), findsOneWidget); |
2656 | |
2657 | // Tap on the barrier. |
2658 | await tester.tapAt(const Offset(10.0, 10.0)); |
2659 | |
2660 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
2661 | expect(find.text('Dialog1'), findsNothing); |
2662 | |
2663 | showAdaptiveDialog<void>( |
2664 | context: context, |
2665 | builder: (BuildContext context) { |
2666 | return Container( |
2667 | width: 100.0, |
2668 | height: 100.0, |
2669 | alignment: Alignment.center, |
2670 | child: const Text('Dialog2'), |
2671 | ); |
2672 | }, |
2673 | ); |
2674 | |
2675 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
2676 | expect(find.text('Dialog2'), findsOneWidget); |
2677 | |
2678 | // Tap on the barrier, which shouldn't do anything this time. |
2679 | await tester.tapAt(const Offset(10.0, 10.0)); |
2680 | |
2681 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
2682 | expect(find.text('Dialog2'), findsOneWidget); |
2683 | }); |
2684 | |
2685 | testWidgets('Applies AnimationStyle to showAdaptiveDialog', (WidgetTester tester) async { |
2686 | const AnimationStyle animationStyle = AnimationStyle( |
2687 | duration: Duration(seconds: 1), |
2688 | curve: Curves.easeInOut, |
2689 | ); |
2690 | |
2691 | await tester.pumpWidget( |
2692 | const MaterialApp( |
2693 | home: Material(child: Center(child: ElevatedButton(onPressed: null, child: Text('Go')))), |
2694 | ), |
2695 | ); |
2696 | final BuildContext context = tester.element(find.text('Go')); |
2697 | showAdaptiveDialog<void>( |
2698 | context: context, |
2699 | builder: (BuildContext context) { |
2700 | return Container( |
2701 | width: 100.0, |
2702 | height: 100.0, |
2703 | alignment: Alignment.center, |
2704 | child: const Text('Dialog1'), |
2705 | ); |
2706 | }, |
2707 | animationStyle: animationStyle, |
2708 | ); |
2709 | |
2710 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
2711 | expect(find.text('Dialog1'), findsOneWidget); |
2712 | |
2713 | await tester.tapAt(const Offset(10.0, 10.0)); |
2714 | await tester.pumpAndSettle(const Duration(seconds: 1)); |
2715 | expect(find.text('Dialog1'), findsNothing); |
2716 | }); |
2717 | |
2718 | testWidgets('Uses open focus traversal when overridden', (WidgetTester tester) async { |
2719 | final FocusNode okNode = FocusNode(); |
2720 | addTearDown(okNode.dispose); |
2721 | final FocusNode cancelNode = FocusNode(); |
2722 | addTearDown(cancelNode.dispose); |
2723 | |
2724 | Future<bool> nextFocus() async { |
2725 | final bool result = Actions.invoke(primaryFocus!.context!, const NextFocusIntent())! as bool; |
2726 | await tester.pump(); |
2727 | return result; |
2728 | } |
2729 | |
2730 | final AlertDialog dialog = AlertDialog( |
2731 | content: const Text('Test dialog'), |
2732 | actions: <Widget>[ |
2733 | TextButton(focusNode: okNode, onPressed: () {}, child: const Text('OK')), |
2734 | TextButton(focusNode: cancelNode, onPressed: () {}, child: const Text('Cancel')), |
2735 | ], |
2736 | ); |
2737 | await tester.pumpWidget( |
2738 | _buildAppWithDialog(dialog, traversalEdgeBehavior: TraversalEdgeBehavior.leaveFlutterView), |
2739 | ); |
2740 | await tester.tap(find.text('X')); |
2741 | await tester.pumpAndSettle(); |
2742 | |
2743 | // Start at OK |
2744 | okNode.requestFocus(); |
2745 | await tester.pump(); |
2746 | expect(okNode.hasFocus, true); |
2747 | expect(cancelNode.hasFocus, false); |
2748 | |
2749 | // OK -> Cancel |
2750 | expect(await nextFocus(), true); |
2751 | expect(okNode.hasFocus, false); |
2752 | expect(cancelNode.hasFocus, true); |
2753 | |
2754 | // Cancel -> nothing |
2755 | expect(await nextFocus(), false); |
2756 | expect(okNode.hasFocus, false); |
2757 | expect(cancelNode.hasFocus, false); |
2758 | }); |
2759 | |
2760 | testWidgets('Dialog.insetPadding is nullable', (WidgetTester tester) async { |
2761 | const Dialog dialog = Dialog(); |
2762 | expect(dialog.insetPadding, isNull); |
2763 | }); |
2764 | |
2765 | testWidgets('AlertDialog.insetPadding is nullable', (WidgetTester tester) async { |
2766 | const AlertDialog alertDialog = AlertDialog(); |
2767 | expect(alertDialog.insetPadding, isNull); |
2768 | }); |
2769 | |
2770 | testWidgets('SimpleDialog.insetPadding is nullable', (WidgetTester tester) async { |
2771 | const SimpleDialog simpleDialog = SimpleDialog(); |
2772 | expect(simpleDialog.insetPadding, isNull); |
2773 | }); |
2774 | |
2775 | // This is a regression test for https://github.com/flutter/flutter/issues/153983. |
2776 | testWidgets('Can pass a null value to AlertDialog.adaptive clip behavior', ( |
2777 | WidgetTester tester, |
2778 | ) async { |
2779 | for (final Clip? clipBehavior in <Clip?>[null, ...Clip.values]) { |
2780 | AlertDialog.adaptive(clipBehavior: clipBehavior); |
2781 | } |
2782 | }); |
2783 | |
2784 | testWidgets('Setting DialogRoute.requestFocus to false does not request focus on the dialog', ( |
2785 | WidgetTester tester, |
2786 | ) async { |
2787 | late BuildContext savedContext; |
2788 | final FocusNode focusNode = FocusNode(); |
2789 | addTearDown(focusNode.dispose); |
2790 | const String dialogText = 'Dialog Text'; |
2791 | await tester.pumpWidget( |
2792 | MaterialApp( |
2793 | home: Material( |
2794 | child: Builder( |
2795 | builder: (BuildContext context) { |
2796 | savedContext = context; |
2797 | return TextField(focusNode: focusNode); |
2798 | }, |
2799 | ), |
2800 | ), |
2801 | ), |
2802 | ); |
2803 | await tester.pump(); |
2804 | |
2805 | FocusNode? getTextFieldFocusNode() { |
2806 | return tester |
2807 | .widget<Focus>(find.descendant(of: find.byType(TextField), matching: find.byType(Focus))) |
2808 | .focusNode; |
2809 | } |
2810 | |
2811 | // Initially, there is no dialog and the text field has no focus. |
2812 | expect(find.text(dialogText), findsNothing); |
2813 | expect(getTextFieldFocusNode()?.hasFocus, false); |
2814 | |
2815 | // Request focus on the text field. |
2816 | focusNode.requestFocus(); |
2817 | await tester.pump(); |
2818 | expect(getTextFieldFocusNode()?.hasFocus, true); |
2819 | |
2820 | // Bring up dialog. |
2821 | final NavigatorState navigator = Navigator.of(savedContext); |
2822 | navigator.push( |
2823 | DialogRoute<void>( |
2824 | context: savedContext, |
2825 | builder: (BuildContext context) => const Text(dialogText), |
2826 | ), |
2827 | ); |
2828 | await tester.pump(); |
2829 | |
2830 | // The dialog is showing and the text field has lost focus. |
2831 | expect(find.text(dialogText), findsOneWidget); |
2832 | expect(getTextFieldFocusNode()?.hasFocus, false); |
2833 | |
2834 | // Dismiss the dialog. |
2835 | navigator.pop(); |
2836 | await tester.pump(); |
2837 | |
2838 | // The dialog is dismissed and the focus is shifted back to the text field. |
2839 | expect(find.text(dialogText), findsNothing); |
2840 | expect(getTextFieldFocusNode()?.hasFocus, true); |
2841 | |
2842 | // Bring up dialog again with requestFocus to false. |
2843 | navigator.push( |
2844 | ModalBottomSheetRoute<void>( |
2845 | requestFocus: false, |
2846 | isScrollControlled: false, |
2847 | builder: (BuildContext context) => const Text(dialogText), |
2848 | ), |
2849 | ); |
2850 | await tester.pump(); |
2851 | |
2852 | // The dialog is showing and the text field still has focus. |
2853 | expect(find.text(dialogText), findsOneWidget); |
2854 | expect(getTextFieldFocusNode()?.hasFocus, true); |
2855 | }); |
2856 | |
2857 | testWidgets('requestFocus works correctly in showDialog.', (WidgetTester tester) async { |
2858 | final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
2859 | final FocusNode focusNode = FocusNode(); |
2860 | addTearDown(focusNode.dispose); |
2861 | await tester.pumpWidget( |
2862 | MaterialApp( |
2863 | navigatorKey: navigatorKey, |
2864 | home: Scaffold(body: TextField(focusNode: focusNode)), |
2865 | ), |
2866 | ); |
2867 | focusNode.requestFocus(); |
2868 | await tester.pump(); |
2869 | expect(focusNode.hasFocus, true); |
2870 | |
2871 | showDialog<void>( |
2872 | context: navigatorKey.currentContext!, |
2873 | requestFocus: true, |
2874 | builder: (BuildContext context) => const Text('dialog'), |
2875 | ); |
2876 | await tester.pumpAndSettle(); |
2877 | expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, true); |
2878 | expect(focusNode.hasFocus, false); |
2879 | |
2880 | navigatorKey.currentState!.pop(); |
2881 | await tester.pumpAndSettle(); |
2882 | expect(focusNode.hasFocus, true); |
2883 | |
2884 | showDialog<void>( |
2885 | context: navigatorKey.currentContext!, |
2886 | requestFocus: false, |
2887 | builder: (BuildContext context) => const Text('dialog'), |
2888 | ); |
2889 | await tester.pumpAndSettle(); |
2890 | expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, false); |
2891 | expect(focusNode.hasFocus, true); |
2892 | }); |
2893 | } |
2894 | |
2895 | @pragma('vm:entry-point') |
2896 | class _RestorableDialogTestWidget extends StatelessWidget { |
2897 | const _RestorableDialogTestWidget(); |
2898 | |
2899 | @pragma('vm:entry-point') |
2900 | static Route<Object?> _materialDialogBuilder(BuildContext context, Object? arguments) { |
2901 | return DialogRoute<void>( |
2902 | context: context, |
2903 | builder: (BuildContext context) => const AlertDialog(title: Text('Material Alert!')), |
2904 | ); |
2905 | } |
2906 | |
2907 | @override |
2908 | Widget build(BuildContext context) { |
2909 | return Scaffold( |
2910 | body: Center( |
2911 | child: OutlinedButton( |
2912 | onPressed: () { |
2913 | Navigator.of(context).restorablePush(_materialDialogBuilder); |
2914 | }, |
2915 | child: const Text('X'), |
2916 | ), |
2917 | ), |
2918 | ); |
2919 | } |
2920 | } |
2921 | |
2922 | class DialogObserver extends NavigatorObserver { |
2923 | int dialogCount = 0; |
2924 | |
2925 | @override |
2926 | void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { |
2927 | if (route is DialogRoute) { |
2928 | dialogCount++; |
2929 | } |
2930 | super.didPush(route, previousRoute); |
2931 | } |
2932 | } |
2933 | |
2934 | class _ClosureNavigatorObserver extends NavigatorObserver { |
2935 | _ClosureNavigatorObserver({required this.onDidChange}); |
2936 | |
2937 | final void Function(Route<dynamic> newRoute) onDidChange; |
2938 | |
2939 | @override |
2940 | void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(route); |
2941 | |
2942 | @override |
2943 | void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(previousRoute!); |
2944 | |
2945 | @override |
2946 | void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) => |
2947 | onDidChange(previousRoute!); |
2948 | |
2949 | @override |
2950 | void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) => onDidChange(newRoute!); |
2951 | } |
2952 |
Definitions
- _buildAppWithDialog
- _getMaterialFromDialog
- _getTextRenderObjectFromDialog
- _findOverflowBar
- _defaultM2DialogShape
- _defaultM3DialogShape
- main
- openDialog
- expectLeftEdgePadding
- expectRightEdgePadding
- expectTopEdgePadding
- expectBottomEdgePadding
- expectVerticalInnerPadding
- confirmDismiss
- buildDismissibleItem
- buildFrame
- dismissItem
- buildFrame
- buildFrame
- nextFocus
- previousFocus
- nextFocus
- getTextFieldFocusNode
- _RestorableDialogTestWidget
- _RestorableDialogTestWidget
- _materialDialogBuilder
- build
- DialogObserver
- didPush
- _ClosureNavigatorObserver
- _ClosureNavigatorObserver
- didPush
- didPop
- didRemove
Learn more about Flutter for embedded and desktop on industrialflutter.com