// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
@TestOn('!chrome')
library;

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';

import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';

void main() {

  final ThemeData material3Theme = ThemeData.light().copyWith(useMaterial3: true);
  final ThemeData material2Theme = ThemeData.light().copyWith(useMaterial3: false);

  testWidgets('Floating Action Button control test', (WidgetTester tester) async {
    bool didPressButton = false;
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: FloatingActionButton(
            onPressed: () {
              didPressButton = true;
            },
            child: const Icon(Icons.add),
          ),
        ),
      ),
    );

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

  testWidgets('Floating Action Button tooltip', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {},
            tooltip: 'Add',
            child: const Icon(Icons.add),
          ),
        ),
      ),
    );

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

  // 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(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {},
            tooltip: 'Add',
            child: const Icon(Icons.add),
          ),
        ),
      ),
    );

    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(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {},
            tooltip: 'Add',
          ),
        ),
      ),
    );

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

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

  testWidgets('Floating Action Button tooltip (no child)', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {},
            tooltip: 'Add',
          ),
        ),
      ),
    );

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

    // Test hover for tooltip.
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    addTearDown(() => gesture.removePointer());
    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.
    await tester.longPress(find.byType(FloatingActionButton));
    await tester.pumpAndSettle();

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

  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.
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    addTearDown(() => gesture.removePointer());
    await tester.pumpAndSettle();
    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.
    await tester.longPress(find.byType(FloatingActionButton));
    await tester.pumpAndSettle();

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

  testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        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, 6.0);
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
            highlightElevation: 20.0,
          ),
        ),
      ),
    );
    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, 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 {
    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);
  });

  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(
        theme: material3Theme,
        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, 6.0);
    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        home: const Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: null,
          ),
        ),
      ),
    );
    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);
    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        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);
  });

  testWidgets('Floating Action Button states elevation', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode();

    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        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();
    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, 6);
  });

  testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
    final Key key1 = UniqueKey();
    await tester.pumpWidget(
      MaterialApp(
        home: Theme(
          data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
              key: key1,
              mini: true,
              onPressed: null,
            ),
          ),
        ),
      ),
    );

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

    final Key key2 = UniqueKey();
    await tester.pumpWidget(
      MaterialApp(
        home: Theme(
          data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
              key: key2,
              mini: true,
              onPressed: null,
            ),
          ),
        ),
      ),
    );

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

  testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        home: const Scaffold(
          floatingActionButton: FloatingActionButton(onPressed: null),
        ),
      ),
    );

    final Finder fabFinder = find.byType(FloatingActionButton);

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

    final Finder materialButtonFinder = find.byType(RawMaterialButton);

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

    expect(getFabWidget().isExtended, false);
    expect(
      getRawMaterialButtonWidget().shape,
      const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))
    );

    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.extended(
            label: SizedBox(
              width: 100.0,
              child: Text('label'),
            ),
            icon: Icon(Icons.android),
            onPressed: null,
          ),
        ),
      ),
    );

    expect(getFabWidget().isExtended, true);
    expect(
      getRawMaterialButtonWidget().shape,
      const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))
    );
    expect(find.text('label'), findsOneWidget);
    expect(find.byType(Icon), findsOneWidget);

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

    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);

    // 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);
  });

  testWidgets('FloatingActionButton.isExtended (without icon)', (WidgetTester tester) async {
    final Finder fabFinder = find.byType(FloatingActionButton);

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

    final Finder materialButtonFinder = find.byType(RawMaterialButton);

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

    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        home: const Scaffold(
          floatingActionButton: FloatingActionButton.extended(
            label: SizedBox(
              width: 100.0,
              child: Text('label'),
            ),
            onPressed: null,
          ),
        ),
      ),
    );

    expect(getFabWidget().isExtended, true);
    expect(
        getRawMaterialButtonWidget().shape,
        const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))
    );
    expect(find.text('label'), findsOneWidget);
    expect(find.byType(Icon), findsNothing);

    // Verify that the widget's height is 56 and that its internal
    /// horizontal layout is: 20 label 20
    expect(tester.getSize(fabFinder).height, 56.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);
  });

  testWidgets('Floating Action Button heroTag', (WidgetTester tester) async {
    late BuildContext theContext;
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Builder(
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(heroTag: 1, onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(heroTag: 2, onPressed: null),
        ),
      ),
    );
    Navigator.push(theContext, PageRouteBuilder<void>(
      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 {
    late BuildContext theContext;
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Builder(
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(onPressed: null),
        ),
      ),
    );
    Navigator.push(theContext, PageRouteBuilder<void>(
      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 {
    late BuildContext theContext;
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Builder(
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(heroTag: 'xyzzy', onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(heroTag: 'xyzzy', onPressed: null),
        ),
      ),
    );
    Navigator.push(theContext, PageRouteBuilder<void>(
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump();
    expect(tester.takeException().toString(), contains('xyzzy'));
  });

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

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: FloatingActionButton(
            onPressed: () { },
            child: const Icon(Icons.add, semanticLabel: 'Add'),
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(TestSemantics.root(
      children: <TestSemantics>[
        TestSemantics.rootChild(
          label: 'Add',
          flags: <SemanticsFlag>[
            SemanticsFlag.hasEnabledState,
            SemanticsFlag.isButton,
            SemanticsFlag.isEnabled,
            SemanticsFlag.isFocusable,
          ],
          actions: <SemanticsAction>[
            SemanticsAction.tap,
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

  testWidgets('Floating Action Button semantics (disabled)', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: FloatingActionButton(
            onPressed: null,
            child: Icon(Icons.add, semanticLabel: 'Add'),
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(TestSemantics.root(
      children: <TestSemantics>[
        TestSemantics.rootChild(
          label: 'Add',
          flags: <SemanticsFlag>[
            SemanticsFlag.isButton,
            SemanticsFlag.hasEnabledState,
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

  testWidgets('Tooltip is used as semantics tooltip', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () { },
            tooltip: 'Add Photo',
            child: const Icon(Icons.add_a_photo),
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(TestSemantics.root(
      children: <TestSemantics>[
        TestSemantics.rootChild(
          children: <TestSemantics>[
            TestSemantics(
              children: <TestSemantics>[
                TestSemantics(
                  flags: <SemanticsFlag>[
                    SemanticsFlag.scopesRoute,
                  ],
                  children: <TestSemantics>[
                    TestSemantics(
                      tooltip: 'Add Photo',
                      actions: <SemanticsAction>[
                        SemanticsAction.tap,
                      ],
                      flags: <SemanticsFlag>[
                        SemanticsFlag.hasEnabledState,
                        SemanticsFlag.isButton,
                        SemanticsFlag.isEnabled,
                        SemanticsFlag.isFocusable,
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

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

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: Builder(
            builder: (BuildContext context) { // define context of Navigator.push()
              return FloatingActionButton.extended(
                icon: const Icon(Icons.add),
                label: const Text('A long FAB label'),
                onPressed: () {
                  Navigator.push(context, MaterialPageRoute<void>(
                    builder: (BuildContext context) {
                      return Scaffold(
                        floatingActionButton: FloatingActionButton.extended(
                          icon: const Icon(Icons.add),
                          label: const Text('X'),
                          onPressed: () { },
                        ),
                        body: Center(
                          child: ElevatedButton(
                            child: const Text('POP'),
                            onPressed: () {
                              Navigator.pop(context);
                            },
                          ),
                        ),
                      );
                    },
                  ));
                },
              );
            },
          ),
          body: const Center(
            child: Text('Hello World'),
          ),
        ),
      ),
    );

    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);
  });

  // This test prevents https://github.com/flutter/flutter/issues/20483
  testWidgets('Floating Action Button clips ink splash and highlight', (WidgetTester tester) async {
    final GlobalKey key = GlobalKey();
    await tester.pumpWidget(
      MaterialApp(
        theme: material3Theme,
        home: Scaffold(
          body: Center(
            child: RepaintBoundary(
              key: key,
              child: FloatingActionButton(
                onPressed: () { },
                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),
      matchesGoldenFile('floating_action_button_test.clip.png'),
    );
  });

  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)));

    await tester.pump();

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);

    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)));
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);

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

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);

    // 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),
            ),
          ),
        ),
      ),
    );

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
  });

  testWidgets('Floating Action Button has no clip by default', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode();
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: FloatingActionButton(
          focusNode: focusNode,
          onPressed: () { /* to make sure the button is enabled */ },
        ),
      ),
    );

    focusNode.unfocus();
    await tester.pump();

    expect(
      tester.renderObject(find.byType(FloatingActionButton)),
      paintsExactlyCountTimes(#clipPath, 0),
    );
  });

  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,
        isFocusable: true,
      ),
    );
  });

  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)),
    );
    expect(iconRichText.text.style!.color, foregroundColor);
  });

  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),
    );
  });

  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);
  });

  testWidgets('FloatingActionButton.small configures correct size', (WidgetTester tester) async {
    final Key key = UniqueKey();
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.small(
            key: key,
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            onPressed: null,
          ),
        ),
      ),
    );

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

  testWidgets('FloatingActionButton.large configures correct size', (WidgetTester tester) async {
    final Key key = UniqueKey();
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.large(
            key: key,
            onPressed: null,
          ),
        ),
      ),
    );

    expect(tester.getSize(find.byKey(key)), const Size(96.0, 96.0));
  });

  testWidgets('FloatingActionButton.extended can customize spacing', (WidgetTester tester) async {
    const Key iconKey = Key('icon');
    const Key labelKey = Key('label');
    const double spacing = 33.0;
    const EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(start: 5.0, end: 6.0);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.extended(
            label: const Text('', key: labelKey),
            icon: const Icon(Icons.add, key: iconKey),
            extendedIconLabelSpacing: spacing,
            extendedPadding: padding,
            onPressed: () {},
          ),
        ),
      ),
    );

    expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, spacing);
    expect(tester.getTopLeft(find.byKey(iconKey)).dx - tester.getTopLeft(find.byType(FloatingActionButton)).dx, padding.start);
    expect(tester.getTopRight(find.byType(FloatingActionButton)).dx - tester.getTopRight(find.byKey(labelKey)).dx, padding.end);
  });

  testWidgets('FloatingActionButton.extended can customize text style', (WidgetTester tester) async {
    const Key labelKey = Key('label');
    const TextStyle style = TextStyle(letterSpacing: 2.0);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.extended(
            label: const Text('', key: labelKey),
            icon: const Icon(Icons.add),
            extendedTextStyle: style,
            onPressed: () {},
          ),
        ),
      ),
    );

    final RawMaterialButton rawMaterialButton = tester.widget<RawMaterialButton>(
      find.descendant(
        of: find.byType(FloatingActionButton),
        matching: find.byType(RawMaterialButton),
      ),
    );
    // The color comes from the default color scheme's onSecondary value.
    expect(rawMaterialButton.textStyle, style.copyWith(color: const Color(0xffffffff)));
  });

  group('Material 2', () {
    // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
    // is turned on by default, these tests can be removed.

    testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          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(
          theme: material2Theme,
          home: Scaffold(
            floatingActionButton: FloatingActionButton(
              onPressed: () { },
              highlightElevation: 20.0,
            ),
          ),
        ),
      );
      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 while highlighted - effect', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          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(
        MaterialApp(
          theme: material2Theme,
          home: const 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(
          theme: material2Theme,
          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);
    });

    testWidgets('Floating Action Button states elevation', (WidgetTester tester) async {
      final FocusNode focusNode = FocusNode();

      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          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();
      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);
    });

    testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          home: const Scaffold(
            floatingActionButton: FloatingActionButton(onPressed: null),
          ),
        ),
      );

      final Finder fabFinder = find.byType(FloatingActionButton);

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

      final Finder materialButtonFinder = find.byType(RawMaterialButton);

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

      expect(getFabWidget().isExtended, false);
      expect(getRawMaterialButtonWidget().shape, const CircleBorder());

      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          home: const Scaffold(
            floatingActionButton: FloatingActionButton.extended(
              label: SizedBox(
                width: 100.0,
                child: Text('label'),
              ),
              icon: Icon(Icons.android),
              onPressed: null,
            ),
          ),
        ),
      );

      expect(getFabWidget().isExtended, true);
      expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
      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);

      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);

      // 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);
    });

    testWidgets('FloatingActionButton.isExtended (without icon)', (WidgetTester tester) async {
      final Finder fabFinder = find.byType(FloatingActionButton);

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

      final Finder materialButtonFinder = find.byType(RawMaterialButton);

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

      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          home: const Scaffold(
            floatingActionButton: FloatingActionButton.extended(
              label: SizedBox(
                width: 100.0,
                child: Text('label'),
              ),
              onPressed: null,
            ),
          ),
        ),
      );

      expect(getFabWidget().isExtended, true);
      expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
      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);
    });


    // This test prevents https://github.com/flutter/flutter/issues/20483
    testWidgets('Floating Action Button clips ink splash and highlight', (WidgetTester tester) async {
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(
        MaterialApp(
          theme: material2Theme,
          home: Scaffold(
            body: Center(
              child: RepaintBoundary(
                key: key,
                child: FloatingActionButton(
                  onPressed: () { },
                  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),
        matchesGoldenFile('floating_action_button_test_m2.clip.png'),
      );
    });
  });

  group('feedback', () {
    late FeedbackTester feedback;

    setUp(() {
      feedback = FeedbackTester();
    });

    tearDown(() {
      feedback.dispose();
    });

    testWidgets('FloatingActionButton with enabled feedback', (WidgetTester tester) async {
      const bool enableFeedback = true;

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

      await tester.tap(find.byType(RawMaterialButton));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 1);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('FloatingActionButton with disabled feedback', (WidgetTester tester) async {
      const bool enableFeedback = false;

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

      await tester.tap(find.byType(RawMaterialButton));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 0);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('FloatingActionButton with enabled feedback by default', (WidgetTester tester) async {
      await tester.pumpWidget(MaterialApp(
        home: FloatingActionButton(
          onPressed: () {},
          child: const Icon(Icons.access_alarm),
        ),
      ));

      await tester.tap(find.byType(RawMaterialButton));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 1);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('FloatingActionButton with disabled feedback using FloatingActionButtonTheme', (WidgetTester tester) async {
      const bool enableFeedbackTheme = false;
      final ThemeData theme = ThemeData(
        floatingActionButtonTheme: const FloatingActionButtonThemeData(
          enableFeedback: enableFeedbackTheme,
        ),
      );

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

      await tester.tap(find.byType(RawMaterialButton));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 0);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('FloatingActionButton.enableFeedback is overridden by FloatingActionButtonThemeData.enableFeedback', (WidgetTester tester) async {
      const bool enableFeedbackTheme = false;
      const bool enableFeedback = true;
      final ThemeData theme = ThemeData(
        floatingActionButtonTheme: const FloatingActionButtonThemeData(
          enableFeedback: enableFeedbackTheme,
        ),
      );

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

      await tester.tap(find.byType(RawMaterialButton));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 1);
      expect(feedback.hapticCount, 0);
    });
  });
}

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