blob: 526419c632d97fcdfc51d93da8d3a1108e1655cc [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
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';
void main() {
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(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
tooltip: 'Add',
child: 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(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
tooltip: 'Add',
child: 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(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
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(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
tooltip: 'Add',
),
),
),
);
expect(find.text('Add'), findsNothing);
await tester.longPress(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
expect(find.text('Add'), findsOneWidget);
});
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(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(onPressed: null),
),
),
);
final Finder fabFinder = find.byType(FloatingActionButton);
FloatingActionButton getFabWidget() {
return tester.widget<FloatingActionButton>(fabFinder);
}
expect(getFabWidget().isExtended, false);
expect(getFabWidget().shape, const CircleBorder());
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton.extended(
label: const SizedBox(
width: 100.0,
child: Text('label'),
),
icon: const Icon(Icons.android),
onPressed: null,
),
),
),
);
expect(getFabWidget().isExtended, true);
expect(getFabWidget().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('Floating Action Button heroTag', (WidgetTester tester) async {
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 {
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 {
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.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
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 label', (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(
flags: <SemanticsFlag>[
SemanticsFlag.scopesRoute,
],
children: <TestSemantics>[
TestSemantics(
label: 'Add Photo',
actions: <SemanticsAction>[
SemanticsAction.tap
],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
),
],
),
],
),
],
), 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: RaisedButton(
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(
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.1.png'),
skip: !Platform.isLinux,
);
});
testWidgets('Floating Action Button has no clip by default', (WidgetTester tester) async{
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: FloatingActionButton(
onPressed: () { /* to make sure the button is enabled */ },
),
)
),
);
expect(
tester.renderObject(find.byType(FloatingActionButton)),
paintsExactlyCountTimes(#clipPath, 0)
);
});
}
Offset _rightEdgeOfFab(WidgetTester tester) {
final Finder fab = find.byType(FloatingActionButton);
return tester.getRect(fab).centerRight - const Offset(1.0, 0.0);
}