// 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. import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('ButtonThemeData defaults', () { const ButtonThemeData theme = ButtonThemeData(); expect(theme.textTheme, ButtonTextTheme.normal); expect(theme.constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0)); expect(theme.padding, const EdgeInsets.symmetric(horizontal: 16.0)); expect(theme.shape, const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(2.0)), )); expect(theme.alignedDropdown, false); expect(theme.layoutBehavior, ButtonBarLayoutBehavior.padded); }); test('ButtonThemeData default overrides', () { const ButtonThemeData theme = ButtonThemeData( textTheme: ButtonTextTheme.primary, minWidth: 100.0, height: 200.0, padding: EdgeInsets.zero, shape: RoundedRectangleBorder(), alignedDropdown: true, ); expect(theme.textTheme, ButtonTextTheme.primary); expect(theme.constraints, const BoxConstraints(minWidth: 100.0, minHeight: 200.0)); expect(theme.padding, EdgeInsets.zero); expect(theme.shape, const RoundedRectangleBorder()); expect(theme.alignedDropdown, true); }); testWidgets('ButtonTheme defaults', (WidgetTester tester) async { late ButtonTextTheme textTheme; late ButtonBarLayoutBehavior layoutBehavior; late BoxConstraints constraints; late EdgeInsets padding; late ShapeBorder shape; late bool alignedDropdown; late ColorScheme colorScheme; await tester.pumpWidget( ButtonTheme( child: Builder( builder: (BuildContext context) { final ButtonThemeData theme = ButtonTheme.of(context); textTheme = theme.textTheme; constraints = theme.constraints; padding = theme.padding as EdgeInsets; shape = theme.shape; layoutBehavior = theme.layoutBehavior; colorScheme = theme.colorScheme!; alignedDropdown = theme.alignedDropdown; return Container( alignment: Alignment.topLeft, child: Directionality( textDirection: TextDirection.ltr, child: FlatButton( onPressed: () { }, child: const Text('b'), // intrinsic width < minimum width ), ), ); }, ), ), ); expect(textTheme, ButtonTextTheme.normal); expect(layoutBehavior, ButtonBarLayoutBehavior.padded); expect(constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0)); expect(padding, const EdgeInsets.symmetric(horizontal: 16.0)); expect(shape, const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(2.0)), )); expect(alignedDropdown, false); expect(colorScheme, ThemeData.light().colorScheme); expect(tester.widget<Material>(find.byType(Material)).shape, shape); expect(tester.getSize(find.byType(Material)), const Size(88.0, 36.0)); }); test('ButtonThemeData.copyWith', () { ButtonThemeData theme = const ButtonThemeData().copyWith(); expect(theme.textTheme, ButtonTextTheme.normal); expect(theme.layoutBehavior, ButtonBarLayoutBehavior.padded); expect(theme.constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0)); expect(theme.padding, const EdgeInsets.symmetric(horizontal: 16.0)); expect(theme.shape, const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(2.0)), )); expect(theme.alignedDropdown, false); expect(theme.colorScheme, null); theme = const ButtonThemeData().copyWith( textTheme: ButtonTextTheme.primary, layoutBehavior: ButtonBarLayoutBehavior.constrained, minWidth: 100.0, height: 200.0, padding: EdgeInsets.zero, shape: const StadiumBorder(), alignedDropdown: true, colorScheme: const ColorScheme.dark(), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ); expect(theme.textTheme, ButtonTextTheme.primary); expect(theme.layoutBehavior, ButtonBarLayoutBehavior.constrained); expect(theme.constraints, const BoxConstraints(minWidth: 100.0, minHeight: 200.0)); expect(theme.padding, EdgeInsets.zero); expect(theme.shape, const StadiumBorder()); expect(theme.alignedDropdown, true); expect(theme.colorScheme, const ColorScheme.dark()); }); testWidgets('Theme buttonTheme defaults', (WidgetTester tester) async { final ThemeData lightTheme = ThemeData.light(); late ButtonTextTheme textTheme; late BoxConstraints constraints; late EdgeInsets padding; late ShapeBorder shape; const Color disabledColor = Color(0xFF00FF00); await tester.pumpWidget( Theme( data: lightTheme.copyWith( disabledColor: disabledColor, // disabled RaisedButton fill color buttonTheme: const ButtonThemeData(disabledColor: disabledColor), textTheme: lightTheme.textTheme.copyWith( button: lightTheme.textTheme.button!.copyWith( // The button's height will match because there's no // vertical padding by default fontSize: 48.0, ), ), ), child: Builder( builder: (BuildContext context) { final ButtonThemeData theme = ButtonTheme.of(context); textTheme = theme.textTheme; constraints = theme.constraints; padding = theme.padding as EdgeInsets; shape = theme.shape; return Container( alignment: Alignment.topLeft, child: const Directionality( textDirection: TextDirection.ltr, child: RaisedButton( onPressed: null, child: Text('b'), // intrinsic width < minimum width ), ), ); }, ), ), ); expect(textTheme, ButtonTextTheme.normal); expect(constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0)); expect(padding, const EdgeInsets.symmetric(horizontal: 16.0)); expect(shape, const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(2.0)), )); expect(tester.widget<Material>(find.byType(Material)).shape, shape); expect(tester.widget<Material>(find.byType(Material)).color, disabledColor); expect(tester.getSize(find.byType(Material)), const Size(88.0, 48.0)); }); testWidgets('Theme buttonTheme ButtonTheme overrides', (WidgetTester tester) async { late ButtonTextTheme textTheme; late BoxConstraints constraints; late EdgeInsets padding; late ShapeBorder shape; await tester.pumpWidget( Theme( data: ThemeData.light().copyWith( buttonColor: const Color(0xFF00FF00), // enabled RaisedButton fill color ), child: ButtonTheme( textTheme: ButtonTextTheme.primary, minWidth: 100.0, height: 200.0, padding: EdgeInsets.zero, buttonColor: const Color(0xFF00FF00), // enabled RaisedButton fill color shape: const RoundedRectangleBorder(), child: Builder( builder: (BuildContext context) { final ButtonThemeData theme = ButtonTheme.of(context); textTheme = theme.textTheme; constraints = theme.constraints; padding = theme.padding as EdgeInsets; shape = theme.shape; return Container( alignment: Alignment.topLeft, child: Directionality( textDirection: TextDirection.ltr, child: RaisedButton( onPressed: () { }, child: const Text('b'), // intrinsic width < minimum width ), ), ); }, ), ), ), ); expect(textTheme, ButtonTextTheme.primary); expect(constraints, const BoxConstraints(minWidth: 100.0, minHeight: 200.0)); expect(padding, EdgeInsets.zero); expect(shape, const RoundedRectangleBorder()); expect(tester.widget<Material>(find.byType(Material)).shape, shape); expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xFF00FF00)); expect(tester.getSize(find.byType(Material)), const Size(100.0, 200.0)); }); testWidgets('ButtonTheme alignedDropdown', (WidgetTester tester) async { final Key dropdownKey = UniqueKey(); Widget buildFrame({ required bool alignedDropdown, required TextDirection textDirection }) { return MaterialApp( builder: (BuildContext context, Widget? child) { return Directionality( textDirection: textDirection, child: child!, ); }, home: ButtonTheme( alignedDropdown: alignedDropdown, child: Material( child: Builder( builder: (BuildContext context) { return Container( alignment: Alignment.center, child: DropdownButtonHideUnderline( child: Container( width: 200.0, child: DropdownButton<String>( key: dropdownKey, onChanged: (String? value) { }, value: 'foo', items: const <DropdownMenuItem<String>>[ DropdownMenuItem<String>( value: 'foo', child: Text('foo'), ), DropdownMenuItem<String>( value: 'bar', child: Text('bar'), ), ], ), ), ), ); }, ), ), ), ); } final Finder button = find.byKey(dropdownKey); final Finder menu = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DropdownMenu<String>'); await tester.pumpWidget( buildFrame( alignedDropdown: false, textDirection: TextDirection.ltr, ), ); await tester.tap(button); await tester.pumpAndSettle(); // 240 = 200.0 (button width) + _kUnalignedMenuMargin (20.0 left and right) expect(tester.getSize(button).width, 200.0); expect(tester.getSize(menu).width, 240.0); // Dismiss the menu. await tester.tapAt(Offset.zero); await tester.pumpAndSettle(); expect(menu, findsNothing); await tester.pumpWidget( buildFrame( alignedDropdown: true, textDirection: TextDirection.ltr, ), ); await tester.tap(button); await tester.pumpAndSettle(); // Aligneddropdown: true means the button and menu widths match expect(tester.getSize(button).width, 200.0); expect(tester.getSize(menu).width, 200.0); // There are two 'foo' widgets: the selected menu item's label and the drop // down button's label. The should both appear at the same location. final Finder fooText = find.text('foo'); expect(fooText, findsNWidgets(2)); expect(tester.getRect(fooText.at(0)), tester.getRect(fooText.at(1))); // Dismiss the menu. await tester.tapAt(Offset.zero); await tester.pumpAndSettle(); expect(menu, findsNothing); // Same test as above except RTL await tester.pumpWidget( buildFrame( alignedDropdown: true, textDirection: TextDirection.rtl, ), ); await tester.tap(button); await tester.pumpAndSettle(); expect(fooText, findsNWidgets(2)); expect(tester.getRect(fooText.at(0)), tester.getRect(fooText.at(1))); }); testWidgets('button theme with stateful color changes color in states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); const Color pressedColor = Color(0x00000001); const Color hoverColor = Color(0x00000002); const Color focusedColor = Color(0x00000003); const Color defaultColor = Color(0x00000004); Color getTextColor(Set<MaterialState> states) { if (states.contains(MaterialState.pressed)) { return pressedColor; } if (states.contains(MaterialState.hovered)) { return hoverColor; } if (states.contains(MaterialState.focused)) { return focusedColor; } return defaultColor; } const ColorScheme colorScheme = ColorScheme.light(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: ButtonTheme( colorScheme: colorScheme.copyWith( primary: MaterialStateColor.resolveWith(getTextColor), ), textTheme: ButtonTextTheme.primary, child: FlatButton( child: const Text('FlatButton'), onPressed: () {}, focusNode: focusNode, ), ), ), ), ), ); Color textColor() { return tester.renderObject<RenderParagraph>(find.text('FlatButton')).text.style!.color!; } // Default, not disabled. expect(textColor(), equals(defaultColor)); // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); expect(textColor(), focusedColor); // Hovered. final Offset center = tester.getCenter(find.byType(FlatButton)); final TestGesture gesture = await tester.createGesture( kind: PointerDeviceKind.mouse, ); await gesture.addPointer(location: Offset.zero); addTearDown(gesture.removePointer); await gesture.moveTo(center); await tester.pumpAndSettle(); expect(textColor(), hoverColor); // 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(textColor(), pressedColor); }, semanticsEnabled: true, ); }