action_sheet_test.dart 33.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';

import 'package:flutter_test/flutter_test.dart';

import '../widgets/semantics_tester.dart';

void main() {
13
  testWidgets('Verify that a tap on modal barrier dismisses an action sheet', (WidgetTester tester) async {
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
        const CupertinoActionSheet(
          title: Text('Action Sheet'),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(find.text('Action Sheet'), findsOneWidget);

    await tester.tapAt(const Offset(20.0, 20.0));
    await tester.pump();
    expect(find.text('Action Sheet'), findsNothing);
  });

32
  testWidgets('Verify that a tap on title section (not buttons) does not dismiss an action sheet', (WidgetTester tester) async {
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
        const CupertinoActionSheet(
          title: Text('Action Sheet'),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(find.text('Action Sheet'), findsOneWidget);

    await tester.tap(find.text('Action Sheet'));
    await tester.pump();
    expect(find.text('Action Sheet'), findsOneWidget);
  });

  testWidgets('Action sheet destructive text style', (WidgetTester tester) async {
    await tester.pumpWidget(
      boilerplate(
54
        CupertinoActionSheetAction(
55 56
          isDestructiveAction: true,
          child: const Text('Ok'),
57
          onPressed: () { },
58 59 60 61 62 63
        ),
      ),
    );

    final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok'));

Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
64
    expect(widget.style.color, const CupertinoDynamicColor.withBrightnessAndContrast(
65 66 67 68 69 70 71 72 73 74 75 76 77 78
      color: Color.fromARGB(255, 255, 59, 48),
      darkColor: Color.fromARGB(255, 255, 69, 58),
      highContrastColor: Color.fromARGB(255, 215, 0, 21),
      darkHighContrastColor: Color.fromARGB(255, 255, 105, 97),
    ));
  });

  testWidgets('Action sheet dark mode', (WidgetTester tester) async {
    final Widget action = CupertinoActionSheetAction(
      child: const Text('action'),
      onPressed: () {},
    );

    Brightness brightness = Brightness.light;
79
    late StateSetter stateSetter;
80 81 82 83 84 85

    TextStyle actionTextStyle(String text) {
      return tester.widget<DefaultTextStyle>(
        find.descendant(
          of: find.widgetWithText(CupertinoActionSheetAction, text),
          matching: find.byType(DefaultTextStyle),
86
        ),
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
87
      ).style;
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    }

    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
        StatefulBuilder(
          builder: (BuildContext context, StateSetter setter) {
            stateSetter = setter;
            return CupertinoTheme(
              data: CupertinoThemeData(
                brightness: brightness,
                primaryColor: const CupertinoDynamicColor.withBrightnessAndContrast(
                  color: Color.fromARGB(255, 0, 122, 255),
                  darkColor: Color.fromARGB(255, 10, 132, 255),
                  highContrastColor: Color.fromARGB(255, 0, 64, 221),
                  darkHighContrastColor: Color.fromARGB(255, 64, 156, 255),
                ),
              ),
              child: CupertinoActionSheet(actions: <Widget>[action]),
            );
          },
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(
116
      actionTextStyle('action').color!.value,
117 118 119 120 121 122 123
      const Color.fromARGB(255, 0, 122, 255).value,
    );

    stateSetter(() { brightness = Brightness.dark; });
    await tester.pump();

    expect(
124
      actionTextStyle('action').color!.value,
125 126
      const Color.fromARGB(255, 10, 132, 255).value,
    );
127 128 129 130 131
  });

  testWidgets('Action sheet default text style', (WidgetTester tester) async {
    await tester.pumpWidget(
      boilerplate(
132
        CupertinoActionSheetAction(
133 134
          isDefaultAction: true,
          child: const Text('Ok'),
135
          onPressed: () { },
136 137 138 139 140 141
        ),
      ),
    );

    final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok'));

Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
142
    expect(widget.style.fontWeight, equals(FontWeight.w600));
143 144
  });

145
  testWidgets('Action sheet text styles are correct when both title and message are included', (WidgetTester tester) async {
146 147 148 149
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
        const CupertinoActionSheet(
          title: Text('Action Sheet'),
150
          message: Text('An action sheet'),
151 152 153 154 155 156 157 158 159 160 161 162
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    final DefaultTextStyle titleStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
        'Action Sheet'));
    final DefaultTextStyle messageStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
        'An action sheet'));

Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
163 164
    expect(titleStyle.style.fontWeight, FontWeight.w600);
    expect(messageStyle.style.fontWeight, FontWeight.w400);
165 166
  });

167
  testWidgets('Action sheet text styles are correct when title but no message is included', (WidgetTester tester) async {
168 169 170 171 172 173 174 175 176 177 178 179 180 181
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
        const CupertinoActionSheet(
          title: Text('Action Sheet'),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    final DefaultTextStyle titleStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
        'Action Sheet'));

Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
182
    expect(titleStyle.style.fontWeight, FontWeight.w400);
183 184
  });

185
  testWidgets('Action sheet text styles are correct when message but no title is included', (WidgetTester tester) async {
186 187 188 189 190 191 192 193 194 195 196 197 198 199
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
        const CupertinoActionSheet(
          message: Text('An action sheet'),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    final DefaultTextStyle messageStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle,
        'An action sheet'));

Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
200
    expect(messageStyle.style.fontWeight, FontWeight.w600);
201 202 203
  });

  testWidgets('Content section but no actions', (WidgetTester tester) async {
204
    final ScrollController scrollController = ScrollController();
205 206
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
207
        CupertinoActionSheet(
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
          title: const Text('The title'),
          message: const Text('The message.'),
          messageScrollController: scrollController,
        ),
      ),
    );

    await tester.tap(find.text('Go'));

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    // Content section should be at the bottom left of action sheet
    // (minus padding).
    expect(tester.getBottomLeft(find.byType(ClipRRect)),
        tester.getBottomLeft(find.byType(CupertinoActionSheet)) - const Offset(-8.0, 10.0));

    // Check that the dialog size is the same as the content section size
    // (minus padding).
    expect(
      tester.getSize(find.byType(ClipRRect)).height,
      tester.getSize(find.byType(CupertinoActionSheet)).height  - 20.0,
    );

    expect(
      tester.getSize(find.byType(ClipRRect)).width,
      tester.getSize(find.byType(CupertinoActionSheet)).width - 16.0,
    );
  });

  testWidgets('Actions but no content section', (WidgetTester tester) async {
239
    final ScrollController actionScrollController = ScrollController();
240 241
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
242
        CupertinoActionSheet(
243
          actions: <Widget>[
244
            CupertinoActionSheetAction(
245
              child: const Text('One'),
246
              onPressed: () { },
247
            ),
248
            CupertinoActionSheetAction(
249
              child: const Text('Two'),
250
              onPressed: () { },
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
            ),
          ],
          actionScrollController: actionScrollController,
        ),
      ),
    );

    await tester.tap(find.text('Go'));

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    final Finder finder = find.byElementPredicate(
      (Element element) {
        return element.widget.runtimeType.toString() == '_CupertinoAlertActionSection';
      },
    );

    // Check that the title/message section is not displayed (action section is
    // at the top of the action sheet + padding).
    expect(tester.getTopLeft(finder),
        tester.getTopLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, 10.0));

    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, 10.0),
        tester.getTopLeft(find.widgetWithText(CupertinoActionSheetAction, 'One')));
    expect(tester.getBottomLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, -10.0),
        tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Two')));
  });

  testWidgets('Action section is scrollable', (WidgetTester tester) async {
281
    final ScrollController actionScrollController = ScrollController();
282 283
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
284 285
        Builder(builder: (BuildContext context) {
          return MediaQuery(
286
            data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0),
287
            child: CupertinoActionSheet(
288 289 290
              title: const Text('The title'),
              message: const Text('The message.'),
              actions: <Widget>[
291
                CupertinoActionSheetAction(
292
                  child: const Text('One'),
293
                  onPressed: () { },
294
                ),
295
                CupertinoActionSheetAction(
296
                  child: const Text('Two'),
297
                  onPressed: () { },
298
                ),
299
                CupertinoActionSheetAction(
300
                  child: const Text('Three'),
301
                  onPressed: () { },
302
                ),
303
                CupertinoActionSheetAction(
304
                  child: const Text('Four'),
305
                  onPressed: () { },
306
                ),
307
                CupertinoActionSheetAction(
308
                  child: const Text('Five'),
309
                  onPressed: () { },
310 311 312 313 314 315
                ),
              ],
              actionScrollController: actionScrollController,
            ),
          );
        }),
316
      ),
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
    );

    await tester.tap(find.text('Go'));

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    // Check that the action buttons list is scrollable.
    expect(actionScrollController.offset, 0.0);
    actionScrollController.jumpTo(100.0);
    expect(actionScrollController.offset, 100.0);
    actionScrollController.jumpTo(0.0);

    // Check that the action buttons are aligned vertically.
    expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'One')).dx, equals(400.0));
    expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Two')).dx, equals(400.0));
    expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Three')).dx, equals(400.0));
    expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Four')).dx, equals(400.0));
    expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Five')).dx, equals(400.0));

    // Check that the action buttons are the correct heights.
    expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'One')).height, equals(92.0));
    expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Two')).height, equals(92.0));
    expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Three')).height, equals(92.0));
    expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Four')).height, equals(92.0));
    expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Five')).height, equals(92.0));
  });

  testWidgets('Content section is scrollable', (WidgetTester tester) async {
346
    final ScrollController messageScrollController = ScrollController();
347
    late double screenHeight;
348 349
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
350
        Builder(builder: (BuildContext context) {
351
          screenHeight = MediaQuery.of(context).size.height;
352
          return MediaQuery(
353
            data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0),
354
            child: CupertinoActionSheet(
355
              title: const Text('The title'),
356
              message: Text('Very long content' * 200),
357
              actions: <Widget>[
358
                CupertinoActionSheetAction(
359
                  child: const Text('One'),
360
                  onPressed: () { },
361
                ),
362
                CupertinoActionSheetAction(
363
                  child: const Text('Two'),
364
                  onPressed: () { },
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
                ),
              ],
              messageScrollController: messageScrollController,
            ),
          );
        }),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(messageScrollController.offset, 0.0);
    messageScrollController.jumpTo(100.0);
    expect(messageScrollController.offset, 100.0);
    // Set the scroll position back to zero.
    messageScrollController.jumpTo(0.0);

    // Expect the action sheet to take all available height.
    expect(tester.getSize(find.byType(CupertinoActionSheet)).height, screenHeight);
  });

  testWidgets('Tap on button calls onPressed', (WidgetTester tester) async {
    bool wasPressed = false;
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
391 392
        Builder(builder: (BuildContext context) {
          return CupertinoActionSheet(
393
            actions: <Widget>[
394
              CupertinoActionSheetAction(
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
                child: const Text('One'),
                onPressed: () {
                  wasPressed = true;
                  Navigator.pop(context);
                },
              ),
            ],
          );
        }),
      ),
    );

    await tester.tap(find.text('Go'));

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(wasPressed, isFalse);

    await tester.tap(find.text('One'));

    expect(wasPressed, isTrue);

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(find.text('One'), findsNothing);
  });

424
  testWidgets('Action sheet width is correct when given infinite horizontal space', (WidgetTester tester) async {
425 426
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
427
        Row(
428
          children: <Widget>[
429
            CupertinoActionSheet(
430
              actions: <Widget>[
431
                CupertinoActionSheetAction(
432
                  child: const Text('One'),
433
                  onPressed: () { },
434
                ),
435
                CupertinoActionSheetAction(
436
                  child: const Text('Two'),
437
                  onPressed: () { },
438 439 440 441 442 443 444 445 446 447 448 449 450 451
                ),
              ],
            ),
          ],
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(tester.getSize(find.byType(CupertinoActionSheet)).width, 600.0);
  });

452
  testWidgets('Action sheet height is correct when given infinite vertical space', (WidgetTester tester) async {
453 454
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
455
        Column(
456
          children: <Widget>[
457
            CupertinoActionSheet(
458
              actions: <Widget>[
459
                CupertinoActionSheetAction(
460
                  child: const Text('One'),
461
                  onPressed: () { },
462
                ),
463
                CupertinoActionSheetAction(
464
                  child: const Text('Two'),
465
                  onPressed: () { },
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
                ),
              ],
            ),
          ],
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(tester.getSize(find.byType(CupertinoActionSheet)).height,
        moreOrLessEquals(132.33333333333334));
  });

  testWidgets('1 action button with cancel button', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
484
        CupertinoActionSheet(
485
          title: const Text('The title'),
486
          message: Text('Very long content' * 200),
487
          actions: <Widget>[
488
            CupertinoActionSheetAction(
489
              child: const Text('One'),
490
              onPressed: () { },
491 492
            ),
          ],
493
          cancelButton: CupertinoActionSheetAction(
494
            child: const Text('Cancel'),
495
            onPressed: () { },
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    // Action section is size of one action button.
    expect(findScrollableActionsSectionRenderBox(tester).size.height, 56.0);
  });

  testWidgets('2 action buttons with cancel button', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
511
        CupertinoActionSheet(
512
          title: const Text('The title'),
513
          message: Text('Very long content' * 200),
514
          actions: <Widget>[
515
            CupertinoActionSheetAction(
516
              child: const Text('One'),
517
              onPressed: () { },
518
            ),
519
            CupertinoActionSheetAction(
520
              child: const Text('Two'),
521
              onPressed: () { },
522 523
            ),
          ],
524
          cancelButton: CupertinoActionSheetAction(
525
            child: const Text('Cancel'),
526
            onPressed: () { },
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(findScrollableActionsSectionRenderBox(tester).size.height,
        moreOrLessEquals(112.33333333333331));
  });

  testWidgets('3 action buttons with cancel button', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
542
        CupertinoActionSheet(
543
          title: const Text('The title'),
544
          message: Text('Very long content' * 200),
545
          actions: <Widget>[
546
            CupertinoActionSheetAction(
547
              child: const Text('One'),
548
              onPressed: () { },
549
            ),
550
            CupertinoActionSheetAction(
551
              child: const Text('Two'),
552
              onPressed: () { },
553
            ),
554
            CupertinoActionSheetAction(
555
              child: const Text('Three'),
556
              onPressed: () { },
557 558
            ),
          ],
559
          cancelButton: CupertinoActionSheetAction(
560
            child: const Text('Cancel'),
561
            onPressed: () { },
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(findScrollableActionsSectionRenderBox(tester).size.height,
        moreOrLessEquals(168.66666666666669));
  });

  testWidgets('4+ action buttons with cancel button', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
577
        CupertinoActionSheet(
578
          title: const Text('The title'),
579
          message: Text('Very long content' * 200),
580
          actions: <Widget>[
581
            CupertinoActionSheetAction(
582
              child: const Text('One'),
583
              onPressed: () { },
584
            ),
585
            CupertinoActionSheetAction(
586
              child: const Text('Two'),
587
              onPressed: () { },
588
            ),
589
            CupertinoActionSheetAction(
590
              child: const Text('Three'),
591
              onPressed: () { },
592
            ),
593
            CupertinoActionSheetAction(
594
              child: const Text('Four'),
595
              onPressed: () { },
596 597
            ),
          ],
598
          cancelButton: CupertinoActionSheetAction(
599
            child: const Text('Cancel'),
600
            onPressed: () { },
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(findScrollableActionsSectionRenderBox(tester).size.height,
        moreOrLessEquals(84.33333333333337));
  });

  testWidgets('1 action button without cancel button', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
616
        CupertinoActionSheet(
617
          title: const Text('The title'),
618
          message: Text('Very long content' * 200),
619
          actions: <Widget>[
620
            CupertinoActionSheetAction(
621
              child: const Text('One'),
622
              onPressed: () { },
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
            ),
          ],
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(findScrollableActionsSectionRenderBox(tester).size.height, 56.0);
  });

  testWidgets('2+ action buttons without cancel button', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
638
        CupertinoActionSheet(
639
          title: const Text('The title'),
640
          message: Text('Very long content' * 200),
641
          actions: <Widget>[
642
            CupertinoActionSheetAction(
643
              child: const Text('One'),
644
              onPressed: () { },
645
            ),
646
            CupertinoActionSheetAction(
647
              child: const Text('Two'),
648
              onPressed: () { },
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
            ),
          ],
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(findScrollableActionsSectionRenderBox(tester).size.height,
        moreOrLessEquals(84.33333333333337));
  });

  testWidgets('Action sheet with just cancel button is correct', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
665 666
        CupertinoActionSheet(
          cancelButton: CupertinoActionSheetAction(
667
            child: const Text('Cancel'),
668
            onPressed: () { },
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    // Height should be cancel button height + padding
    expect(tester.getSize(find.byType(CupertinoActionSheet)).height, 76.0);
    expect(tester.getSize(find.byType(CupertinoActionSheet)).width, 600.0);
  });

  testWidgets('Cancel button tap calls onPressed', (WidgetTester tester) async {
    bool wasPressed = false;
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
686 687 688
        Builder(builder: (BuildContext context) {
          return CupertinoActionSheet(
            cancelButton: CupertinoActionSheetAction(
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
              child: const Text('Cancel'),
              onPressed: () {
                wasPressed = true;
                Navigator.pop(context);
              },
            ),
          );
        }),
      ),
    );

    await tester.tap(find.text('Go'));

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(wasPressed, isFalse);

    await tester.tap(find.text('Cancel'));

    expect(wasPressed, isTrue);

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(find.text('Cancel'), findsNothing);
  });

  testWidgets('Layout is correct when cancel button is present', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
720
        CupertinoActionSheet(
721 722 723
          title: const Text('The title'),
          message: const Text('The message'),
          actions: <Widget>[
724
            CupertinoActionSheetAction(
725
              child: const Text('One'),
726
              onPressed: () { },
727
            ),
728
            CupertinoActionSheetAction(
729
              child: const Text('Two'),
730
              onPressed: () { },
731 732
            ),
          ],
733
          cancelButton: CupertinoActionSheetAction(
734
            child: const Text('Cancel'),
735
            onPressed: () { },
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Cancel')).dy, 590.0);
    expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'One')).dy,
        moreOrLessEquals(469.66666666666663));
    expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Two')).dy, 526.0);
  });

  testWidgets('Enter/exit animation is correct', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
755
        CupertinoActionSheet(
756 757 758
          title: const Text('The title'),
          message: const Text('The message'),
          actions: <Widget>[
759
            CupertinoActionSheetAction(
760
              child: const Text('One'),
761
              onPressed: () { },
762
            ),
763
            CupertinoActionSheetAction(
764
              child: const Text('Two'),
765
              onPressed: () { },
766 767
            ),
          ],
768
          cancelButton: CupertinoActionSheetAction(
769
            child: const Text('Cancel'),
770
            onPressed: () { },
771 772 773 774 775 776 777 778 779 780 781 782
          ),
        ),
      ),
    );

    // Enter animation
    await tester.tap(find.text('Go'));

    await tester.pump();
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);

    await tester.pump(const Duration(milliseconds: 60));
783
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1));
784 785

    await tester.pump(const Duration(milliseconds: 60));
786
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1));
787 788

    await tester.pump(const Duration(milliseconds: 60));
789
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1));
790 791

    await tester.pump(const Duration(milliseconds: 60));
792
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(325.3, epsilon: 0.1));
793 794

    await tester.pump(const Duration(milliseconds: 60));
795
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(320.8, epsilon: 0.1));
796 797

    await tester.pump(const Duration(milliseconds: 60));
798
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(319.3, epsilon: 0.1));
799 800 801

    // Action sheet has reached final height
    await tester.pump(const Duration(milliseconds: 60));
802
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(319.3, epsilon: 0.1));
803 804 805 806

    // Exit animation
    await tester.tapAt(const Offset(20.0, 20.0));
    await tester.pump();
807
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(319.3, epsilon: 0.1));
808 809

    await tester.pump(const Duration(milliseconds: 60));
810
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(449.3, epsilon: 0.1));
811 812

    await tester.pump(const Duration(milliseconds: 60));
813
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(544.9, epsilon: 0.1));
814 815

    await tester.pump(const Duration(milliseconds: 60));
816
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(582.1, epsilon: 0.1));
817 818

    await tester.pump(const Duration(milliseconds: 60));
819
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(593.9, epsilon: 0.1));
820 821

    await tester.pump(const Duration(milliseconds: 60));
822
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(598.5, epsilon: 0.1));
823 824 825 826 827 828 829 830 831

    // Action sheet has disappeared
    await tester.pump(const Duration(milliseconds: 60));
    expect(find.byType(CupertinoActionSheet), findsNothing);
  });

  testWidgets('Modal barrier is pressed during transition', (WidgetTester tester) async {
    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
832
        CupertinoActionSheet(
833 834 835
          title: const Text('The title'),
          message: const Text('The message'),
          actions: <Widget>[
836
            CupertinoActionSheetAction(
837
              child: const Text('One'),
838
              onPressed: () { },
839
            ),
840
            CupertinoActionSheetAction(
841
              child: const Text('Two'),
842
              onPressed: () { },
843 844
            ),
          ],
845
          cancelButton: CupertinoActionSheetAction(
846
            child: const Text('Cancel'),
847
            onPressed: () { },
848 849 850 851 852 853 854 855 856 857 858 859
          ),
        ),
      ),
    );

    // Enter animation
    await tester.tap(find.text('Go'));

    await tester.pump();
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);

    await tester.pump(const Duration(milliseconds: 60));
860
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1));
861 862

    await tester.pump(const Duration(milliseconds: 60));
863
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1));
864 865

    await tester.pump(const Duration(milliseconds: 60));
866
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1));
867 868 869 870 871 872

    // Exit animation
    await tester.tapAt(const Offset(20.0, 20.0));
    await tester.pump(const Duration(milliseconds: 60));

    await tester.pump(const Duration(milliseconds: 60));
873
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1));
874 875

    await tester.pump(const Duration(milliseconds: 60));
876
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1));
877 878 879 880 881 882 883 884 885 886 887

    await tester.pump(const Duration(milliseconds: 60));
    expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);

    // Action sheet has disappeared
    await tester.pump(const Duration(milliseconds: 60));
    expect(find.byType(CupertinoActionSheet), findsNothing);
  });


  testWidgets('Action sheet semantics', (WidgetTester tester) async {
888
    final SemanticsTester semantics = SemanticsTester(tester);
889 890 891

    await tester.pumpWidget(
      createAppWithButtonThatLaunchesActionSheet(
892
        CupertinoActionSheet(
893 894 895
          title: const Text('The title'),
          message: const Text('The message'),
          actions: <Widget>[
896
            CupertinoActionSheetAction(
897
              child: const Text('One'),
898
              onPressed: () { },
899
            ),
900
            CupertinoActionSheetAction(
901
              child: const Text('Two'),
902
              onPressed: () { },
903 904
            ),
          ],
905
          cancelButton: CupertinoActionSheetAction(
906
            child: const Text('Cancel'),
907
            onPressed: () { },
908 909 910 911 912 913 914 915 916 917 918
          ),
        ),
      ),
    );

    await tester.tap(find.text('Go'));
    await tester.pump();

    expect(
      semantics,
      hasSemantics(
919
        TestSemantics.root(
920
          children: <TestSemantics>[
921
            TestSemantics(
922
              children: <TestSemantics>[
923
                TestSemantics(
924
                  children: <TestSemantics>[
925
                    TestSemantics(
926
                      flags: <SemanticsFlag>[
927 928
                        SemanticsFlag.scopesRoute,
                        SemanticsFlag.namesRoute,
929
                      ],
930
                      label: 'Alert',
931
                      children: <TestSemantics>[
932
                        TestSemantics(
933 934 935 936 937 938 939 940 941 942 943
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasImplicitScrolling,
                          ],
                          children: <TestSemantics>[
                            TestSemantics(
                              label: 'The title',
                            ),
                            TestSemantics(
                              label: 'The message',
                            ),
                          ],
944
                        ),
945
                        TestSemantics(
946
                          flags: <SemanticsFlag>[
947
                            SemanticsFlag.hasImplicitScrolling,
948
                          ],
949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
                          children: <TestSemantics>[
                            TestSemantics(
                              flags: <SemanticsFlag>[
                                SemanticsFlag.isButton,
                              ],
                              actions: <SemanticsAction>[
                                SemanticsAction.tap,
                              ],
                              label: 'One',
                            ),
                            TestSemantics(
                              flags: <SemanticsFlag>[
                                SemanticsFlag.isButton,
                              ],
                              actions: <SemanticsAction>[
                                SemanticsAction.tap,
                              ],
                              label: 'Two',
                            ),
968 969
                          ],
                        ),
970
                        TestSemantics(
971 972 973 974 975 976
                          flags: <SemanticsFlag>[
                            SemanticsFlag.isButton,
                          ],
                          actions: <SemanticsAction>[
                            SemanticsAction.tap,
                          ],
977
                          label: 'Cancel',
978 979 980
                        ),
                      ],
                    ),
981
                  ]
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
                ),
              ],
            ),
          ],
        ),
        ignoreId: true,
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    semantics.dispose();
  });
}

RenderBox findScrollableActionsSectionRenderBox(WidgetTester tester) {
  final RenderObject actionsSection = tester.renderObject(find.byElementPredicate(
    (Element element) {
      return element.widget.runtimeType.toString() == '_CupertinoAlertActionSection';
    }),
  );
  assert(actionsSection is RenderBox);
1004
  return actionsSection as RenderBox;
1005 1006 1007
}

Widget createAppWithButtonThatLaunchesActionSheet(Widget actionSheet) {
1008 1009 1010 1011
  return CupertinoApp(
    home: Center(
      child: Builder(builder: (BuildContext context) {
        return CupertinoButton(
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
          onPressed: () {
            showCupertinoModalPopup<void>(
              context: context,
              builder: (BuildContext context) {
                return actionSheet;
              },
            );
          },
          child: const Text('Go'),
        );
      }),
    ),
  );
}

Widget boilerplate(Widget child) {
1028
  return Directionality(
1029 1030 1031
    textDirection: TextDirection.ltr,
    child: child,
  );
1032
}