blob: 7cda152c9e05718ef2be1a68be02141f551ee82b [file] [log] [blame]
// 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 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';
void main() {
final ThemeData material3Theme = ThemeData(useMaterial3: true);
final ThemeData material2Theme = ThemeData(useMaterial3: false);
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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
testWidgetsWithLeakTracking('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
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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();
});
testWidgetsWithLeakTracking('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));
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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).
});
testWidgetsWithLeakTracking('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'));
});
testWidgetsWithLeakTracking('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'));
});
testWidgetsWithLeakTracking('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();
});
testWidgetsWithLeakTracking('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();
});
testWidgetsWithLeakTracking('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'),
);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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();
});
testWidgetsWithLeakTracking('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,
),
);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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),
);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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));
});
testWidgetsWithLeakTracking('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));
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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.
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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();
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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();
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
});
testWidgetsWithLeakTracking('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);
}