| // 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']) |
| library; |
| |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../foundation/leak_tracking.dart'; |
| import '../rendering/mock_canvas.dart'; |
| import '../widgets/semantics_tester.dart'; |
| |
| const double _defaultBorderWidth = 1.0; |
| |
| Widget boilerplate({ |
| bool? useMaterial3, |
| MaterialTapTargetSize? tapTargetSize, |
| required Widget child, |
| }) { |
| return Theme( |
| data: ThemeData( |
| useMaterial3: useMaterial3, |
| materialTapTargetSize: tapTargetSize, |
| ), |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: Material(child: child), |
| ), |
| ), |
| ); |
| } |
| |
| void main() { |
| testWidgetsWithLeakTracking('Initial toggle state is reflected', (WidgetTester tester) async { |
| TextStyle buttonTextStyle(String text) { |
| return tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, text), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| } |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| onPressed: (int index) {}, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.87), |
| ); |
| expect( |
| buttonTextStyle('Second child').color, |
| theme.colorScheme.primary, |
| ); |
| }); |
| |
| testWidgetsWithLeakTracking( |
| 'onPressed is triggered on button tap', |
| (WidgetTester tester) async { |
| TextStyle buttonTextStyle(String text) { |
| return tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, text), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| } |
| final List<bool> isSelected = <bool>[false, true]; |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| StatefulBuilder( |
| builder: (BuildContext context, StateSetter setState) { |
| return boilerplate( |
| child: ToggleButtons( |
| onPressed: (int index) { |
| setState(() { |
| isSelected[index] = !isSelected[index]; |
| }); |
| }, |
| isSelected: isSelected, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ); |
| }, |
| ), |
| ); |
| |
| expect(isSelected[0], isFalse); |
| expect(isSelected[1], isTrue); |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.87), |
| ); |
| expect( |
| buttonTextStyle('Second child').color, |
| theme.colorScheme.primary, |
| ); |
| |
| await tester.tap(find.text('Second child')); |
| await tester.pumpAndSettle(); |
| |
| expect(isSelected[0], isFalse); |
| expect(isSelected[1], isFalse); |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.87), |
| ); |
| expect( |
| buttonTextStyle('Second child').color, |
| theme.colorScheme.onSurface.withOpacity(0.87), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking( |
| 'onPressed that is null disables buttons', |
| (WidgetTester tester) async { |
| TextStyle buttonTextStyle(String text) { |
| return tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, text), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| } |
| final List<bool> isSelected = <bool>[false, true]; |
| final ThemeData theme = ThemeData(); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: isSelected, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(isSelected[0], isFalse); |
| expect(isSelected[1], isTrue); |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.38), |
| ); |
| expect( |
| buttonTextStyle('Second child').color, |
| theme.colorScheme.onSurface.withOpacity(0.38), |
| ); |
| |
| await tester.tap(find.text('Second child')); |
| await tester.pumpAndSettle(); |
| |
| // Nothing should change |
| expect(isSelected[0], isFalse); |
| expect(isSelected[1], isTrue); |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.38), |
| ); |
| expect( |
| buttonTextStyle('Second child').color, |
| theme.colorScheme.onSurface.withOpacity(0.38), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking( |
| 'children and isSelected properties have to be the same length', |
| (WidgetTester tester) async { |
| await expectLater( |
| () => tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ), |
| throwsA(isAssertionError.having( |
| (AssertionError error) => error.toString(), |
| '.toString()', |
| allOf( |
| contains('children.length'), |
| contains('isSelected.length'), |
| ), |
| )), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking('Default text style is applied', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| TextStyle textStyle; |
| textStyle = tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, 'First child'), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| expect(textStyle.fontFamily, theme.textTheme.bodyMedium!.fontFamily); |
| expect(textStyle.decoration, theme.textTheme.bodyMedium!.decoration); |
| |
| textStyle = tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, 'Second child'), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| expect(textStyle.fontFamily, theme.textTheme.bodyMedium!.fontFamily); |
| expect(textStyle.decoration, theme.textTheme.bodyMedium!.decoration); |
| }); |
| |
| testWidgetsWithLeakTracking('Custom text style except color is applied', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true], |
| onPressed: (int index) {}, |
| textStyle: const TextStyle( |
| textBaseline: TextBaseline.ideographic, |
| fontSize: 20.0, |
| color: Colors.orange, |
| ), |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| TextStyle textStyle; |
| textStyle = tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, 'First child'), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| expect(textStyle.textBaseline, TextBaseline.ideographic); |
| expect(textStyle.fontSize, 20.0); |
| expect(textStyle.color, isNot(Colors.orange)); |
| |
| textStyle = tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, 'Second child'), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| expect(textStyle.textBaseline, TextBaseline.ideographic); |
| expect(textStyle.fontSize, 20.0); |
| expect(textStyle.color, isNot(Colors.orange)); |
| }); |
| |
| testWidgetsWithLeakTracking('Default BoxConstraints', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, false, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Icon(Icons.check), |
| Icon(Icons.access_alarm), |
| Icon(Icons.cake), |
| ], |
| ), |
| ), |
| ); |
| |
| final Rect firstRect = tester.getRect(find.byType(TextButton).at(0)); |
| expect(firstRect.width, 48.0); |
| expect(firstRect.height, 48.0); |
| final Rect secondRect = tester.getRect(find.byType(TextButton).at(1)); |
| expect(secondRect.width, 48.0); |
| expect(secondRect.height, 48.0); |
| final Rect thirdRect = tester.getRect(find.byType(TextButton).at(2)); |
| expect(thirdRect.width, 48.0); |
| expect(thirdRect.height, 48.0); |
| }); |
| |
| testWidgetsWithLeakTracking('Custom BoxConstraints', (WidgetTester tester) async { |
| // Test for minimum constraints |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| constraints: const BoxConstraints( |
| minWidth: 50.0, |
| minHeight: 60.0, |
| ), |
| isSelected: const <bool>[false, false, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Icon(Icons.check), |
| Icon(Icons.access_alarm), |
| Icon(Icons.cake), |
| ], |
| ), |
| ), |
| ); |
| |
| Rect firstRect = tester.getRect(find.byType(TextButton).at(0)); |
| expect(firstRect.width, 50.0); |
| expect(firstRect.height, 60.0); |
| Rect secondRect = tester.getRect(find.byType(TextButton).at(1)); |
| expect(secondRect.width, 50.0); |
| expect(secondRect.height, 60.0); |
| Rect thirdRect = tester.getRect(find.byType(TextButton).at(2)); |
| expect(thirdRect.width, 50.0); |
| expect(thirdRect.height, 60.0); |
| |
| // Test for maximum constraints |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| tapTargetSize: MaterialTapTargetSize.shrinkWrap, |
| constraints: const BoxConstraints( |
| maxWidth: 20.0, |
| maxHeight: 10.0, |
| ), |
| isSelected: const <bool>[false, false, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Icon(Icons.check), |
| Icon(Icons.access_alarm), |
| Icon(Icons.cake), |
| ], |
| ), |
| ), |
| ); |
| |
| firstRect = tester.getRect(find.byType(TextButton).at(0)); |
| expect(firstRect.width, 20.0); |
| expect(firstRect.height, 10.0); |
| secondRect = tester.getRect(find.byType(TextButton).at(1)); |
| expect(secondRect.width, 20.0); |
| expect(secondRect.height, 10.0); |
| thirdRect = tester.getRect(find.byType(TextButton).at(2)); |
| expect(thirdRect.width, 20.0); |
| expect(thirdRect.height, 10.0); |
| }); |
| |
| testWidgetsWithLeakTracking( |
| 'Default text/icon colors for enabled, selected and disabled states', |
| (WidgetTester tester) async { |
| TextStyle buttonTextStyle(String text) { |
| return tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, text), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| } |
| IconTheme iconTheme(IconData icon) { |
| return tester.widget(find.descendant( |
| of: find.widgetWithIcon(TextButton, icon), |
| matching: find.byType(IconTheme), |
| )); |
| } |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| Icon(Icons.check), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| |
| // Default enabled color |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.87), |
| ); |
| expect( |
| iconTheme(Icons.check).data.color, |
| theme.colorScheme.onSurface.withOpacity(0.87), |
| ); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| Icon(Icons.check), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| // Default selected color |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.primary, |
| ); |
| expect( |
| iconTheme(Icons.check).data.color, |
| theme.colorScheme.primary, |
| ); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true], |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| Icon(Icons.check), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| // Default disabled color |
| expect( |
| buttonTextStyle('First child').color, |
| theme.colorScheme.onSurface.withOpacity(0.38), |
| ); |
| expect( |
| iconTheme(Icons.check).data.color, |
| theme.colorScheme.onSurface.withOpacity(0.38), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking( |
| 'Custom text/icon colors for enabled, selected and disabled states', |
| (WidgetTester tester) async { |
| TextStyle buttonTextStyle(String text) { |
| return tester.widget<DefaultTextStyle>(find.descendant( |
| of: find.widgetWithText(TextButton, text), |
| matching: find.byType(DefaultTextStyle), |
| )).style; |
| } |
| IconTheme iconTheme(IconData icon) { |
| return tester.widget(find.descendant( |
| of: find.widgetWithIcon(TextButton, icon), |
| matching: find.byType(IconTheme), |
| )); |
| } |
| final ThemeData theme = ThemeData(); |
| const Color enabledColor = Colors.lime; |
| const Color selectedColor = Colors.green; |
| const Color disabledColor = Colors.yellow; |
| |
| // Tests are ineffective if the custom colors are the same as the theme's |
| expect(theme.colorScheme.onSurface, isNot(enabledColor)); |
| expect(theme.colorScheme.primary, isNot(selectedColor)); |
| expect(theme.colorScheme.onSurface.withOpacity(0.38), isNot(disabledColor)); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| color: enabledColor, |
| isSelected: const <bool>[false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| Icon(Icons.check), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| |
| // Custom enabled color |
| expect(buttonTextStyle('First child').color, enabledColor); |
| expect(iconTheme(Icons.check).data.color, enabledColor); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| selectedColor: selectedColor, |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| Icon(Icons.check), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| // Custom selected color |
| expect(buttonTextStyle('First child').color, selectedColor); |
| expect(iconTheme(Icons.check).data.color, selectedColor); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| disabledColor: disabledColor, |
| isSelected: const <bool>[true], |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| Icon(Icons.check), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| // Custom disabled color |
| expect(buttonTextStyle('First child').color, disabledColor); |
| expect(iconTheme(Icons.check).data.color, disabledColor); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking('Default button fillColor - unselected', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| |
| final Material material = tester.widget<Material>(find.descendant( |
| of: find.byType(TextButton), |
| matching: find.byType(Material), |
| )); |
| expect( |
| material.color, |
| theme.colorScheme.surface.withOpacity(0.0), |
| ); |
| expect(material.type, MaterialType.button); |
| }); |
| |
| testWidgetsWithLeakTracking('Default button fillColor - selected', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| |
| final Material material = tester.widget<Material>(find.descendant( |
| of: find.byType(TextButton), |
| matching: find.byType(Material), |
| )); |
| expect( |
| material.color, |
| theme.colorScheme.primary.withOpacity(0.12), |
| ); |
| expect(material.type, MaterialType.button); |
| }); |
| |
| testWidgetsWithLeakTracking('Default button fillColor - disabled', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true], |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| |
| final Material material = tester.widget<Material>(find.descendant( |
| of: find.byType(TextButton), |
| matching: find.byType(Material), |
| )); |
| expect( |
| material.color, |
| theme.colorScheme.surface.withOpacity(0.0), |
| ); |
| expect(material.type, MaterialType.button); |
| }); |
| |
| testWidgetsWithLeakTracking('Custom button fillColor', (WidgetTester tester) async { |
| const Color customFillColor = Colors.green; |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| fillColor: customFillColor, |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Row(children: <Widget>[ |
| Text('First child'), |
| ]), |
| ], |
| ), |
| ), |
| ); |
| |
| final Material material = tester.widget<Material>(find.descendant( |
| of: find.byType(TextButton), |
| matching: find.byType(Material), |
| )); |
| expect(material.color, customFillColor); |
| expect(material.type, MaterialType.button); |
| }); |
| |
| testWidgetsWithLeakTracking('Custom button fillColor - Non MaterialState', (WidgetTester tester) async { |
| Material buttonColor(String text) { |
| return tester.widget<Material>( |
| find.descendant( |
| of: find.byType(TextButton), |
| matching: find.widgetWithText(Material, text), |
| ), |
| ); |
| } |
| |
| final ThemeData theme = ThemeData(); |
| const Color selectedFillColor = Colors.yellow; |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| fillColor: selectedFillColor, |
| isSelected: const <bool>[false, true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(buttonColor('First child').color, theme.colorScheme.surface.withOpacity(0.0)); |
| expect(buttonColor('Second child').color, selectedFillColor); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| fillColor: selectedFillColor, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(buttonColor('First child').color, theme.colorScheme.surface.withOpacity(0.0)); |
| expect(buttonColor('Second child').color, theme.colorScheme.surface.withOpacity(0.0)); |
| }); |
| |
| testWidgetsWithLeakTracking('Custom button fillColor - MaterialState', (WidgetTester tester) async { |
| Material buttonColor(String text) { |
| return tester.widget<Material>( |
| find.descendant( |
| of: find.byType(TextButton), |
| matching: find.widgetWithText(Material, text), |
| ), |
| ); |
| } |
| |
| const Color selectedFillColor = Colors.orange; |
| const Color defaultFillColor = Colors.blue; |
| |
| Color getFillColor(Set<MaterialState> states) { |
| if (states.contains(MaterialState.selected)) { |
| return selectedFillColor; |
| } |
| return defaultFillColor; |
| } |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| fillColor: MaterialStateColor.resolveWith(getFillColor), |
| isSelected: const <bool>[false, true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(buttonColor('First child').color, defaultFillColor); |
| expect(buttonColor('Second child').color, selectedFillColor); |
| |
| // disabled |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| fillColor: MaterialStateColor.resolveWith(getFillColor), |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(buttonColor('First child').color, defaultFillColor); |
| expect(buttonColor('Second child').color, defaultFillColor); |
| }); |
| |
| testWidgetsWithLeakTracking('Default InkWell colors - unselected', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| final FocusNode focusNode = FocusNode(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false], |
| onPressed: (int index) {}, |
| focusNodes: <FocusNode>[focusNode], |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| final Offset center = tester.getCenter(find.text('First child')); |
| |
| // hoverColor |
| final TestGesture hoverGesture = await tester.createGesture( |
| kind: PointerDeviceKind.mouse, |
| ); |
| await hoverGesture.addPointer(); |
| await hoverGesture.moveTo(center); |
| await tester.pumpAndSettle(); |
| await hoverGesture.moveTo(Offset.zero); |
| |
| RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect( |
| inkFeatures, |
| paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.04)), |
| ); |
| |
| // splashColor |
| final TestGesture touchGesture = await tester.createGesture(); |
| await touchGesture.down(center); // The button is on hovered and pressed |
| await tester.pumpAndSettle(); |
| |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect( |
| inkFeatures, |
| paints |
| ..circle(color: theme.colorScheme.onSurface.withOpacity(0.16)), |
| ); |
| |
| await touchGesture.up(); |
| await tester.pumpAndSettle(); |
| await hoverGesture.moveTo(const Offset(0, 50)); |
| await tester.pumpAndSettle(); |
| |
| // focusColor |
| focusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect(inkFeatures, paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.12))); |
| |
| await hoverGesture.removePointer(); |
| }); |
| |
| testWidgetsWithLeakTracking('Default InkWell colors - selected', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| final FocusNode focusNode = FocusNode(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| focusNodes: <FocusNode>[focusNode], |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| final Offset center = tester.getCenter(find.text('First child')); |
| |
| // hoverColor |
| final TestGesture hoverGesture = await tester.createGesture( |
| kind: PointerDeviceKind.mouse, |
| ); |
| await hoverGesture.addPointer(); |
| await hoverGesture.moveTo(center); |
| await tester.pumpAndSettle(); |
| |
| RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect( |
| inkFeatures, |
| paints..rect(color: theme.colorScheme.primary.withOpacity(0.04)), |
| ); |
| await hoverGesture.moveTo(Offset.zero); |
| |
| // splashColor |
| final TestGesture touchGesture = await tester.createGesture(); |
| await touchGesture.down(center); // The button is on hovered and pressed |
| await tester.pumpAndSettle(); |
| |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect( |
| inkFeatures, |
| paints |
| ..circle(color: theme.colorScheme.primary.withOpacity(0.16)), |
| ); |
| |
| await touchGesture.up(); |
| await tester.pumpAndSettle(); |
| await hoverGesture.moveTo(const Offset(0, 50)); |
| await tester.pumpAndSettle(); |
| |
| // focusColor |
| focusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect(inkFeatures, paints..rect(color: theme.colorScheme.primary.withOpacity(0.12))); |
| |
| await hoverGesture.removePointer(); |
| }); |
| |
| testWidgetsWithLeakTracking('Custom InkWell colors', (WidgetTester tester) async { |
| const Color splashColor = Color(0xff4caf50); |
| const Color highlightColor = Color(0xffcddc39); |
| const Color hoverColor = Color(0xffffeb3b); |
| const Color focusColor = Color(0xffffff00); |
| final FocusNode focusNode = FocusNode(); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| splashColor: splashColor, |
| highlightColor: highlightColor, |
| hoverColor: hoverColor, |
| focusColor: focusColor, |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| focusNodes: <FocusNode>[focusNode], |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| final Offset center = tester.getCenter(find.text('First child')); |
| |
| // splashColor |
| final TestGesture touchGesture = await tester.createGesture(); |
| await touchGesture.down(center); |
| await tester.pumpAndSettle(); |
| |
| RenderObject inkFeatures; |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect( |
| inkFeatures, |
| paints |
| ..circle(color: splashColor), |
| ); |
| |
| await touchGesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // hoverColor |
| final TestGesture hoverGesture = await tester.createGesture( |
| kind: PointerDeviceKind.mouse, |
| ); |
| await hoverGesture.addPointer(); |
| await hoverGesture.moveTo(center); |
| await tester.pumpAndSettle(); |
| |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect(inkFeatures, paints..rect(color: hoverColor)); |
| await hoverGesture.moveTo(Offset.zero); |
| |
| // focusColor |
| focusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_RenderInkFeatures'; |
| }); |
| expect(inkFeatures, paints..rect(color: focusColor)); |
| |
| await hoverGesture.removePointer(); |
| }); |
| |
| testWidgetsWithLeakTracking( |
| 'Default border width and border colors for enabled, selected and disabled states', |
| (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| const double defaultBorderWidth = 1.0; |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| RenderObject toggleButtonRenderObject; |
| toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }); |
| expect( |
| toggleButtonRenderObject, |
| paints |
| // physical model |
| ..path() |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: defaultBorderWidth, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }); |
| expect( |
| toggleButtonRenderObject, |
| paints |
| // physical model |
| ..path() |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: defaultBorderWidth, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false], |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }); |
| expect( |
| toggleButtonRenderObject, |
| paints |
| // physical model |
| ..path() |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: defaultBorderWidth, |
| ), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking( |
| 'Custom border width and border colors for enabled, selected and disabled states', |
| (WidgetTester tester) async { |
| const Color borderColor = Color(0xff4caf50); |
| const Color selectedBorderColor = Color(0xffcddc39); |
| const Color disabledBorderColor = Color(0xffffeb3b); |
| const double customWidth = 2.0; |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| borderColor: borderColor, |
| borderWidth: customWidth, |
| isSelected: const <bool>[false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| RenderObject toggleButtonRenderObject; |
| toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }); |
| expect( |
| toggleButtonRenderObject, |
| paints |
| // physical model |
| ..path() |
| ..path( |
| style: PaintingStyle.stroke, |
| color: borderColor, |
| strokeWidth: customWidth, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| selectedBorderColor: selectedBorderColor, |
| borderWidth: customWidth, |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }); |
| expect( |
| toggleButtonRenderObject, |
| paints |
| // physical model |
| ..path() |
| ..path( |
| style: PaintingStyle.stroke, |
| color: selectedBorderColor, |
| strokeWidth: customWidth, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| disabledBorderColor: disabledBorderColor, |
| borderWidth: customWidth, |
| isSelected: const <bool>[false], |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ); |
| |
| toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }); |
| expect( |
| toggleButtonRenderObject, |
| paints |
| // physical model |
| ..path() |
| ..path( |
| style: PaintingStyle.stroke, |
| color: disabledBorderColor, |
| strokeWidth: customWidth, |
| ), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking('Height of segmented control is determined by tallest widget', (WidgetTester tester) async { |
| final List<Widget> children = <Widget>[ |
| Container( |
| constraints: const BoxConstraints.tightFor(height: 100.0), |
| ), |
| Container( |
| constraints: const BoxConstraints.tightFor(height: 400.0), // tallest widget |
| ), |
| Container( |
| constraints: const BoxConstraints.tightFor(height: 200.0), |
| ), |
| ]; |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true, false], |
| children: children, |
| ), |
| ), |
| ); |
| |
| final List<Widget> toggleButtons = tester.allWidgets.where((Widget widget) { |
| return widget.runtimeType.toString() == '_SelectToggleButton'; |
| }).toList(); |
| |
| for (int i = 0; i < toggleButtons.length; i++) { |
| final Rect rect = tester.getRect(find.byWidget(toggleButtons[i])); |
| expect(rect.height, 400.0 + 2 * _defaultBorderWidth); |
| } |
| }); |
| |
| testWidgetsWithLeakTracking('Sizes of toggle buttons rebuilds with the correct dimensions', (WidgetTester tester) async { |
| final List<Widget> children = <Widget>[ |
| Container( |
| constraints: const BoxConstraints.tightFor( |
| width: 100.0, |
| height: 100.0, |
| ), |
| ), |
| Container( |
| constraints: const BoxConstraints.tightFor( |
| width: 100.0, |
| height: 100.0, |
| ), |
| ), |
| Container( |
| constraints: const BoxConstraints.tightFor( |
| width: 100.0, |
| height: 100.0, |
| ), |
| ), |
| ]; |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true, false], |
| children: children, |
| ), |
| ), |
| ); |
| |
| List<Widget> toggleButtons; |
| toggleButtons = tester.allWidgets.where((Widget widget) { |
| return widget.runtimeType.toString() == '_SelectToggleButton'; |
| }).toList(); |
| |
| for (int i = 0; i < toggleButtons.length; i++) { |
| final Rect rect = tester.getRect(find.byWidget(toggleButtons[i])); |
| expect(rect.height, 100.0 + 2 * _defaultBorderWidth); |
| |
| // Only the last button paints both leading and trailing borders. |
| // Other buttons only paint the leading border. |
| if (i == toggleButtons.length - 1) { |
| expect(rect.width, 100.0 + 2 * _defaultBorderWidth); |
| } else { |
| expect(rect.width, 100.0 + 1 * _defaultBorderWidth); |
| } |
| } |
| |
| final List<Widget> childrenRebuilt = <Widget>[ |
| Container( |
| constraints: const BoxConstraints.tightFor( |
| width: 200.0, |
| height: 200.0, |
| ), |
| ), |
| Container( |
| constraints: const BoxConstraints.tightFor( |
| width: 200.0, |
| height: 200.0, |
| ), |
| ), |
| Container( |
| constraints: const BoxConstraints.tightFor( |
| width: 200.0, |
| height: 200.0, |
| ), |
| ), |
| ]; |
| |
| // Update border width and widget sized to verify layout updates correctly |
| const double customBorderWidth = 5.0; |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| borderWidth: customBorderWidth, |
| isSelected: const <bool>[false, true, false], |
| children: childrenRebuilt, |
| ), |
| ), |
| ); |
| |
| toggleButtons = tester.allWidgets.where((Widget widget) { |
| return widget.runtimeType.toString() == '_SelectToggleButton'; |
| }).toList(); |
| |
| // Only the last button paints both leading and trailing borders. |
| // Other buttons only paint the leading border. |
| for (int i = 0; i < toggleButtons.length; i++) { |
| final Rect rect = tester.getRect(find.byWidget(toggleButtons[i])); |
| expect(rect.height, 200.0 + 2 * customBorderWidth); |
| if (i == toggleButtons.length - 1) { |
| expect(rect.width, 200.0 + 2 * customBorderWidth); |
| } else { |
| expect(rect.width, 200.0 + 1 * customBorderWidth); |
| } |
| } |
| }); |
| |
| testWidgetsWithLeakTracking('ToggleButtons text baseline alignment', (WidgetTester tester) async { |
| // The point size of the fonts must be a multiple of 4 until |
| // https://github.com/flutter/flutter/issues/122066 is resolved. |
| await tester.pumpWidget( |
| boilerplate( |
| useMaterial3: false, |
| child: Row( |
| crossAxisAlignment: CrossAxisAlignment.baseline, |
| textBaseline: TextBaseline.alphabetic, |
| children: <Widget>[ |
| ToggleButtons( |
| borderWidth: 5.0, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 8.0)), |
| Text('Second child', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 8.0)), |
| ], |
| ), |
| ElevatedButton( |
| onPressed: null, |
| style: ElevatedButton.styleFrom(textStyle: const TextStyle( |
| fontFamily: 'FlutterTest', |
| fontSize: 20.0, |
| )), |
| child: const Text('Elevated Button'), |
| ), |
| const Text('Text', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 28.0)), |
| ], |
| ), |
| ), |
| ); |
| |
| // The test font extends 0.25 * fontSize below the baseline. |
| // So the three row elements line up like this: |
| // |
| // ToggleButton MaterialButton Text |
| // ------------------------------------ baseline |
| // 2.0 5.0 7.0 space below the baseline = 0.25 * fontSize |
| // ------------------------------------ widget text dy values |
| |
| final double firstToggleButtonDy = tester.getBottomLeft(find.text('First child')).dy; |
| final double secondToggleButtonDy = tester.getBottomLeft(find.text('Second child')).dy; |
| final double elevatedButtonDy = tester.getBottomLeft(find.text('Elevated Button')).dy; |
| final double textDy = tester.getBottomLeft(find.text('Text')).dy; |
| |
| expect(firstToggleButtonDy, secondToggleButtonDy); |
| expect(firstToggleButtonDy, elevatedButtonDy - 3.0); |
| expect(firstToggleButtonDy, textDy - 5.0); |
| }); |
| |
| testWidgetsWithLeakTracking('Directionality test', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Material( |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: ToggleButtons( |
| onPressed: (int index) {}, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect( |
| tester.getTopRight(find.text('First child')).dx < tester.getTopRight(find.text('Second child')).dx, |
| isTrue, |
| ); |
| |
| await tester.pumpWidget( |
| Material( |
| child: Directionality( |
| textDirection: TextDirection.rtl, |
| child: Center( |
| child: ToggleButtons( |
| onPressed: (int index) {}, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect( |
| tester.getTopRight(find.text('First child')).dx > tester.getTopRight(find.text('Second child')).dx, |
| isTrue, |
| ); |
| }); |
| |
| testWidgetsWithLeakTracking( |
| 'Properly draws borders based on state', |
| (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| Text('Third child'), |
| ], |
| ), |
| ), |
| ); |
| |
| final List<RenderObject> toggleButtonRenderObject = tester.allRenderObjects.where((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }).toSet().toList(); |
| |
| // The first button paints the leading, top and bottom sides with a path |
| expect( |
| toggleButtonRenderObject[0], |
| paints |
| // physical model |
| ..path() |
| // leading side, top and bottom - enabled |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| |
| // The middle buttons paint a leading side path first, followed by a |
| // top and bottom side path |
| expect( |
| toggleButtonRenderObject[1], |
| paints |
| // physical model |
| ..path() |
| // leading side - selected |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ) |
| // top and bottom - selected |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| |
| // The last button paints a leading side path first, followed by |
| // a trailing, top and bottom side path |
| expect( |
| toggleButtonRenderObject[2], |
| paints |
| // physical model |
| ..path() |
| // leading side - selected, since previous button is selected |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ) |
| // trailing side, top and bottom - enabled |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking( |
| 'Properly draws borders based on state when direction is vertical and verticalDirection is down.', |
| (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| direction: Axis.vertical, |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| Text('Third child'), |
| ], |
| ), |
| ), |
| ); |
| |
| // The children should be laid out along vertical and the first child at top. |
| // The item height is icon height + default border width (48.0 + 1.0) pixels. |
| expect(tester.getCenter(find.text('First child')), const Offset(400.0, 251.0)); |
| expect(tester.getCenter(find.text('Second child')), const Offset(400.0, 300.0)); |
| expect(tester.getCenter(find.text('Third child')), const Offset(400.0, 349.0)); |
| |
| final List<RenderObject> toggleButtonRenderObject = tester.allRenderObjects.where((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }).toSet().toList(); |
| |
| // The first button paints the left, top and right sides with a path. |
| expect( |
| toggleButtonRenderObject[0], |
| paints |
| // physical model |
| ..path() |
| // left side, top and right - enabled. |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| |
| // The middle buttons paint a top side path first, followed by a |
| // left and right side path. |
| expect( |
| toggleButtonRenderObject[1], |
| paints |
| // physical model |
| ..path() |
| // top side - selected. |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ) |
| // left and right - selected. |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| |
| // The last button paints a top side path first, followed by |
| // a left, bottom and right side path |
| expect( |
| toggleButtonRenderObject[2], |
| paints |
| // physical model |
| ..path() |
| // top side - selected, since previous button is selected. |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ) |
| // left side, bottom and right - enabled. |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking( |
| 'VerticalDirection test when direction is vertical.', |
| (WidgetTester tester) async { |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| direction: Axis.vertical, |
| verticalDirection: VerticalDirection.up, |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| Text('Third child'), |
| ], |
| ), |
| ), |
| ); |
| |
| // The children should be laid out along vertical and the last child at top. |
| expect(tester.getCenter(find.text('Third child')), const Offset(400.0, 251.0)); |
| expect(tester.getCenter(find.text('Second child')), const Offset(400.0, 300.0)); |
| expect(tester.getCenter(find.text('First child')), const Offset(400.0, 349.0)); |
| }, |
| ); |
| |
| testWidgetsWithLeakTracking('Tap target size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { |
| Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { |
| return boilerplate( |
| useMaterial3: false, |
| tapTargetSize: tapTargetSize, |
| child: ToggleButtons( |
| key: key, |
| constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First'), |
| Text('Second'), |
| Text('Third'), |
| ], |
| ), |
| ); |
| } |
| |
| final Key key1 = UniqueKey(); |
| await tester.pumpWidget(buildFrame(MaterialTapTargetSize.padded, key1)); |
| expect(tester.getSize(find.byKey(key1)), const Size(228.0, 48.0)); |
| |
| final Key key2 = UniqueKey(); |
| await tester.pumpWidget(buildFrame(MaterialTapTargetSize.shrinkWrap, key2)); |
| expect(tester.getSize(find.byKey(key2)), const Size(228.0, 34.0)); |
| }); |
| |
| testWidgetsWithLeakTracking('Tap target size is configurable', (WidgetTester tester) async { |
| Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { |
| return boilerplate( |
| useMaterial3: false, |
| child: ToggleButtons( |
| key: key, |
| tapTargetSize: tapTargetSize, |
| constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First'), |
| Text('Second'), |
| Text('Third'), |
| ], |
| ), |
| ); |
| } |
| |
| final Key key1 = UniqueKey(); |
| await tester.pumpWidget(buildFrame(MaterialTapTargetSize.padded, key1)); |
| expect(tester.getSize(find.byKey(key1)), const Size(228.0, 48.0)); |
| |
| final Key key2 = UniqueKey(); |
| await tester.pumpWidget(buildFrame(MaterialTapTargetSize.shrinkWrap, key2)); |
| expect(tester.getSize(find.byKey(key2)), const Size(228.0, 34.0)); |
| }); |
| |
| testWidgetsWithLeakTracking('Tap target size is configurable for vertical axis', (WidgetTester tester) async { |
| Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { |
| return boilerplate( |
| child: ToggleButtons( |
| key: key, |
| tapTargetSize: tapTargetSize, |
| constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), |
| direction: Axis.vertical, |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('1'), |
| Text('2'), |
| Text('3'), |
| ], |
| ), |
| ); |
| } |
| |
| final Key key1 = UniqueKey(); |
| await tester.pumpWidget(buildFrame(MaterialTapTargetSize.padded, key1)); |
| expect(tester.getSize(find.byKey(key1)), const Size(48.0, 100.0)); |
| |
| final Key key2 = UniqueKey(); |
| await tester.pumpWidget(buildFrame(MaterialTapTargetSize.shrinkWrap, key2)); |
| expect(tester.getSize(find.byKey(key2)), const Size(34.0, 100.0)); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/73725 |
| testWidgetsWithLeakTracking('Border radius paint test when there is only one button', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(useMaterial3: false); |
| await tester.pumpWidget( |
| boilerplate( |
| useMaterial3: false, |
| child: RepaintBoundary( |
| child: ToggleButtons( |
| borderRadius: const BorderRadius.all(Radius.circular(7.0)), |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| // The only button should be laid out at the center of the screen. |
| expect(tester.getCenter(find.text('First child')), const Offset(400.0, 300.0)); |
| |
| final List<RenderObject> toggleButtonRenderObject = tester.allRenderObjects.where((RenderObject object) { |
| return object.runtimeType.toString() == '_SelectToggleButtonRenderObject'; |
| }).toSet().toList(); |
| |
| // The first button paints the left, top and right sides with a path. |
| expect( |
| toggleButtonRenderObject[0], |
| paints |
| // physical model paints |
| ..path() |
| // left side, top and right - enabled. |
| ..path( |
| style: PaintingStyle.stroke, |
| color: theme.colorScheme.onSurface.withOpacity(0.12), |
| strokeWidth: _defaultBorderWidth, |
| ), |
| ); |
| |
| await expectLater( |
| find.byType(RepaintBoundary), |
| matchesGoldenFile('toggle_buttons.oneButton.boardsPaint.png'), |
| ); |
| }); |
| |
| testWidgetsWithLeakTracking('Border radius paint test when Radius.x or Radius.y equal 0.0', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| boilerplate( |
| useMaterial3: false, |
| child: RepaintBoundary( |
| child: ToggleButtons( |
| borderRadius: const BorderRadius.only( |
| topRight: Radius.elliptical(10, 0), |
| topLeft: Radius.elliptical(0, 10), |
| bottomRight: Radius.elliptical(0, 10), |
| bottomLeft: Radius.elliptical(10, 0), |
| ), |
| isSelected: const <bool>[true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| await expectLater( |
| find.byType(RepaintBoundary), |
| matchesGoldenFile('toggle_buttons.oneButton.boardsPaint2.png'), |
| ); |
| }); |
| |
| testWidgetsWithLeakTracking('ToggleButtons implements debugFillProperties', (WidgetTester tester) async { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| |
| ToggleButtons( |
| direction: Axis.vertical, |
| verticalDirection: VerticalDirection.up, |
| borderWidth: 3.0, |
| color: Colors.green, |
| selectedBorderColor: Colors.pink, |
| disabledColor: Colors.blue, |
| disabledBorderColor: Colors.yellow, |
| borderRadius: const BorderRadius.all(Radius.circular(7.0)), |
| isSelected: const <bool>[false, true, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| Text('Third child'), |
| ], |
| ).debugFillProperties(builder); |
| |
| final List<String> description = builder.properties |
| .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) |
| .map((DiagnosticsNode node) => node.toString()).toList(); |
| |
| expect(description, <String>[ |
| 'Buttons are enabled', |
| 'color: MaterialColor(primary value: Color(0xff4caf50))', |
| 'disabledColor: MaterialColor(primary value: Color(0xff2196f3))', |
| 'selectedBorderColor: MaterialColor(primary value: Color(0xffe91e63))', |
| 'disabledBorderColor: MaterialColor(primary value: Color(0xffffeb3b))', |
| 'borderRadius: BorderRadius.circular(7.0)', |
| 'borderWidth: 3.0', |
| 'direction: Axis.vertical', |
| 'verticalDirection: VerticalDirection.up', |
| ]); |
| }); |
| |
| testWidgetsWithLeakTracking('ToggleButtons changes mouse cursor when the button is hovered', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| boilerplate( |
| child: MouseRegion( |
| cursor: SystemMouseCursors.forbidden, |
| child: ToggleButtons( |
| mouseCursor: SystemMouseCursors.text, |
| onPressed: (int index) {}, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1); |
| await gesture.addPointer(location: tester.getCenter(find.text('First child'))); |
| |
| await tester.pump(); |
| |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); |
| |
| // Test default cursor |
| await tester.pumpWidget( |
| boilerplate( |
| child: MouseRegion( |
| cursor: SystemMouseCursors.forbidden, |
| child: ToggleButtons( |
| onPressed: (int index) {}, |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click); |
| |
| // Test default cursor when disabled |
| await tester.pumpWidget( |
| boilerplate( |
| child: MouseRegion( |
| cursor: SystemMouseCursors.forbidden, |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true], |
| children: const <Widget>[ |
| Text('First child'), |
| Text('Second child'), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); |
| }); |
| |
| testWidgetsWithLeakTracking('ToggleButtons focus, hover, and highlight elevations are 0', (WidgetTester tester) async { |
| final List<FocusNode> focusNodes = <FocusNode>[FocusNode(), FocusNode()]; |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[true, false], |
| onPressed: (int index) { }, |
| focusNodes: focusNodes, |
| children: const <Widget>[Text('one'), Text('two')], |
| ), |
| ), |
| ); |
| |
| double toggleButtonElevation(String text) { |
| return tester.widget<Material>(find.widgetWithText(Material, text).first).elevation; |
| } |
| |
| // Default toggle button elevation |
| expect(toggleButtonElevation('one'), 0); // highlighted |
| expect(toggleButtonElevation('two'), 0); // not highlighted |
| |
| // Hovered button elevation |
| final TestGesture hoverGesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await hoverGesture.addPointer(); |
| await hoverGesture.moveTo(tester.getCenter(find.text('one'))); |
| await tester.pumpAndSettle(); |
| expect(toggleButtonElevation('one'), 0); |
| await hoverGesture.moveTo(tester.getCenter(find.text('two'))); |
| await tester.pumpAndSettle(); |
| expect(toggleButtonElevation('two'), 0); |
| |
| // Focused button elevation |
| focusNodes[0].requestFocus(); |
| await tester.pumpAndSettle(); |
| expect(focusNodes[0].hasFocus, isTrue); |
| expect(focusNodes[1].hasFocus, isFalse); |
| expect(toggleButtonElevation('one'), 0); |
| focusNodes[1].requestFocus(); |
| await tester.pumpAndSettle(); |
| expect(focusNodes[0].hasFocus, isFalse); |
| expect(focusNodes[1].hasFocus, isTrue); |
| expect(toggleButtonElevation('two'), 0); |
| |
| await hoverGesture.removePointer(); |
| }); |
| |
| testWidgetsWithLeakTracking('Toggle buttons height matches MaterialTapTargetSize.padded height', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, false, false], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Icon(Icons.check), |
| Icon(Icons.access_alarm), |
| Icon(Icons.cake), |
| ], |
| ), |
| ), |
| ); |
| |
| final Rect firstRect = tester.getRect(find.byType(TextButton).at(0)); |
| expect(firstRect.height, 48.0); |
| final Rect secondRect = tester.getRect(find.byType(TextButton).at(1)); |
| expect(secondRect.height, 48.0); |
| final Rect thirdRect = tester.getRect(find.byType(TextButton).at(2)); |
| expect(thirdRect.height, 48.0); |
| }); |
| |
| testWidgetsWithLeakTracking('Toggle buttons constraints size does not affect minimum input padding', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/97302 |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, false, false], |
| onPressed: (int index) {}, |
| constraints: const BoxConstraints.tightFor( |
| width: 86, |
| height: 32, |
| ), |
| children: const <Widget>[ |
| Icon(Icons.check), |
| Icon(Icons.access_alarm), |
| Icon(Icons.cake), |
| ], |
| ), |
| ), |
| ); |
| |
| // Button's height is constrained to `32.0`. |
| final Rect firstRect = tester.getRect(find.byType(TextButton).at(0)); |
| expect(firstRect.height, 32.0); |
| final Rect secondRect = tester.getRect(find.byType(TextButton).at(1)); |
| expect(secondRect.height, 32.0); |
| final Rect thirdRect = tester.getRect(find.byType(TextButton).at(2)); |
| expect(thirdRect.height, 32.0); |
| |
| // While button's height is constrained to `32.0`, semantic node height |
| // should remain `48.0`, matching `MaterialTapTargetSize.padded` height (default). |
| expect( |
| semantics, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.hasCheckedState, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[ |
| SemanticsAction.tap, |
| ], |
| rect: const Rect.fromLTRB(0.0, 0.0, 87.0, 48.0), |
| ), |
| TestSemantics( |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.hasCheckedState, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[ |
| SemanticsAction.tap, |
| ], |
| rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0) |
| ), |
| TestSemantics( |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.hasCheckedState, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[ |
| SemanticsAction.tap, |
| ], |
| rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0), |
| ), |
| ], |
| ), |
| ignoreId: true, |
| ignoreRect: true, |
| ignoreTransform: true, |
| ), |
| ); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgetsWithLeakTracking('Toggle buttons have correct semantics', (WidgetTester tester) async { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| await tester.pumpWidget( |
| boilerplate( |
| child: ToggleButtons( |
| isSelected: const <bool>[false, true], |
| onPressed: (int index) {}, |
| children: const <Widget>[ |
| Icon(Icons.check), |
| Icon(Icons.access_alarm), |
| ], |
| ), |
| ), |
| ); |
| |
| expect( |
| semantics, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.hasCheckedState, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[ |
| SemanticsAction.tap, |
| ], |
| ), |
| TestSemantics( |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isChecked, |
| SemanticsFlag.hasCheckedState, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[ |
| SemanticsAction.tap, |
| ], |
| ), |
| ], |
| ), |
| ignoreId: true, |
| ignoreRect: true, |
| ignoreTransform: true, |
| ), |
| ); |
| |
| semantics.dispose(); |
| }); |
| } |