floating_action_button_test.dart 30.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
@TestOn('!chrome')
6 7
import 'dart:ui';

8
import 'package:flutter/material.dart';
9
import 'package:flutter/rendering.dart';
10 11
import 'package:flutter_test/flutter_test.dart';

12
import '../rendering/mock_canvas.dart';
13 14
import '../widgets/semantics_tester.dart';

15
void main() {
Ian Hickson's avatar
Ian Hickson committed
16
  testWidgets('Floating Action Button control test', (WidgetTester tester) async {
17 18
    bool didPressButton = false;
    await tester.pumpWidget(
19
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
20
        textDirection: TextDirection.ltr,
21 22
        child: Center(
          child: FloatingActionButton(
Ian Hickson's avatar
Ian Hickson committed
23 24 25 26 27
            onPressed: () {
              didPressButton = true;
            },
            child: const Icon(Icons.add),
          ),
28 29
        ),
      ),
30 31 32 33 34 35
    );

    expect(didPressButton, isFalse);
    await tester.tap(find.byType(Icon));
    expect(didPressButton, isTrue);
  });
36 37 38

  testWidgets('Floating Action Button tooltip', (WidgetTester tester) async {
    await tester.pumpWidget(
39
      MaterialApp(
40
        home: Scaffold(
41
          floatingActionButton: FloatingActionButton(
42
            onPressed: () {},
43
            tooltip: 'Add',
44
            child: const Icon(Icons.add),
45 46 47 48 49 50 51 52
          ),
        ),
      ),
    );

    await tester.tap(find.byType(Icon));
    expect(find.byTooltip('Add'), findsOneWidget);
  });
53

54 55 56
  // Regression test for: https://github.com/flutter/flutter/pull/21084
  testWidgets('Floating Action Button tooltip (long press button edge)', (WidgetTester tester) async {
    await tester.pumpWidget(
57
      MaterialApp(
58
        home: Scaffold(
59
          floatingActionButton: FloatingActionButton(
60
            onPressed: () {},
61
            tooltip: 'Add',
62
            child: const Icon(Icons.add),
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
          ),
        ),
      ),
    );

    expect(find.text('Add'), findsNothing);
    await tester.longPressAt(_rightEdgeOfFab(tester));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);
  });

  // Regression test for: https://github.com/flutter/flutter/pull/21084
  testWidgets('Floating Action Button tooltip (long press button edge - no child)', (WidgetTester tester) async {
    await tester.pumpWidget(
78
      MaterialApp(
79
        home: Scaffold(
80
          floatingActionButton: FloatingActionButton(
81
            onPressed: () {},
82 83 84 85 86 87 88 89 90 91 92 93 94
            tooltip: 'Add',
          ),
        ),
      ),
    );

    expect(find.text('Add'), findsNothing);
    await tester.longPressAt(_rightEdgeOfFab(tester));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);
  });

95 96
  testWidgets('Floating Action Button tooltip (no child)', (WidgetTester tester) async {
    await tester.pumpWidget(
97
      MaterialApp(
98
        home: Scaffold(
99
          floatingActionButton: FloatingActionButton(
100
            onPressed: () {},
101 102 103 104 105 106
            tooltip: 'Add',
          ),
        ),
      ),
    );

107
    expect(find.text('Add'), findsNothing);
108 109

    // Test hover for tooltip.
110 111 112
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    addTearDown(() => gesture.removePointer());
113 114 115 116 117 118 119 120 121 122 123
    await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton)));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);

    await gesture.moveTo(Offset.zero);
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsNothing);

    // Test long press for tooltip.
124
    await tester.longPress(find.byType(FloatingActionButton));
125
    await tester.pumpAndSettle();
126

127
    expect(find.text('Add'), findsOneWidget);
128 129
  });

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
  testWidgets('Floating Action Button tooltip reacts when disabled', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
            tooltip: 'Add',
          ),
        ),
      ),
    );

    expect(find.text('Add'), findsNothing);

    // Test hover for tooltip.
145 146 147
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    addTearDown(() => gesture.removePointer());
148
    await tester.pumpAndSettle();
149 150 151 152 153 154
    await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton)));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);

    await gesture.moveTo(Offset.zero);
155 156 157 158 159 160 161 162 163 164 165
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsNothing);

    // Test long press for tooltip.
    await tester.longPress(find.byType(FloatingActionButton));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);
  });

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
  testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
          ),
        ),
      ),
    );
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    final TestGesture gesture = await tester.press(find.byType(PhysicalShape));
    await tester.pump();
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
187
            highlightElevation: 20.0,
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
          ),
        ),
      ),
    );
    await tester.pump();
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
    await gesture.up();
    await tester.pump();
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
  });

  testWidgets('Floating Action Button elevation when disabled - defaults', (WidgetTester tester) async {
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
          ),
        ),
      ),
    );

    // Disabled elevation defaults to regular default elevation.
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
  });

  testWidgets('Floating Action Button elevation when disabled - override', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
            disabledElevation: 0,
          ),
        ),
      ),
    );

    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 0.0);
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
  });

  testWidgets('Floating Action Button elevation when disabled - effect', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
          ),
        ),
      ),
    );
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
            disabledElevation: 3.0,
          ),
        ),
      ),
    );
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 3.0);
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
            disabledElevation: 3.0,
          ),
        ),
      ),
    );
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 3.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
  });

  testWidgets('Floating Action Button elevation when disabled while highlighted - effect', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
          ),
        ),
      ),
    );
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.press(find.byType(PhysicalShape));
    await tester.pump();
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
          ),
        ),
      ),
    );
    await tester.pump();
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
          ),
        ),
      ),
    );
    await tester.pump();
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
    await tester.pump(const Duration(seconds: 1));
    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
  });

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 346 347 348 349 350 351 352 353 354 355 356 357 358 359
  testWidgets('Floating Action Button states elevation', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode();

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: FloatingActionButton.extended(
            label: const Text('tooltip'),
            onPressed: () {},
            focusNode: focusNode,
          ),
        ),
      ),
    );

    final Finder fabFinder = find.byType(PhysicalShape);
    PhysicalShape getFABWidget(Finder finder) => tester.widget<PhysicalShape>(finder);

    // Default, not disabled.
    expect(getFABWidget(fabFinder).elevation, 6);

    // Focused.
    focusNode.requestFocus();
    await tester.pumpAndSettle();
    expect(getFABWidget(fabFinder).elevation, 6);

    // Hovered.
    final Offset center = tester.getCenter(fabFinder);
    final TestGesture gesture = await tester.createGesture(
      kind: PointerDeviceKind.mouse,
    );
    await gesture.addPointer();
    addTearDown(gesture.removePointer);
    await gesture.moveTo(center);
    await tester.pumpAndSettle();
    expect(getFABWidget(fabFinder).elevation, 8);

    // Highlighted (pressed).
    await gesture.down(center);
    await tester.pump(); // Start the splash and highlight animations.
    await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
    expect(getFABWidget(fabFinder).elevation, 12);
  });

360
  testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
361
    final Key key1 = UniqueKey();
362
    await tester.pumpWidget(
363 364 365 366 367
      MaterialApp(
        home: Theme(
          data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
368 369 370 371 372 373 374 375 376 377 378
              key: key1,
              mini: true,
              onPressed: null,
            ),
          ),
        ),
      ),
    );

    expect(tester.getSize(find.byKey(key1)), const Size(48.0, 48.0));

379
    final Key key2 = UniqueKey();
380
    await tester.pumpWidget(
381 382 383 384 385
      MaterialApp(
        home: Theme(
          data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
386 387 388 389 390 391 392 393 394 395 396 397
              key: key2,
              mini: true,
              onPressed: null,
            ),
          ),
        ),
      ),
    );

    expect(tester.getSize(find.byKey(key2)), const Size(40.0, 40.0));
  });

398 399
  testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
    await tester.pumpWidget(
400 401
      const MaterialApp(
        home: Scaffold(
402
          floatingActionButton: FloatingActionButton(onPressed: null),
403 404 405 406 407 408 409 410 411 412
        ),
      ),
    );

    final Finder fabFinder = find.byType(FloatingActionButton);

    FloatingActionButton getFabWidget() {
      return tester.widget<FloatingActionButton>(fabFinder);
    }

413 414 415 416 417 418
    final Finder materialButtonFinder = find.byType(RawMaterialButton);

    RawMaterialButton getRawMaterialButtonWidget() {
      return tester.widget<RawMaterialButton>(materialButtonFinder);
    }

419
    expect(getFabWidget().isExtended, false);
420
    expect(getRawMaterialButtonWidget().shape, const CircleBorder());
421 422

    await tester.pumpWidget(
423 424 425
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.extended(
426 427
            label: const SizedBox(
              width: 100.0,
428
              child: Text('label'),
429
            ),
430 431 432 433 434 435 436 437
            icon: const Icon(Icons.android),
            onPressed: null,
          ),
        ),
      ),
    );

    expect(getFabWidget().isExtended, true);
438
    expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
439 440 441 442 443 444
    expect(find.text('label'), findsOneWidget);
    expect(find.byType(Icon), findsOneWidget);

    // Verify that the widget's height is 48 and that its internal
    /// horizontal layout is: 16 icon 8 label 20
    expect(tester.getSize(fabFinder).height, 48.0);
445

446 447 448 449 450 451 452 453 454
    final double fabLeft = tester.getTopLeft(fabFinder).dx;
    final double fabRight = tester.getTopRight(fabFinder).dx;
    final double iconLeft = tester.getTopLeft(find.byType(Icon)).dx;
    final double iconRight = tester.getTopRight(find.byType(Icon)).dx;
    final double labelLeft = tester.getTopLeft(find.text('label')).dx;
    final double labelRight = tester.getTopRight(find.text('label')).dx;
    expect(iconLeft - fabLeft, 16.0);
    expect(labelLeft - iconRight, 8.0);
    expect(fabRight - labelRight, 20.0);
455 456 457 458 459 460

    // The overall width of the button is:
    // 168 = 16 + 24(icon) + 8 + 100(label) + 20
    expect(tester.getSize(find.byType(Icon)).width, 24.0);
    expect(tester.getSize(find.text('label')).width, 100.0);
    expect(tester.getSize(fabFinder).width, 168);
461 462
  });

463 464 465 466 467 468 469
  testWidgets('FloatingActionButton.isExtended (without icon)', (WidgetTester tester) async {
    final Finder fabFinder = find.byType(FloatingActionButton);

    FloatingActionButton getFabWidget() {
      return tester.widget<FloatingActionButton>(fabFinder);
    }

470 471 472 473 474 475
    final Finder materialButtonFinder = find.byType(RawMaterialButton);

    RawMaterialButton getRawMaterialButtonWidget() {
      return tester.widget<RawMaterialButton>(materialButtonFinder);
    }

476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.extended(
            label: const SizedBox(
              width: 100.0,
              child: Text('label'),
            ),
            onPressed: null,
          ),
        ),
      ),
    );

    expect(getFabWidget().isExtended, true);
491
    expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
    expect(find.text('label'), findsOneWidget);
    expect(find.byType(Icon), findsNothing);

    // Verify that the widget's height is 48 and that its internal
    /// horizontal layout is: 20 label 20
    expect(tester.getSize(fabFinder).height, 48.0);

    final double fabLeft = tester.getTopLeft(fabFinder).dx;
    final double fabRight = tester.getTopRight(fabFinder).dx;
    final double labelLeft = tester.getTopLeft(find.text('label')).dx;
    final double labelRight = tester.getTopRight(find.text('label')).dx;
    expect(labelLeft - fabLeft, 20.0);
    expect(fabRight - labelRight, 20.0);

    // The overall width of the button is:
    // 140 = 20 + 100(label) + 20
    expect(tester.getSize(find.text('label')).width, 100.0);
    expect(tester.getSize(fabFinder).width, 140);
  });

512
  testWidgets('Floating Action Button heroTag', (WidgetTester tester) async {
513
    late BuildContext theContext;
514
    await tester.pumpWidget(
515 516 517
      MaterialApp(
        home: Scaffold(
          body: Builder(
518 519 520 521 522 523 524 525 526
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(heroTag: 1, onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(heroTag: 2, onPressed: null),
        ),
      ),
    );
527
    Navigator.push(theContext, PageRouteBuilder<void>(
528 529 530 531 532 533 534 535
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump(); // this would fail if heroTag was the same on both FloatingActionButtons (see below).
  });

  testWidgets('Floating Action Button heroTag - with duplicate', (WidgetTester tester) async {
536
    late BuildContext theContext;
537
    await tester.pumpWidget(
538 539 540
      MaterialApp(
        home: Scaffold(
          body: Builder(
541 542 543 544 545 546 547 548 549
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(onPressed: null),
        ),
      ),
    );
550
    Navigator.push(theContext, PageRouteBuilder<void>(
551 552 553 554 555 556 557 558 559
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump();
    expect(tester.takeException().toString(), contains('FloatingActionButton'));
  });

  testWidgets('Floating Action Button heroTag - with duplicate', (WidgetTester tester) async {
560
    late BuildContext theContext;
561
    await tester.pumpWidget(
562 563 564
      MaterialApp(
        home: Scaffold(
          body: Builder(
565 566 567 568 569 570 571 572 573
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(heroTag: 'xyzzy', onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(heroTag: 'xyzzy', onPressed: null),
        ),
      ),
    );
574
    Navigator.push(theContext, PageRouteBuilder<void>(
575 576 577 578 579 580 581
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump();
    expect(tester.takeException().toString(), contains('xyzzy'));
  });
582 583

  testWidgets('Floating Action Button semantics (enabled)', (WidgetTester tester) async {
584
    final SemanticsTester semantics = SemanticsTester(tester);
585 586

    await tester.pumpWidget(
587
      Directionality(
588
        textDirection: TextDirection.ltr,
589 590
        child: Center(
          child: FloatingActionButton(
591 592 593 594 595 596 597
            onPressed: () { },
            child: const Icon(Icons.add, semanticLabel: 'Add'),
          ),
        ),
      ),
    );

598
    expect(semantics, hasSemantics(TestSemantics.root(
599
      children: <TestSemantics>[
600
        TestSemantics.rootChild(
601 602 603
          label: 'Add',
          flags: <SemanticsFlag>[
            SemanticsFlag.hasEnabledState,
604
            SemanticsFlag.isButton,
605
            SemanticsFlag.isEnabled,
606
            SemanticsFlag.isFocusable,
607 608
          ],
          actions: <SemanticsAction>[
609
            SemanticsAction.tap,
610 611 612 613 614 615 616 617 618
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

  testWidgets('Floating Action Button semantics (disabled)', (WidgetTester tester) async {
619
    final SemanticsTester semantics = SemanticsTester(tester);
620 621 622 623

    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
624 625
        child: Center(
          child: FloatingActionButton(
626
            onPressed: null,
627
            child: Icon(Icons.add, semanticLabel: 'Add'),
628 629 630 631 632
          ),
        ),
      ),
    );

633
    expect(semantics, hasSemantics(TestSemantics.root(
634
      children: <TestSemantics>[
635
        TestSemantics.rootChild(
636 637 638 639 640 641 642 643 644 645 646
          label: 'Add',
          flags: <SemanticsFlag>[
            SemanticsFlag.isButton,
            SemanticsFlag.hasEnabledState,
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });
647

648
  testWidgets('Tooltip is used as semantics label', (WidgetTester tester) async {
649
    final SemanticsTester semantics = SemanticsTester(tester);
650 651

    await tester.pumpWidget(
652 653 654
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
655 656 657 658 659 660 661 662
            onPressed: () { },
            tooltip: 'Add Photo',
            child: const Icon(Icons.add_a_photo),
          ),
        ),
      ),
    );

663
    expect(semantics, hasSemantics(TestSemantics.root(
664
      children: <TestSemantics>[
665
        TestSemantics.rootChild(
666
          children: <TestSemantics>[
667
            TestSemantics(
668
              children: <TestSemantics>[
669
                TestSemantics(
670
                  flags: <SemanticsFlag>[
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
                    SemanticsFlag.scopesRoute,
                  ],
                  children: <TestSemantics>[
                    TestSemantics(
                      label: 'Add Photo',
                      actions: <SemanticsAction>[
                        SemanticsAction.tap,
                      ],
                      flags: <SemanticsFlag>[
                        SemanticsFlag.hasEnabledState,
                        SemanticsFlag.isButton,
                        SemanticsFlag.isEnabled,
                        SemanticsFlag.isFocusable,
                      ],
                    ),
686 687
                  ],
                ),
688 689
              ],
            ),
690 691 692 693 694 695 696 697
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

698 699
  testWidgets('extended FAB hero transitions succeed', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/18782
700

701
    await tester.pumpWidget(
702 703 704
      MaterialApp(
        home: Scaffold(
          floatingActionButton: Builder(
705
            builder: (BuildContext context) { // define context of Navigator.push()
706
              return FloatingActionButton.extended(
707 708 709
                icon: const Icon(Icons.add),
                label: const Text('A long FAB label'),
                onPressed: () {
710
                  Navigator.push(context, MaterialPageRoute<void>(
711
                    builder: (BuildContext context) {
712 713
                      return Scaffold(
                        floatingActionButton: FloatingActionButton.extended(
714 715 716 717
                          icon: const Icon(Icons.add),
                          label: const Text('X'),
                          onPressed: () { },
                        ),
718
                        body: Center(
719
                          child: ElevatedButton(
720 721 722 723 724 725 726 727 728 729 730 731 732 733
                            child: const Text('POP'),
                            onPressed: () {
                              Navigator.pop(context);
                            },
                          ),
                        ),
                      );
                    },
                  ));
                },
              );
            },
          ),
          body: const Center(
734
            child: Text('Hello World'),
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
          ),
        ),
      ),
    );

    final Finder longFAB = find.text('A long FAB label');
    final Finder shortFAB = find.text('X');
    final Finder helloWorld = find.text('Hello World');

    expect(longFAB, findsOneWidget);
    expect(shortFAB, findsNothing);
    expect(helloWorld, findsOneWidget);

    await tester.tap(longFAB);
    await tester.pumpAndSettle();

    expect(shortFAB, findsOneWidget);
    expect(longFAB, findsNothing);

    // Trigger a hero transition from shortFAB to longFAB.
    await tester.tap(find.text('POP'));
    await tester.pumpAndSettle();

    expect(longFAB, findsOneWidget);
    expect(shortFAB, findsNothing);
    expect(helloWorld, findsOneWidget);
  });
762 763 764

  // This test prevents https://github.com/flutter/flutter/issues/20483
  testWidgets('Floating Action Button clips ink splash and highlight', (WidgetTester tester) async {
765
    final GlobalKey key = GlobalKey();
766
    await tester.pumpWidget(
767 768 769 770
      MaterialApp(
        home: Scaffold(
          body: Center(
            child: RepaintBoundary(
771
              key: key,
772
              child: FloatingActionButton(
773
                onPressed: () { },
774 775 776 777 778 779 780 781 782 783 784 785 786
                child: const Icon(Icons.add),
              ),
            ),
          ),
        ),
      ),
    );

    await tester.press(find.byKey(key));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 1000));
    await expectLater(
      find.byKey(key),
787
      matchesGoldenFile('floating_action_button_test.clip.png'),
788 789
    );
  });
790

791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
  testWidgets('Floating Action Button changes mouse cursor when hovered', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Align(
            alignment: Alignment.topLeft,
            child: FloatingActionButton.extended(
              onPressed: () { },
              mouseCursor: SystemMouseCursors.text,
              label: const Text('label'),
              icon: const Icon(Icons.android),
            ),
          ),
        ),
      ),
    );

    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
    await gesture.addPointer(location: tester.getCenter(find.byType(FloatingActionButton)));
    addTearDown(gesture.removePointer);

    await tester.pump();

814
    expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Align(
            alignment: Alignment.topLeft,
            child: FloatingActionButton(
              onPressed: () { },
              mouseCursor: SystemMouseCursors.text,
              child: const Icon(Icons.add),
            ),
          ),
        ),
      ),
    );

    await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton)));
832
    expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848

    // Test default cursor
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Align(
            alignment: Alignment.topLeft,
            child: FloatingActionButton(
              onPressed: () { },
              child: const Icon(Icons.add),
            ),
          ),
        ),
      ),
    );

849
    expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865

    // Test default cursor when disabled
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: Align(
            alignment: Alignment.topLeft,
            child: FloatingActionButton(
              onPressed: null,
              child: Icon(Icons.add),
            ),
          ),
        ),
      ),
    );

866
    expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
867 868
  });

869
  testWidgets('Floating Action Button has no clip by default', (WidgetTester tester) async {
870
    final FocusNode focusNode = FocusNode();
871
    await tester.pumpWidget(
872
      Directionality(
873 874 875 876 877
        textDirection: TextDirection.ltr,
        child: Material(
          child: FloatingActionButton(
            focusNode: focusNode,
            onPressed: () { /* to make sure the button is enabled */ },
878
          ),
879
        ),
880 881 882
      ),
    );

883 884 885
    focusNode.unfocus();
    await tester.pump();

886
    expect(
887 888
      tester.renderObject(find.byType(FloatingActionButton)),
      paintsExactlyCountTimes(#clipPath, 0),
889 890
    );
  });
891 892 893 894 895 896 897 898 899 900 901 902 903

  testWidgets('Can find FloatingActionButton semantics', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: FloatingActionButton(onPressed: () {}),
    ));

    expect(
      tester.getSemantics(find.byType(FloatingActionButton)),
      matchesSemantics(
        hasTapAction: true,
        hasEnabledState: true,
        isButton: true,
        isEnabled: true,
904
        isFocusable: true,
905 906 907
      ),
    );
  }, semanticsEnabled: true);
908 909 910 911 912 913 914 915 916 917 918 919 920 921 922

  testWidgets('Foreground color applies to icon on fab', (WidgetTester tester) async {
    const Color foregroundColor = Color(0xcafefeed);

    await tester.pumpWidget(MaterialApp(
      home: FloatingActionButton(
        onPressed: () {},
        foregroundColor: foregroundColor,
        child: const Icon(Icons.access_alarm),
      ),
    ));

    final RichText iconRichText = tester.widget<RichText>(
      find.descendant(of: find.byIcon(Icons.access_alarm), matching: find.byType(RichText)),
    );
923
    expect(iconRichText.text.style!.color, foregroundColor);
924
  });
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944

  testWidgets('FloatingActionButton uses custom splash color', (WidgetTester tester) async {
    const Color splashColor = Color(0xcafefeed);

    await tester.pumpWidget(MaterialApp(
      home: FloatingActionButton(
        onPressed: () {},
        splashColor: splashColor,
        child: const Icon(Icons.access_alarm),
      ),
    ));

    await tester.press(find.byType(FloatingActionButton));
    await tester.pumpAndSettle();

    expect(
      find.byType(FloatingActionButton),
      paints..circle(color: splashColor),
    );
  });
945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965

  testWidgets('extended FAB does not show label when isExtended is false', (WidgetTester tester) async {
    const Key iconKey = Key('icon');
    const Key labelKey = Key('label');

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: FloatingActionButton.extended(
          isExtended: false,
          label: const Text('', key: labelKey),
          icon: const Icon(Icons.add, key: iconKey),
          onPressed: () {},
        ),
      ),
    );

    // Verify that Icon is present and label is not.
    expect(find.byKey(iconKey), findsOneWidget);
    expect(find.byKey(labelKey), findsNothing);
  });
966
}
967 968 969 970 971

Offset _rightEdgeOfFab(WidgetTester tester) {
  final Finder fab = find.byType(FloatingActionButton);
  return tester.getRect(fab).centerRight - const Offset(1.0, 0.0);
}