// 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 '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; void main() { final ThemeData material3Theme = ThemeData(useMaterial3: true); final ThemeData material2Theme = ThemeData(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); focusNode.dispose(); }); 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), ); focusNode.dispose(); }); 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( theme: material2Theme, 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( theme: material2Theme, 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', () { // These tests are only relevant for Material 2. Once Material 2 // support is deprecated and the APIs are removed, these tests // can be deleted. 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); focusNode.dispose(); }); 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); }