| // 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/scheduler.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../rendering/mock_canvas.dart'; |
| import '../widgets/semantics_tester.dart'; |
| import 'feedback_tester.dart'; |
| |
| Finder findRenderChipElement() { |
| return find.byElementPredicate((Element e) => '${e.renderObject.runtimeType}' == '_RenderChip'); |
| } |
| |
| RenderBox getMaterialBox(WidgetTester tester) { |
| return tester.firstRenderObject<RenderBox>( |
| find.descendant( |
| of: find.byType(RawChip), |
| matching: find.byType(CustomPaint), |
| ), |
| ); |
| } |
| |
| Material getMaterial(WidgetTester tester) { |
| return tester.widget<Material>( |
| find.descendant( |
| of: find.byType(RawChip), |
| matching: find.byType(Material), |
| ), |
| ); |
| } |
| |
| IconThemeData getIconData(WidgetTester tester) { |
| final IconTheme iconTheme = tester.firstWidget( |
| find.descendant( |
| of: find.byType(RawChip), |
| matching: find.byType(IconTheme), |
| ), |
| ); |
| return iconTheme.data; |
| } |
| |
| DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { |
| return tester.widget( |
| find.ancestor( |
| of: find.text(labelText), |
| matching: find.byType(DefaultTextStyle), |
| ).first, |
| ); |
| } |
| |
| dynamic getRenderChip(WidgetTester tester) { |
| if (!tester.any(findRenderChipElement())) { |
| return null; |
| } |
| final Element element = tester.element(findRenderChipElement()); |
| return element.renderObject; |
| } |
| |
| // ignore: avoid_dynamic_calls |
| double getSelectProgress(WidgetTester tester) => getRenderChip(tester)?.checkmarkAnimation?.value as double; |
| // ignore: avoid_dynamic_calls |
| double getAvatarDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.avatarDrawerAnimation?.value as double; |
| // ignore: avoid_dynamic_calls |
| double getDeleteDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.deleteDrawerAnimation?.value as double; |
| |
| /// Adds the basic requirements for a Chip. |
| Widget wrapForChip({ |
| required Widget child, |
| TextDirection textDirection = TextDirection.ltr, |
| double textScaleFactor = 1.0, |
| Brightness brightness = Brightness.light, |
| }) { |
| return MaterialApp( |
| theme: ThemeData(brightness: brightness), |
| home: Directionality( |
| textDirection: textDirection, |
| child: MediaQuery( |
| data: MediaQueryData(textScaleFactor: textScaleFactor), |
| child: Material(child: child), |
| ), |
| ), |
| ); |
| } |
| |
| /// Tests that a [Chip] that has its size constrained by its parent is |
| /// further constraining the size of its child, the label widget. |
| /// Optionally, adding an avatar or delete icon to the chip should not |
| /// cause the chip or label to exceed its constrained height. |
| Future<void> testConstrainedLabel( |
| WidgetTester tester, { |
| CircleAvatar? avatar, |
| VoidCallback? onDeleted, |
| }) async { |
| const double labelWidth = 100.0; |
| const double labelHeight = 50.0; |
| const double chipParentWidth = 75.0; |
| const double chipParentHeight = 25.0; |
| final Key labelKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Center( |
| child: SizedBox( |
| width: chipParentWidth, |
| height: chipParentHeight, |
| child: Chip( |
| avatar: avatar, |
| label: SizedBox( |
| key: labelKey, |
| width: labelWidth, |
| height: labelHeight, |
| ), |
| onDeleted: onDeleted, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Size labelSize = tester.getSize(find.byKey(labelKey)); |
| expect(labelSize.width, lessThan(chipParentWidth)); |
| expect(labelSize.height, lessThanOrEqualTo(chipParentHeight)); |
| |
| final Size chipSize = tester.getSize(find.byType(Chip)); |
| expect(chipSize.width, chipParentWidth); |
| expect(chipSize.height, chipParentHeight); |
| } |
| |
| void doNothing() {} |
| |
| Widget chipWithOptionalDeleteButton({ |
| Key? deleteButtonKey, |
| Key? labelKey, |
| required bool deletable, |
| TextDirection textDirection = TextDirection.ltr, |
| bool useDeleteButtonTooltip = true, |
| String? chipTooltip, |
| String? deleteButtonTooltipMessage, |
| VoidCallback? onPressed = doNothing, |
| }) { |
| return wrapForChip( |
| textDirection: textDirection, |
| child: Wrap( |
| children: <Widget>[ |
| RawChip( |
| tooltip: chipTooltip, |
| onPressed: onPressed, |
| onDeleted: deletable ? doNothing : null, |
| deleteIcon: Icon(Icons.close, key: deleteButtonKey), |
| useDeleteButtonTooltip: useDeleteButtonTooltip, |
| deleteButtonTooltipMessage: deleteButtonTooltipMessage, |
| label: Text( |
| deletable |
| ? 'Chip with Delete Button' |
| : 'Chip without Delete Button', |
| key: labelKey, |
| ), |
| ), |
| ], |
| ), |
| ); |
| } |
| |
| bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0; |
| bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0; |
| |
| // Ripple pattern matches if there exists at least one ripple |
| // with the [expectedCenter] and [expectedRadius]. |
| // This ensures the existence of a ripple. |
| PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius) { |
| return paints |
| ..something((Symbol method, List<dynamic> arguments) { |
| if (method != #drawCircle) { |
| return false; |
| } |
| final Offset center = arguments[0] as Offset; |
| final double radius = arguments[1] as double; |
| return offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius); |
| } |
| ); |
| } |
| |
| // Unique ripple pattern matches if there does not exist ripples |
| // other than ones with the [expectedCenter] and [expectedRadius]. |
| // This ensures the nonexistence of two different ripples. |
| PaintPattern uniqueRipplePattern(Offset expectedCenter, double expectedRadius) { |
| return paints |
| ..everything((Symbol method, List<dynamic> arguments) { |
| if (method != #drawCircle) { |
| return true; |
| } |
| final Offset center = arguments[0] as Offset; |
| final double radius = arguments[1] as double; |
| if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius)) { |
| return true; |
| } |
| throw ''' |
| Expected: center == $expectedCenter, radius == $expectedRadius |
| Found: center == $center radius == $radius'''; |
| } |
| ); |
| } |
| |
| // Finds any container of a tooltip. |
| Finder findTooltipContainer(String tooltipText) { |
| return find.ancestor( |
| of: find.text(tooltipText), |
| matching: find.byType(Container), |
| ); |
| } |
| |
| void main() { |
| testWidgets('M2 Chip defaults', (WidgetTester tester) async { |
| late TextTheme textTheme; |
| |
| Widget buildFrame(Brightness brightness) { |
| return MaterialApp( |
| theme: ThemeData(brightness: brightness), |
| home: Scaffold( |
| body: Center( |
| child: Builder( |
| builder: (BuildContext context) { |
| textTheme = Theme.of(context).textTheme; |
| return Chip( |
| avatar: const CircleAvatar(child: Text('A')), |
| label: const Text('Chip A'), |
| onDeleted: () { }, |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(Brightness.light)); |
| expect(getMaterialBox(tester), paints..rrect()..circle(color: const Color(0xff1976d2))); |
| expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0)); |
| expect(getMaterial(tester).color, null); |
| expect(getMaterial(tester).elevation, 0); |
| expect(getMaterial(tester).shape, const StadiumBorder()); |
| expect(getIconData(tester).color?.value, 0xffffffff); |
| expect(getIconData(tester).opacity, null); |
| expect(getIconData(tester).size, null); |
| |
| TextStyle labelStyle = getLabelStyle(tester, 'Chip A').style; |
| expect(labelStyle.color?.value, 0xde000000); |
| expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily); |
| expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback); |
| expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures); |
| expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize); |
| expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle); |
| expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight); |
| expect(labelStyle.height, textTheme.bodyLarge?.height); |
| expect(labelStyle.inherit, textTheme.bodyLarge?.inherit); |
| expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution); |
| expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing); |
| expect(labelStyle.overflow, textTheme.bodyLarge?.overflow); |
| expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline); |
| expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing); |
| |
| await tester.pumpWidget(buildFrame(Brightness.dark)); |
| await tester.pumpAndSettle(); // Theme transition animation |
| expect(getMaterialBox(tester), paints..rrect(color: const Color(0x1fffffff))); |
| expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0)); |
| expect(getMaterial(tester).color, null); |
| expect(getMaterial(tester).elevation, 0); |
| expect(getMaterial(tester).shape, const StadiumBorder()); |
| expect(getIconData(tester).color?.value, 0xffffffff); |
| expect(getIconData(tester).opacity, null); |
| expect(getIconData(tester).size, null); |
| |
| labelStyle = getLabelStyle(tester, 'Chip A').style; |
| expect(labelStyle.color?.value, 0xdeffffff); |
| expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily); |
| expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback); |
| expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures); |
| expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize); |
| expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle); |
| expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight); |
| expect(labelStyle.height, textTheme.bodyLarge?.height); |
| expect(labelStyle.inherit, textTheme.bodyLarge?.inherit); |
| expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution); |
| expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing); |
| expect(labelStyle.overflow, textTheme.bodyLarge?.overflow); |
| expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline); |
| expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing); |
| }); |
| |
| testWidgets('M3 Chip defaults', (WidgetTester tester) async { |
| late TextTheme textTheme; |
| final ThemeData lightTheme = ThemeData.light(useMaterial3: true); |
| final ThemeData darkTheme = ThemeData.dark(useMaterial3: true); |
| |
| Widget buildFrame(ThemeData theme) { |
| return MaterialApp( |
| theme: theme, |
| home: Scaffold( |
| body: Center( |
| child: Builder( |
| builder: (BuildContext context) { |
| textTheme = Theme.of(context).textTheme; |
| return Chip( |
| avatar: const CircleAvatar(child: Text('A')), |
| label: const Text('Chip A'), |
| onDeleted: () { }, |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(lightTheme)); |
| expect(getMaterial(tester).color, null); |
| expect(getMaterial(tester).elevation, 0); |
| expect(getMaterial(tester).shape, RoundedRectangleBorder( |
| side: BorderSide(color: lightTheme.colorScheme.outline), |
| borderRadius: BorderRadius.circular(8.0), |
| )); |
| expect(getIconData(tester).color, lightTheme.colorScheme.primary); |
| expect(getIconData(tester).opacity, null); |
| expect(getIconData(tester).size, 18); |
| |
| TextStyle labelStyle = getLabelStyle(tester, 'Chip A').style; |
| expect(labelStyle.color, textTheme.labelLarge?.color); |
| expect(labelStyle.fontFamily, textTheme.labelLarge?.fontFamily); |
| expect(labelStyle.fontFamilyFallback, textTheme.labelLarge?.fontFamilyFallback); |
| expect(labelStyle.fontFeatures, textTheme.labelLarge?.fontFeatures); |
| expect(labelStyle.fontSize, textTheme.labelLarge?.fontSize); |
| expect(labelStyle.fontStyle, textTheme.labelLarge?.fontStyle); |
| expect(labelStyle.fontWeight, textTheme.labelLarge?.fontWeight); |
| expect(labelStyle.height, textTheme.labelLarge?.height); |
| expect(labelStyle.inherit, textTheme.labelLarge?.inherit); |
| expect(labelStyle.leadingDistribution, textTheme.labelLarge?.leadingDistribution); |
| expect(labelStyle.letterSpacing, textTheme.labelLarge?.letterSpacing); |
| expect(labelStyle.overflow, textTheme.labelLarge?.overflow); |
| expect(labelStyle.textBaseline, textTheme.labelLarge?.textBaseline); |
| expect(labelStyle.wordSpacing, textTheme.labelLarge?.wordSpacing); |
| |
| await tester.pumpWidget(buildFrame(darkTheme)); |
| await tester.pumpAndSettle(); // Theme transition animation |
| expect(getMaterial(tester).color, null); |
| expect(getMaterial(tester).elevation, 0); |
| expect(getMaterial(tester).shape, RoundedRectangleBorder( |
| side: BorderSide(color: darkTheme.colorScheme.outline), |
| borderRadius: BorderRadius.circular(8.0), |
| )); |
| expect(getIconData(tester).color, darkTheme.colorScheme.primary); |
| expect(getIconData(tester).opacity, null); |
| expect(getIconData(tester).size, 18); |
| |
| labelStyle = getLabelStyle(tester, 'Chip A').style; |
| expect(labelStyle.color, textTheme.labelLarge?.color); |
| expect(labelStyle.fontFamily, textTheme.labelLarge?.fontFamily); |
| expect(labelStyle.fontFamilyFallback, textTheme.labelLarge?.fontFamilyFallback); |
| expect(labelStyle.fontFeatures, textTheme.labelLarge?.fontFeatures); |
| expect(labelStyle.fontSize, textTheme.labelLarge?.fontSize); |
| expect(labelStyle.fontStyle, textTheme.labelLarge?.fontStyle); |
| expect(labelStyle.fontWeight, textTheme.labelLarge?.fontWeight); |
| expect(labelStyle.height, textTheme.labelLarge?.height); |
| expect(labelStyle.inherit, textTheme.labelLarge?.inherit); |
| expect(labelStyle.leadingDistribution, textTheme.labelLarge?.leadingDistribution); |
| expect(labelStyle.letterSpacing, textTheme.labelLarge?.letterSpacing); |
| expect(labelStyle.overflow, textTheme.labelLarge?.overflow); |
| expect(labelStyle.textBaseline, textTheme.labelLarge?.textBaseline); |
| expect(labelStyle.wordSpacing, textTheme.labelLarge?.wordSpacing); |
| }); |
| |
| testWidgets('Chip control test', (WidgetTester tester) async { |
| final FeedbackTester feedback = FeedbackTester(); |
| final List<String> deletedChipLabels = <String>[]; |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| avatar: const CircleAvatar(child: Text('A')), |
| label: const Text('Chip A'), |
| onDeleted: () { |
| deletedChipLabels.add('A'); |
| }, |
| deleteButtonTooltipMessage: 'Delete chip A', |
| ), |
| Chip( |
| avatar: const CircleAvatar(child: Text('B')), |
| label: const Text('Chip B'), |
| onDeleted: () { |
| deletedChipLabels.add('B'); |
| }, |
| deleteButtonTooltipMessage: 'Delete chip B', |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(tester.widget(find.byTooltip('Delete chip A')), isNotNull); |
| expect(tester.widget(find.byTooltip('Delete chip B')), isNotNull); |
| |
| expect(feedback.clickSoundCount, 0); |
| |
| expect(deletedChipLabels, isEmpty); |
| await tester.tap(find.byTooltip('Delete chip A')); |
| expect(deletedChipLabels, equals(<String>['A'])); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(feedback.clickSoundCount, 1); |
| |
| await tester.tap(find.byTooltip('Delete chip B')); |
| expect(deletedChipLabels, equals(<String>['A', 'B'])); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(feedback.clickSoundCount, 2); |
| |
| feedback.dispose(); |
| }); |
| |
| testWidgets( |
| 'Chip does not constrain size of label widget if it does not exceed ' |
| 'the available space', |
| (WidgetTester tester) async { |
| const double labelWidth = 50.0; |
| const double labelHeight = 30.0; |
| final Key labelKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Center( |
| child: SizedBox( |
| width: 500.0, |
| height: 500.0, |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| label: SizedBox( |
| key: labelKey, |
| width: labelWidth, |
| height: labelHeight, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Size labelSize = tester.getSize(find.byKey(labelKey)); |
| expect(labelSize.width, labelWidth); |
| expect(labelSize.height, labelHeight); |
| }, |
| ); |
| |
| testWidgets( |
| 'Chip constrains the size of the label widget when it exceeds the ' |
| 'available space', |
| (WidgetTester tester) async { |
| await testConstrainedLabel(tester); |
| }, |
| ); |
| |
| testWidgets( |
| 'Chip constrains the size of the label widget when it exceeds the ' |
| 'available space and the avatar is present', |
| (WidgetTester tester) async { |
| await testConstrainedLabel( |
| tester, |
| avatar: const CircleAvatar(child: Text('A')), |
| ); |
| }, |
| ); |
| |
| testWidgets( |
| 'Chip constrains the size of the label widget when it exceeds the ' |
| 'available space and the delete icon is present', |
| (WidgetTester tester) async { |
| await testConstrainedLabel( |
| tester, |
| onDeleted: () { }, |
| ); |
| }, |
| ); |
| |
| testWidgets( |
| 'Chip constrains the size of the label widget when it exceeds the ' |
| 'available space and both avatar and delete icons are present', |
| (WidgetTester tester) async { |
| await testConstrainedLabel( |
| tester, |
| avatar: const CircleAvatar(child: Text('A')), |
| onDeleted: () { }, |
| ); |
| }, |
| ); |
| |
| testWidgets( |
| 'Chip constrains the avatar, label, and delete icons to the bounds of ' |
| 'the chip when it exceeds the available space', |
| (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/11523 |
| Widget chipBuilder (String text, {Widget? avatar, VoidCallback? onDeleted}) { |
| return MaterialApp( |
| home: Scaffold( |
| body: SizedBox( |
| width: 150, |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| avatar: avatar, |
| label: Text(text), |
| onDeleted: onDeleted, |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| void chipRectContains(Rect chipRect, Rect rect) { |
| expect(chipRect.contains(rect.topLeft), true); |
| expect(chipRect.contains(rect.topRight), true); |
| expect(chipRect.contains(rect.bottomLeft), true); |
| expect(chipRect.contains(rect.bottomRight), true); |
| } |
| |
| Rect chipRect; |
| Rect avatarRect; |
| Rect labelRect; |
| Rect deleteIconRect; |
| const String text = 'Very long text that will be clipped'; |
| |
| await tester.pumpWidget(chipBuilder(text)); |
| |
| chipRect = tester.getRect(find.byType(Chip)); |
| labelRect = tester.getRect(find.text(text)); |
| chipRectContains(chipRect, labelRect); |
| |
| await tester.pumpWidget(chipBuilder( |
| text, |
| avatar: const CircleAvatar(child: Text('A')), |
| )); |
| await tester.pumpAndSettle(); |
| |
| chipRect = tester.getRect(find.byType(Chip)); |
| avatarRect = tester.getRect(find.byType(CircleAvatar)); |
| chipRectContains(chipRect, avatarRect); |
| |
| labelRect = tester.getRect(find.text(text)); |
| chipRectContains(chipRect, labelRect); |
| |
| await tester.pumpWidget(chipBuilder( |
| text, |
| avatar: const CircleAvatar(child: Text('A')), |
| onDeleted: () {}, |
| )); |
| await tester.pumpAndSettle(); |
| |
| chipRect = tester.getRect(find.byType(Chip)); |
| avatarRect = tester.getRect(find.byType(CircleAvatar)); |
| chipRectContains(chipRect, avatarRect); |
| |
| labelRect = tester.getRect(find.text(text)); |
| chipRectContains(chipRect, labelRect); |
| |
| deleteIconRect = tester.getRect(find.byIcon(Icons.cancel)); |
| chipRectContains(chipRect, deleteIconRect); |
| }, |
| ); |
| |
| testWidgets('Chip in row works ok', (WidgetTester tester) async { |
| const TextStyle style = TextStyle(fontFamily: 'Ahem', fontSize: 10.0); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Row( |
| children: const <Widget>[ |
| Chip(label: Text('Test'), labelStyle: style), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0)); |
| expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0)); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Row( |
| children: const <Widget>[ |
| Flexible(child: Chip(label: Text('Test'), labelStyle: style)), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0)); |
| expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0)); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Row( |
| children: const <Widget>[ |
| Expanded(child: Chip(label: Text('Test'), labelStyle: style)), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0)); |
| expect(tester.getSize(find.byType(Chip)), const Size(800.0, 48.0)); |
| }); |
| |
| testWidgets('Chip responds to materialTapTargetSize', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: const <Widget>[ |
| Chip( |
| label: Text('X'), |
| materialTapTargetSize: MaterialTapTargetSize.padded, |
| ), |
| Chip( |
| label: Text('X'), |
| materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
| ), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.getSize(find.byType(Chip).first), const Size(48.0, 48.0)); |
| expect(tester.getSize(find.byType(Chip).last), const Size(38.0, 32.0)); |
| }, |
| ); |
| |
| testWidgets('delete button tap target is the right proportion of the chip', (WidgetTester tester) async { |
| final UniqueKey deleteKey = UniqueKey(); |
| bool calledDelete = false; |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| label: const Text('Really Long Label'), |
| deleteIcon: Icon(Icons.delete, key: deleteKey), |
| onDeleted: () { |
| calledDelete = true; |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(24.0, 0.0)); |
| await tester.pump(); |
| expect(calledDelete, isTrue); |
| calledDelete = false; |
| |
| await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(25.0, 0.0)); |
| await tester.pump(); |
| expect(calledDelete, isFalse); |
| calledDelete = false; |
| |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| label: const SizedBox(), // Short label |
| deleteIcon: Icon(Icons.cancel, key: deleteKey), |
| onDeleted: () { |
| calledDelete = true; |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| // Chip width is 48 with padding, 40 without padding, so halfway is at 20. Cancel |
| // icon is 24x24, so since 24 > 20 the split location should be halfway across the |
| // chip, which is at 12 + 8 = 20 from the right side. Since the split is just |
| // slightly less than 50%, 8 from the center of the delete button should hit the |
| // chip, not the delete button. |
| await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(7.0, 0.0)); |
| await tester.pump(); |
| expect(calledDelete, isTrue); |
| calledDelete = false; |
| |
| await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(8.0, 0.0)); |
| await tester.pump(); |
| expect(calledDelete, isFalse); |
| }); |
| |
| testWidgets('Chip elements are ordered horizontally for locale', (WidgetTester tester) async { |
| final UniqueKey iconKey = UniqueKey(); |
| final Widget test = Overlay( |
| initialEntries: <OverlayEntry>[ |
| OverlayEntry( |
| builder: (BuildContext context) { |
| return Material( |
| child: Chip( |
| deleteIcon: Icon(Icons.delete, key: iconKey), |
| onDeleted: () { }, |
| label: const Text('ABC'), |
| ), |
| ); |
| }, |
| ), |
| ], |
| ); |
| |
| await tester.pumpWidget( |
| wrapForChip( |
| child: test, |
| textDirection: TextDirection.rtl, |
| ), |
| ); |
| await tester.pumpAndSettle(const Duration(milliseconds: 500)); |
| expect(tester.getCenter(find.text('ABC')).dx, greaterThan(tester.getCenter(find.byKey(iconKey)).dx)); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: test, |
| ), |
| ); |
| await tester.pumpAndSettle(const Duration(milliseconds: 500)); |
| expect(tester.getCenter(find.text('ABC')).dx, lessThan(tester.getCenter(find.byKey(iconKey)).dx)); |
| }); |
| |
| testWidgets('Chip responds to textScaleFactor', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: const <Widget>[ |
| Chip( |
| avatar: CircleAvatar(child: Text('A')), |
| label: Text('Chip A'), |
| ), |
| Chip( |
| avatar: CircleAvatar(child: Text('B')), |
| label: Text('Chip B'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| // TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs. |
| // https://github.com/flutter/flutter/issues/12357 |
| expect( |
| tester.getSize(find.text('Chip A')), |
| anyOf(const Size(84.0, 14.0), const Size(83.0, 14.0)), |
| ); |
| expect( |
| tester.getSize(find.text('Chip B')), |
| anyOf(const Size(84.0, 14.0), const Size(83.0, 14.0)), |
| ); |
| expect(tester.getSize(find.byType(Chip).first), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0))); |
| expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0))); |
| |
| await tester.pumpWidget( |
| wrapForChip( |
| textScaleFactor: 3.0, |
| child: Column( |
| children: const <Widget>[ |
| Chip( |
| avatar: CircleAvatar(child: Text('A')), |
| label: Text('Chip A'), |
| ), |
| Chip( |
| avatar: CircleAvatar(child: Text('B')), |
| label: Text('Chip B'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| // TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs. |
| // https://github.com/flutter/flutter/issues/12357 |
| expect(tester.getSize(find.text('Chip A')), anyOf(const Size(252.0, 42.0), const Size(251.0, 42.0))); |
| expect(tester.getSize(find.text('Chip B')), anyOf(const Size(252.0, 42.0), const Size(251.0, 42.0))); |
| expect(tester.getSize(find.byType(Chip).first).width, anyOf(310.0, 311.0)); |
| expect(tester.getSize(find.byType(Chip).first).height, equals(50.0)); |
| expect(tester.getSize(find.byType(Chip).last).width, anyOf(310.0, 311.0)); |
| expect(tester.getSize(find.byType(Chip).last).height, equals(50.0)); |
| |
| // Check that individual text scales are taken into account. |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: const <Widget>[ |
| Chip( |
| avatar: CircleAvatar(child: Text('A')), |
| label: Text('Chip A', textScaleFactor: 3.0), |
| ), |
| Chip( |
| avatar: CircleAvatar(child: Text('B')), |
| label: Text('Chip B'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| // TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs. |
| // https://github.com/flutter/flutter/issues/12357 |
| expect(tester.getSize(find.text('Chip A')), anyOf(const Size(252.0, 42.0), const Size(251.0, 42.0))); |
| expect(tester.getSize(find.text('Chip B')), anyOf(const Size(84.0, 14.0), const Size(83.0, 14.0))); |
| expect(tester.getSize(find.byType(Chip).first).width, anyOf(318.0, 319.0)); |
| expect(tester.getSize(find.byType(Chip).first).height, equals(50.0)); |
| expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0))); |
| }); |
| |
| testWidgets('Labels can be non-text widgets', (WidgetTester tester) async { |
| final Key keyA = GlobalKey(); |
| final Key keyB = GlobalKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| avatar: const CircleAvatar(child: Text('A')), |
| label: Text('Chip A', key: keyA), |
| ), |
| Chip( |
| avatar: const CircleAvatar(child: Text('B')), |
| label: SizedBox(key: keyB, width: 10.0, height: 10.0), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| // TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs. |
| // https://github.com/flutter/flutter/issues/12357 |
| expect( |
| tester.getSize(find.byKey(keyA)), |
| anyOf(const Size(84.0, 14.0), const Size(83.0, 14.0)), |
| ); |
| expect(tester.getSize(find.byKey(keyB)), const Size(10.0, 10.0)); |
| expect( |
| tester.getSize(find.byType(Chip).first), |
| anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)), |
| ); |
| expect(tester.getSize(find.byType(Chip).last), const Size(58.0, 48.0)); |
| }); |
| |
| testWidgets('Avatars can be non-circle avatar widgets', (WidgetTester tester) async { |
| final Key keyA = GlobalKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| avatar: SizedBox(key: keyA, width: 20.0, height: 20.0), |
| label: const Text('Chip A'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byKey(keyA)), equals(const Size(20.0, 20.0))); |
| }); |
| |
| testWidgets('Delete icons can be non-icon widgets', (WidgetTester tester) async { |
| final Key keyA = GlobalKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| Chip( |
| deleteIcon: SizedBox(key: keyA, width: 20.0, height: 20.0), |
| label: const Text('Chip A'), |
| onDeleted: () { }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byKey(keyA)), equals(const Size(20.0, 20.0))); |
| }); |
| |
| testWidgets('Chip padding - LTR', (WidgetTester tester) async { |
| final GlobalKey keyA = GlobalKey(); |
| final GlobalKey keyB = GlobalKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Overlay( |
| initialEntries: <OverlayEntry>[ |
| OverlayEntry( |
| builder: (BuildContext context) { |
| return Material( |
| child: Center( |
| child: Chip( |
| avatar: Placeholder(key: keyA), |
| label: SizedBox( |
| key: keyB, |
| width: 40.0, |
| height: 40.0, |
| ), |
| onDeleted: () { }, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.getTopLeft(find.byKey(keyA)), const Offset(332.0, 280.0)); |
| expect(tester.getBottomRight(find.byKey(keyA)), const Offset(372.0, 320.0)); |
| expect(tester.getTopLeft(find.byKey(keyB)), const Offset(380.0, 280.0)); |
| expect(tester.getBottomRight(find.byKey(keyB)), const Offset(420.0, 320.0)); |
| expect(tester.getTopLeft(find.byType(Icon)), const Offset(439.0, 291.0)); |
| expect(tester.getBottomRight(find.byType(Icon)), const Offset(457.0, 309.0)); |
| }); |
| |
| testWidgets('Chip padding - RTL', (WidgetTester tester) async { |
| final GlobalKey keyA = GlobalKey(); |
| final GlobalKey keyB = GlobalKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| textDirection: TextDirection.rtl, |
| child: Overlay( |
| initialEntries: <OverlayEntry>[ |
| OverlayEntry( |
| builder: (BuildContext context) { |
| return Material( |
| child: Center( |
| child: Chip( |
| avatar: Placeholder(key: keyA), |
| label: SizedBox( |
| key: keyB, |
| width: 40.0, |
| height: 40.0, |
| ), |
| onDeleted: () { }, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(tester.getTopLeft(find.byKey(keyA)), const Offset(428.0, 280.0)); |
| expect(tester.getBottomRight(find.byKey(keyA)), const Offset(468.0, 320.0)); |
| expect(tester.getTopLeft(find.byKey(keyB)), const Offset(380.0, 280.0)); |
| expect(tester.getBottomRight(find.byKey(keyB)), const Offset(420.0, 320.0)); |
| expect(tester.getTopLeft(find.byType(Icon)), const Offset(343.0, 291.0)); |
| expect(tester.getBottomRight(find.byType(Icon)), const Offset(361.0, 309.0)); |
| }); |
| |
| testWidgets('Avatar drawer works as expected on RawChip', (WidgetTester tester) async { |
| final GlobalKey labelKey = GlobalKey(); |
| Future<void> pushChip({ Widget? avatar }) async { |
| return tester.pumpWidget( |
| wrapForChip( |
| child: Wrap( |
| children: <Widget>[ |
| RawChip( |
| avatar: avatar, |
| label: Text('Chip', key: labelKey), |
| shape: const StadiumBorder(), |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| // No avatar |
| await pushChip(); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); |
| final GlobalKey avatarKey = GlobalKey(); |
| |
| // Add an avatar |
| await pushChip( |
| avatar: Container( |
| key: avatarKey, |
| color: const Color(0xff000000), |
| width: 40.0, |
| height: 40.0, |
| ), |
| ); |
| // Avatar drawer should start out closed. |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(-20.0, 12.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| // Avatar drawer should start expanding. |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(81.2, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-18.8, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(13.2, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(86.7, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-13.3, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(18.6, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(94.7, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-5.3, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(26.7, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(99.5, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-0.5, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(31.5, epsilon: 0.1)); |
| |
| // Wait for being done with animation, and make sure it didn't change |
| // height. |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0))); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 12.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 17.0))); |
| |
| // Remove the avatar again |
| await pushChip(); |
| // Avatar drawer should start out open. |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0))); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 12.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 17.0))); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| // Avatar drawer should start contracting. |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(102.9, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(2.9, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(34.9, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(98.0, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-2.0, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(30.0, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(84.1, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-15.9, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(16.1, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(80.0, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-20.0, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(12.0, epsilon: 0.1)); |
| |
| // Wait for being done with animation, make sure it didn't change |
| // height, and make sure that the avatar is no longer drawn. |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| expect(find.byKey(avatarKey), findsNothing); |
| }); |
| |
| testWidgets('Delete button drawer works as expected on RawChip', (WidgetTester tester) async { |
| const Key labelKey = Key('label'); |
| const Key deleteButtonKey = Key('delete'); |
| bool wasDeleted = false; |
| Future<void> pushChip({ bool deletable = false }) async { |
| return tester.pumpWidget( |
| wrapForChip( |
| child: Wrap( |
| children: <Widget>[ |
| StatefulBuilder(builder: (BuildContext context, StateSetter setState) { |
| return RawChip( |
| onDeleted: deletable |
| ? () { |
| setState(() { |
| wasDeleted = true; |
| }); |
| } |
| : null, |
| deleteIcon: Container(width: 40.0, height: 40.0, color: Colors.blue, key: deleteButtonKey), |
| label: const Text('Chip', key: labelKey), |
| shape: const StadiumBorder(), |
| ); |
| }), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| // No delete button |
| await pushChip(); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); |
| |
| // Add a delete button |
| await pushChip(deletable: true); |
| // Delete button drawer should start out closed. |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(52.0, 12.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| // Delete button drawer should start expanding. |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(81.2, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(53.2, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(86.7, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(58.7, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(94.7, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(66.7, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(99.5, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(71.5, epsilon: 0.1)); |
| |
| // Wait for being done with animation, and make sure it didn't change |
| // height. |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0))); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 12.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| |
| // Test the tap work for the delete button, but not the rest of the chip. |
| expect(wasDeleted, isFalse); |
| await tester.tap(find.byKey(labelKey)); |
| expect(wasDeleted, isFalse); |
| await tester.tap(find.byKey(deleteButtonKey)); |
| expect(wasDeleted, isTrue); |
| |
| // Remove the delete button again |
| await pushChip(); |
| // Delete button drawer should start out open. |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0))); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 12.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| // Delete button drawer should start contracting. |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(103.8, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(75.8, epsilon: 0.1)); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(102.9, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(74.9, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(101.0, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(73.0, epsilon: 0.1)); |
| |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(97.5, epsilon: 0.1)); |
| expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0))); |
| expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(69.5, epsilon: 0.1)); |
| |
| // Wait for being done with animation, make sure it didn't change |
| // height, and make sure that the delete button is no longer drawn. |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); |
| expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0))); |
| expect(find.byKey(deleteButtonKey), findsNothing); |
| }); |
| |
| testWidgets('Delete button takes up at most half of the chip', (WidgetTester tester) async { |
| final UniqueKey chipKey = UniqueKey(); |
| bool chipPressed = false; |
| bool deletePressed = false; |
| |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Wrap( |
| children: <Widget>[ |
| RawChip( |
| key: chipKey, |
| onPressed: () { |
| chipPressed = true; |
| }, |
| onDeleted: () { |
| deletePressed = true; |
| }, |
| label: const Text(''), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.tapAt(tester.getCenter(find.byKey(chipKey))); |
| await tester.pump(); |
| expect(chipPressed, isTrue); |
| expect(deletePressed, isFalse); |
| chipPressed = false; |
| |
| await tester.tapAt(tester.getCenter(find.byKey(chipKey)) + const Offset(1.0, 0.0)); |
| await tester.pump(); |
| expect(chipPressed, isFalse); |
| expect(deletePressed, isTrue); |
| }); |
| |
| testWidgets('Chip creates centered, unique ripple when label is tapped', (WidgetTester tester) async { |
| final UniqueKey labelKey = UniqueKey(); |
| final UniqueKey deleteButtonKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| chipWithOptionalDeleteButton( |
| labelKey: labelKey, |
| deleteButtonKey: deleteButtonKey, |
| deletable: true, |
| ), |
| ); |
| |
| final RenderBox box = getMaterialBox(tester); |
| |
| // Taps at a location close to the center of the label. |
| final Offset centerOfLabel = tester.getCenter(find.byKey(labelKey)); |
| final Offset tapLocationOfLabel = centerOfLabel + const Offset(-10, -10); |
| final TestGesture gesture = await tester.startGesture(tapLocationOfLabel); |
| await tester.pump(); |
| |
| // Waits for 100 ms. |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // There should be one unique, centered ink ripple. |
| expect(box, ripplePattern(const Offset(163.0, 6.0), 20.9)); |
| expect(box, uniqueRipplePattern(const Offset(163.0, 6.0), 20.9)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for 100 ms again. |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // The ripple should grow, with the same center. |
| expect(box, ripplePattern(const Offset(163.0, 6.0), 41.8)); |
| expect(box, uniqueRipplePattern(const Offset(163.0, 6.0), 41.8)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for a very long time. |
| await tester.pumpAndSettle(); |
| |
| // There should still be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('Delete button is focusable', (WidgetTester tester) async { |
| final GlobalKey labelKey = GlobalKey(); |
| final GlobalKey deleteButtonKey = GlobalKey(); |
| |
| await tester.pumpWidget( |
| chipWithOptionalDeleteButton( |
| labelKey: labelKey, |
| deleteButtonKey: deleteButtonKey, |
| deletable: true, |
| ), |
| ); |
| |
| Focus.of(deleteButtonKey.currentContext!).requestFocus(); |
| await tester.pump(); |
| |
| // They shouldn't have the same focus node. |
| expect(Focus.of(deleteButtonKey.currentContext!), isNot(equals(Focus.of(labelKey.currentContext!)))); |
| expect(Focus.of(deleteButtonKey.currentContext!).hasFocus, isTrue); |
| expect(Focus.of(deleteButtonKey.currentContext!).hasPrimaryFocus, isTrue); |
| // Delete button is a child widget of the Chip, so the Chip should have focus if |
| // the delete button does. |
| expect(Focus.of(labelKey.currentContext!).hasFocus, isTrue); |
| expect(Focus.of(labelKey.currentContext!).hasPrimaryFocus, isFalse); |
| |
| Focus.of(labelKey.currentContext!).requestFocus(); |
| await tester.pump(); |
| |
| expect(Focus.of(deleteButtonKey.currentContext!).hasFocus, isFalse); |
| expect(Focus.of(deleteButtonKey.currentContext!).hasPrimaryFocus, isFalse); |
| expect(Focus.of(labelKey.currentContext!).hasFocus, isTrue); |
| expect(Focus.of(labelKey.currentContext!).hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets('Delete button creates non-centered, unique ripple when tapped', (WidgetTester tester) async { |
| final UniqueKey labelKey = UniqueKey(); |
| final UniqueKey deleteButtonKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| chipWithOptionalDeleteButton( |
| labelKey: labelKey, |
| deleteButtonKey: deleteButtonKey, |
| deletable: true, |
| ), |
| ); |
| |
| final RenderBox box = getMaterialBox(tester); |
| |
| // Taps at a location close to the center of the delete icon. |
| final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey)); |
| final Offset tapLocationOfDeleteButton = centerOfDeleteButton + const Offset(-10, -10); |
| final TestGesture gesture = await tester.startGesture(tapLocationOfDeleteButton); |
| await tester.pump(); |
| |
| // Waits for 200 ms. |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // There should be one unique ink ripple. |
| expect(box, ripplePattern(const Offset(3.0, 3.0), 1.44)); |
| expect(box, uniqueRipplePattern(const Offset(3.0, 3.0), 1.44)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for 200 ms again. |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // The ripple should grow, but the center should move, |
| // Towards the center of the delete icon. |
| expect(box, ripplePattern(const Offset(5.0, 5.0), 4.32)); |
| expect(box, uniqueRipplePattern(const Offset(5.0, 5.0), 4.32)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for a very long time. |
| // This is pressing and holding the delete button. |
| await tester.pumpAndSettle(); |
| |
| // There should be a tooltip. |
| expect(findTooltipContainer('Delete'), findsOneWidget); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('Delete button in a chip with null onPressed creates ripple when tapped', (WidgetTester tester) async { |
| final UniqueKey labelKey = UniqueKey(); |
| final UniqueKey deleteButtonKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| chipWithOptionalDeleteButton( |
| labelKey: labelKey, |
| onPressed: null, |
| deleteButtonKey: deleteButtonKey, |
| deletable: true, |
| ), |
| ); |
| |
| final RenderBox box = getMaterialBox(tester); |
| |
| // Taps at a location close to the center of the delete icon. |
| final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey)); |
| final Offset tapLocationOfDeleteButton = centerOfDeleteButton + const Offset(-10, -10); |
| final TestGesture gesture = await tester.startGesture(tapLocationOfDeleteButton); |
| await tester.pump(); |
| |
| // Waits for 200 ms. |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // There should be one unique ink ripple. |
| expect(box, ripplePattern(const Offset(3.0, 3.0), 1.44)); |
| expect(box, uniqueRipplePattern(const Offset(3.0, 3.0), 1.44)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for 200 ms again. |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // The ripple should grow, but the center should move, |
| // Towards the center of the delete icon. |
| expect(box, ripplePattern(const Offset(5.0, 5.0), 4.32)); |
| expect(box, uniqueRipplePattern(const Offset(5.0, 5.0), 4.32)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for a very long time. |
| // This is pressing and holding the delete button. |
| await tester.pumpAndSettle(); |
| |
| // There should be a tooltip. |
| expect(findTooltipContainer('Delete'), findsOneWidget); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('RTL delete button responds to tap on the left of the chip', (WidgetTester tester) async { |
| // Creates an RTL chip with a delete button. |
| final UniqueKey labelKey = UniqueKey(); |
| final UniqueKey deleteButtonKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| chipWithOptionalDeleteButton( |
| labelKey: labelKey, |
| deleteButtonKey: deleteButtonKey, |
| deletable: true, |
| textDirection: TextDirection.rtl, |
| ), |
| ); |
| |
| // Taps at a location close to the center of the delete icon, |
| // Which is on the left side of the chip. |
| final Offset topLeftOfInkWell = tester.getTopLeft(find.byType(InkWell).first); |
| final Offset tapLocation = topLeftOfInkWell + const Offset(8, 8); |
| final TestGesture gesture = await tester.startGesture(tapLocation); |
| await tester.pump(); |
| |
| await tester.pumpAndSettle(); |
| |
| // The existence of a 'Delete' tooltip indicates the delete icon is tapped, |
| // Instead of the label. |
| expect(findTooltipContainer('Delete'), findsOneWidget); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('Chip without delete button creates correct ripple', (WidgetTester tester) async { |
| // Creates a chip with a delete button. |
| final UniqueKey labelKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| chipWithOptionalDeleteButton( |
| labelKey: labelKey, |
| deletable: false, |
| ), |
| ); |
| |
| final RenderBox box = getMaterialBox(tester); |
| |
| // Taps at a location close to the bottom-right corner of the chip. |
| final Offset bottomRightOfInkWell = tester.getBottomRight(find.byType(InkWell)); |
| final Offset tapLocation = bottomRightOfInkWell + const Offset(-10, -10); |
| final TestGesture gesture = await tester.startGesture(tapLocation); |
| await tester.pump(); |
| |
| // Waits for 100 ms. |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // There should be exactly one ink-creating widget. |
| expect(find.byType(InkWell), findsOneWidget); |
| expect(find.byType(InkResponse), findsNothing); |
| |
| // There should be one unique, centered ink ripple. |
| expect(box, ripplePattern(const Offset(378.0, 22.0), 37.9)); |
| expect(box, uniqueRipplePattern(const Offset(378.0, 22.0), 37.9)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for 100 ms again. |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // The ripple should grow, with the same center. |
| // This indicates that the tap is not on a delete icon. |
| expect(box, ripplePattern(const Offset(378.0, 22.0), 75.8)); |
| expect(box, uniqueRipplePattern(const Offset(378.0, 22.0), 75.8)); |
| |
| // There should be no tooltip. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| // Waits for a very long time. |
| await tester.pumpAndSettle(); |
| |
| // There should still be no tooltip. |
| // This indicates that the tap is not on a delete icon. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('Selection with avatar works as expected on RawChip', (WidgetTester tester) async { |
| bool selected = false; |
| final UniqueKey labelKey = UniqueKey(); |
| Future<void> pushChip({ Widget? avatar, bool selectable = false }) async { |
| return tester.pumpWidget( |
| wrapForChip( |
| child: Wrap( |
| children: <Widget>[ |
| StatefulBuilder(builder: (BuildContext context, StateSetter setState) { |
| return RawChip( |
| avatar: avatar, |
| onSelected: selectable |
| ? (bool value) { |
| setState(() { |
| selected = value; |
| }); |
| } |
| : null, |
| selected: selected, |
| label: Text('Long Chip Label', key: labelKey), |
| shape: const StadiumBorder(), |
| ); |
| }), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| // With avatar, but not selectable. |
| final UniqueKey avatarKey = UniqueKey(); |
| await pushChip( |
| avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey), |
| ); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(258.0, 48.0))); |
| |
| // Turn on selection. |
| await pushChip( |
| avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey), |
| selectable: true, |
| ); |
| await tester.pumpAndSettle(); |
| |
| // Simulate a tap on the label to select the chip. |
| await tester.tap(find.byKey(labelKey)); |
| expect(selected, equals(true)); |
| expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(getSelectProgress(tester), equals(1.0)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pumpAndSettle(); |
| // Simulate another tap on the label to deselect the chip. |
| await tester.tap(find.byKey(labelKey)); |
| expect(selected, equals(false)); |
| expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.875, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.13, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(getSelectProgress(tester), equals(0.0)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| }); |
| |
| testWidgets('Selection without avatar works as expected on RawChip', (WidgetTester tester) async { |
| bool selected = false; |
| final UniqueKey labelKey = UniqueKey(); |
| Future<void> pushChip({ bool selectable = false }) async { |
| return tester.pumpWidget( |
| wrapForChip( |
| child: Wrap( |
| children: <Widget>[ |
| StatefulBuilder(builder: (BuildContext context, StateSetter setState) { |
| return RawChip( |
| onSelected: selectable |
| ? (bool value) { |
| setState(() { |
| selected = value; |
| }); |
| } |
| : null, |
| selected: selected, |
| label: Text('Long Chip Label', key: labelKey), |
| shape: const StadiumBorder(), |
| ); |
| }), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| // Without avatar, but not selectable. |
| await pushChip(); |
| expect(tester.getSize(find.byType(RawChip)), equals(const Size(234.0, 48.0))); |
| |
| // Turn on selection. |
| await pushChip(selectable: true); |
| await tester.pumpAndSettle(); |
| |
| // Simulate a tap on the label to select the chip. |
| await tester.tap(find.byKey(labelKey)); |
| expect(selected, equals(true)); |
| expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.459, epsilon: 0.01)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.92, epsilon: 0.01)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(getSelectProgress(tester), equals(1.0)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pumpAndSettle(); |
| // Simulate another tap on the label to deselect the chip. |
| await tester.tap(find.byKey(labelKey)); |
| expect(selected, equals(false)); |
| expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.875, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.96, epsilon: 0.01)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 20)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.13, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.75, epsilon: 0.01)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(getSelectProgress(tester), equals(0.0)); |
| expect(getAvatarDrawerProgress(tester), equals(0.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| }); |
| |
| testWidgets('Activation works as expected on RawChip', (WidgetTester tester) async { |
| bool selected = false; |
| final UniqueKey labelKey = UniqueKey(); |
| Future<void> pushChip({ Widget? avatar, bool selectable = false }) async { |
| return tester.pumpWidget( |
| wrapForChip( |
| child: Wrap( |
| children: <Widget>[ |
| StatefulBuilder(builder: (BuildContext context, StateSetter setState) { |
| return RawChip( |
| avatar: avatar, |
| onSelected: selectable |
| ? (bool value) { |
| setState(() { |
| selected = value; |
| }); |
| } |
| : null, |
| selected: selected, |
| label: Text('Long Chip Label', key: labelKey), |
| shape: const StadiumBorder(), |
| showCheckmark: false, |
| ); |
| }), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| final UniqueKey avatarKey = UniqueKey(); |
| await pushChip( |
| avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey), |
| selectable: true, |
| ); |
| await tester.pumpAndSettle(); |
| |
| await tester.tap(find.byKey(labelKey)); |
| expect(selected, equals(true)); |
| expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(getSelectProgress(tester), equals(1.0)); |
| expect(getAvatarDrawerProgress(tester), equals(1.0)); |
| expect(getDeleteDrawerProgress(tester), equals(0.0)); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Chip uses ThemeData chip theme if present', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData( |
| platform: TargetPlatform.android, |
| primarySwatch: Colors.red, |
| ); |
| final ChipThemeData chipTheme = theme.chipTheme; |
| |
| Widget buildChip(ChipThemeData data) { |
| return wrapForChip( |
| child: Theme( |
| data: theme, |
| child: const InputChip( |
| label: Text('Label'), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildChip(chipTheme)); |
| |
| final RenderBox materialBox = tester.firstRenderObject<RenderBox>( |
| find.descendant( |
| of: find.byType(RawChip), |
| matching: find.byType(CustomPaint), |
| ), |
| ); |
| |
| expect(materialBox, paints..rrect(color: chipTheme.disabledColor)); |
| }); |
| |
| testWidgets('Chip merges ChipThemeData label style with the provided label style', (WidgetTester tester) async { |
| // The font family should be preserved even if the chip overrides some label style properties |
| final ThemeData theme = ThemeData( |
| fontFamily: 'MyFont', |
| ); |
| |
| Widget buildChip() { |
| return wrapForChip( |
| child: Theme( |
| data: theme, |
| child: const Chip( |
| label: Text('Label'), |
| labelStyle: TextStyle(fontWeight: FontWeight.w200), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildChip()); |
| |
| final TextStyle labelStyle = getLabelStyle(tester, 'Label').style; |
| expect(labelStyle.inherit, false); |
| expect(labelStyle.fontFamily, 'MyFont'); |
| expect(labelStyle.fontWeight, FontWeight.w200); |
| }); |
| |
| testWidgets('ChipTheme labelStyle with inherit:true', (WidgetTester tester) async { |
| Widget buildChip() { |
| return wrapForChip( |
| child: Theme( |
| data: ThemeData.light().copyWith( |
| chipTheme: const ChipThemeData( |
| labelStyle: TextStyle(height: 4), // inherit: true |
| ), |
| ), |
| child: const Chip(label: Text('Label')), // labeStyle: null |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildChip()); |
| final TextStyle labelStyle = getLabelStyle(tester, 'Label').style; |
| expect(labelStyle.inherit, true); // because chipTheme.labelStyle.merge(null) |
| expect(labelStyle.height, 4); |
| }); |
| |
| testWidgets('Chip does not merge inherit:false label style with the theme label style', (WidgetTester tester) async { |
| Widget buildChip() { |
| return wrapForChip( |
| child: Theme( |
| data: ThemeData(fontFamily: 'MyFont'), |
| child: const DefaultTextStyle( |
| style: TextStyle(height: 8), |
| child: Chip( |
| label: Text('Label'), |
| labelStyle: TextStyle(fontWeight: FontWeight.w200, inherit: false), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildChip()); |
| final TextStyle labelStyle = getLabelStyle(tester, 'Label').style; |
| expect(labelStyle.inherit, false); |
| expect(labelStyle.fontFamily, null); |
| expect(labelStyle.height, null); |
| expect(labelStyle.fontWeight, FontWeight.w200); |
| }); |
| |
| testWidgets('Chip size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { |
| final Key key1 = UniqueKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Theme( |
| data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), |
| child: Center( |
| child: RawChip( |
| key: key1, |
| label: const Text('test'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byKey(key1)), const Size(80.0, 48.0)); |
| |
| final Key key2 = UniqueKey(); |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Theme( |
| data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), |
| child: Center( |
| child: RawChip( |
| key: key2, |
| label: const Text('test'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byKey(key2)), const Size(80.0, 32.0)); |
| }); |
| |
| testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async { |
| final ThemeData themeData = ThemeData( |
| platform: TargetPlatform.android, |
| primarySwatch: Colors.blue, |
| ); |
| final ChipThemeData defaultChipTheme = ChipThemeData.fromDefaults( |
| brightness: themeData.brightness, |
| secondaryColor: Colors.blue, |
| labelStyle: themeData.textTheme.bodyLarge!, |
| ); |
| bool value = false; |
| Widget buildApp({ |
| ChipThemeData? chipTheme, |
| Widget? avatar, |
| Widget? deleteIcon, |
| bool isSelectable = true, |
| bool isPressable = false, |
| bool isDeletable = true, |
| bool showCheckmark = true, |
| }) { |
| chipTheme ??= defaultChipTheme; |
| return wrapForChip( |
| child: Theme( |
| data: themeData, |
| child: ChipTheme( |
| data: chipTheme, |
| child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { |
| return RawChip( |
| showCheckmark: showCheckmark, |
| onDeleted: isDeletable ? () { } : null, |
| avatar: avatar, |
| deleteIcon: deleteIcon, |
| isEnabled: isSelectable || isPressable, |
| shape: chipTheme?.shape, |
| selected: isSelectable && value, |
| label: Text('$value'), |
| onSelected: isSelectable |
| ? (bool newValue) { |
| setState(() { |
| value = newValue; |
| }); |
| } |
| : null, |
| onPressed: isPressable |
| ? () { |
| setState(() { |
| value = true; |
| }); |
| } |
| : null, |
| ); |
| }), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildApp()); |
| |
| RenderBox materialBox = getMaterialBox(tester); |
| IconThemeData iconData = getIconData(tester); |
| DefaultTextStyle labelStyle = getLabelStyle(tester, 'false'); |
| |
| // Check default theme for enabled widget. |
| expect(materialBox, paints..rrect(color: defaultChipTheme.backgroundColor)); |
| expect(iconData.color, equals(const Color(0xde000000))); |
| expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); |
| await tester.tap(find.byType(RawChip)); |
| await tester.pumpAndSettle(); |
| materialBox = getMaterialBox(tester); |
| expect(materialBox, paints..rrect(color: defaultChipTheme.selectedColor)); |
| await tester.tap(find.byType(RawChip)); |
| await tester.pumpAndSettle(); |
| |
| // Check default theme with disabled widget. |
| await tester.pumpWidget(buildApp(isSelectable: false)); |
| await tester.pumpAndSettle(); |
| materialBox = getMaterialBox(tester); |
| labelStyle = getLabelStyle(tester, 'false'); |
| expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor)); |
| expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); |
| |
| // Apply a custom theme. |
| const Color customColor1 = Color(0xcafefeed); |
| const Color customColor2 = Color(0xdeadbeef); |
| const Color customColor3 = Color(0xbeefcafe); |
| const Color customColor4 = Color(0xaddedabe); |
| final ChipThemeData customTheme = defaultChipTheme.copyWith( |
| brightness: Brightness.dark, |
| backgroundColor: customColor1, |
| disabledColor: customColor2, |
| selectedColor: customColor3, |
| deleteIconColor: customColor4, |
| ); |
| await tester.pumpWidget(buildApp(chipTheme: customTheme)); |
| await tester.pumpAndSettle(); |
| materialBox = getMaterialBox(tester); |
| iconData = getIconData(tester); |
| labelStyle = getLabelStyle(tester, 'false'); |
| |
| // Check custom theme for enabled widget. |
| expect(materialBox, paints..rrect(color: customTheme.backgroundColor)); |
| expect(iconData.color, equals(customTheme.deleteIconColor)); |
| expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); |
| await tester.tap(find.byType(RawChip)); |
| await tester.pumpAndSettle(); |
| materialBox = getMaterialBox(tester); |
| expect(materialBox, paints..rrect(color: customTheme.selectedColor)); |
| await tester.tap(find.byType(RawChip)); |
| await tester.pumpAndSettle(); |
| |
| // Check custom theme with disabled widget. |
| await tester.pumpWidget(buildApp( |
| chipTheme: customTheme, |
| isSelectable: false, |
| )); |
| await tester.pumpAndSettle(); |
| materialBox = getMaterialBox(tester); |
| labelStyle = getLabelStyle(tester, 'false'); |
| expect(materialBox, paints..rrect(color: customTheme.disabledColor)); |
| expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); |
| }); |
| |
| group('Chip semantics', () { |
| testWidgets('label only', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| |
| await tester.pumpWidget(const MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: Text('test'), |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| semanticsTester.dispose(); |
| }); |
| |
| testWidgets('delete', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: const Text('test'), |
| onDeleted: () { }, |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| ], |
| children: <TestSemantics>[ |
| TestSemantics( |
| tooltip: 'Delete', |
| actions: <SemanticsAction>[SemanticsAction.tap], |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.isButton, |
| SemanticsFlag.isFocusable, |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| semanticsTester.dispose(); |
| }); |
| |
| testWidgets('with onPressed', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: const Text('test'), |
| onPressed: () { }, |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics> [ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[SemanticsAction.tap], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| semanticsTester.dispose(); |
| }); |
| |
| |
| testWidgets('with onSelected', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| bool selected = false; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: const Text('test'), |
| selected: selected, |
| onSelected: (bool value) { |
| selected = value; |
| }, |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[SemanticsAction.tap], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| await tester.tap(find.byType(RawChip)); |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: const Text('test'), |
| selected: selected, |
| onSelected: (bool value) { |
| selected = value; |
| }, |
| ), |
| ), |
| )); |
| |
| expect(selected, true); |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.isFocusable, |
| SemanticsFlag.isSelected, |
| ], |
| actions: <SemanticsAction>[SemanticsAction.tap], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| semanticsTester.dispose(); |
| }); |
| |
| testWidgets('disabled', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: RawChip( |
| isEnabled: false, |
| onPressed: () { }, |
| label: const Text('test'), |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| ], |
| actions: <SemanticsAction>[], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| semanticsTester.dispose(); |
| }); |
| |
| testWidgets('tapEnabled explicitly false', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| |
| await tester.pumpWidget(const MaterialApp( |
| home: Material( |
| child: RawChip( |
| tapEnabled: false, |
| label: Text('test'), |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[], // Must not be a button when tapping is disabled. |
| actions: <SemanticsAction>[], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| semanticsTester.dispose(); |
| }); |
| |
| testWidgets('enabled when tapEnabled and canTap', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| |
| // These settings make a Chip which can be tapped, both in general and at this moment. |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: RawChip( |
| onPressed: () {}, |
| label: const Text('test'), |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| SemanticsFlag.isEnabled, |
| SemanticsFlag.isFocusable, |
| ], |
| actions: <SemanticsAction>[SemanticsAction.tap], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| semanticsTester.dispose(); |
| }); |
| |
| testWidgets('disabled when tapEnabled but not canTap', (WidgetTester tester) async { |
| final SemanticsTester semanticsTester = SemanticsTester(tester); |
| // These settings make a Chip which _could_ be tapped, but not currently (ensures `canTap == false`). |
| await tester.pumpWidget(const MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: Text('test'), |
| ), |
| ), |
| )); |
| |
| expect( |
| semanticsTester, |
| hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'test', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[ |
| SemanticsFlag.hasEnabledState, |
| SemanticsFlag.isButton, |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ignoreTransform: true, |
| ignoreId: true, |
| ignoreRect: true, |
| ), |
| ); |
| |
| semanticsTester.dispose(); |
| }); |
| }); |
| |
| testWidgets('can be tapped outside of chip delete icon', (WidgetTester tester) async { |
| bool deleted = false; |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Row( |
| children: <Widget>[ |
| Chip( |
| materialTapTargetSize: MaterialTapTargetSize.padded, |
| shape: const RoundedRectangleBorder(), |
| avatar: const CircleAvatar(child: Text('A')), |
| label: const Text('Chip A'), |
| onDeleted: () { |
| deleted = true; |
| }, |
| deleteIcon: const Icon(Icons.delete), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.tapAt(tester.getTopRight(find.byType(Chip)) - const Offset(2.0, -2.0)); |
| await tester.pumpAndSettle(); |
| expect(deleted, true); |
| }); |
| |
| testWidgets('Chips can be tapped', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Material( |
| child: RawChip( |
| label: Text('raw chip'), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.byType(RawChip)); |
| expect(tester.takeException(), null); |
| }); |
| |
| testWidgets('Chip elevation and shadow color work correctly', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData( |
| platform: TargetPlatform.android, |
| primarySwatch: Colors.red, |
| ); |
| |
| final ChipThemeData chipTheme = theme.chipTheme; |
| |
| InputChip inputChip = const InputChip(label: Text('Label')); |
| |
| Widget buildChip(ChipThemeData data) { |
| return wrapForChip( |
| child: Theme( |
| data: theme, |
| child: inputChip, |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildChip(chipTheme)); |
| Material material = getMaterial(tester); |
| expect(material.elevation, 0.0); |
| expect(material.shadowColor, Colors.black); |
| |
| inputChip = const InputChip( |
| label: Text('Label'), |
| elevation: 4.0, |
| shadowColor: Colors.green, |
| selectedShadowColor: Colors.blue, |
| ); |
| |
| await tester.pumpWidget(buildChip(chipTheme)); |
| await tester.pumpAndSettle(); |
| material = getMaterial(tester); |
| expect(material.elevation, 4.0); |
| expect(material.shadowColor, Colors.green); |
| |
| inputChip = const InputChip( |
| label: Text('Label'), |
| selected: true, |
| shadowColor: Colors.green, |
| selectedShadowColor: Colors.blue, |
| ); |
| |
| await tester.pumpWidget(buildChip(chipTheme)); |
| await tester.pumpAndSettle(); |
| material = getMaterial(tester); |
| expect(material.shadowColor, Colors.blue); |
| }); |
| |
| testWidgets('can be tapped outside of chip body', (WidgetTester tester) async { |
| bool pressed = false; |
| await tester.pumpWidget( |
| wrapForChip( |
| child: Row( |
| children: <Widget>[ |
| InputChip( |
| materialTapTargetSize: MaterialTapTargetSize.padded, |
| shape: const RoundedRectangleBorder(), |
| avatar: const CircleAvatar(child: Text('A')), |
| label: const Text('Chip A'), |
| onPressed: () { |
| pressed = true; |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.tapAt(tester.getRect(find.byType(InputChip)).topCenter); |
| await tester.pumpAndSettle(); |
| expect(pressed, true); |
| }); |
| |
| testWidgets('is hitTestable', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| wrapForChip( |
| child: InputChip( |
| shape: const RoundedRectangleBorder(), |
| avatar: const CircleAvatar(child: Text('A')), |
| label: const Text('Chip A'), |
| onPressed: () { }, |
| ), |
| ), |
| ); |
| |
| expect(find.byType(InputChip).hitTestable(), findsOneWidget); |
| }); |
| |
| void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { |
| final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material)); |
| expect(materials.length, 2); |
| expect(materials.last.clipBehavior, clipBehavior); |
| } |
| |
| testWidgets('Chip clipBehavior properly passes through to the Material', (WidgetTester tester) async { |
| const Text label = Text('label'); |
| await tester.pumpWidget(wrapForChip(child: const Chip(label: label))); |
| checkChipMaterialClipBehavior(tester, Clip.none); |
| |
| await tester.pumpWidget(wrapForChip(child: const Chip(label: label, clipBehavior: Clip.antiAlias))); |
| checkChipMaterialClipBehavior(tester, Clip.antiAlias); |
| }); |
| |
| testWidgets('selected chip and avatar draw darkened layer within avatar circle', (WidgetTester tester) async { |
| await tester.pumpWidget(wrapForChip(child: const FilterChip( |
| avatar: CircleAvatar(child: Text('t')), |
| label: Text('test'), |
| selected: true, |
| onSelected: null, |
| ))); |
| final RenderBox rawChip = tester.firstRenderObject<RenderBox>( |
| find.descendant( |
| of: find.byType(RawChip), |
| matching: find.byWidgetPredicate((Widget widget) { |
| return widget.runtimeType.toString() == '_ChipRenderWidget'; |
| }), |
| ), |
| ); |
| const Color selectScrimColor = Color(0x60191919); |
| expect(rawChip, paints..path(color: selectScrimColor, includes: <Offset>[ |
| const Offset(10, 10), |
| ], excludes: <Offset>[ |
| const Offset(4, 4), |
| ])); |
| }); |
| |
| testWidgets('Chips should use InkWell instead of InkResponse.', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/28646 |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: ActionChip( |
| onPressed: () { }, |
| label: const Text('action chip'), |
| ), |
| ), |
| ), |
| ); |
| expect(find.byType(InkWell), findsOneWidget); |
| }); |
| |
| testWidgets('Chip uses stateful color for text color in different 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); |
| const Color selectedColor = Color(0x00000005); |
| const Color disabledColor = Color(0x00000006); |
| |
| Color getTextColor(Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled)) { |
| return disabledColor; |
| } |
| |
| if (states.contains(MaterialState.pressed)) { |
| return pressedColor; |
| } |
| |
| if (states.contains(MaterialState.hovered)) { |
| return hoverColor; |
| } |
| |
| if (states.contains(MaterialState.focused)) { |
| return focusedColor; |
| } |
| |
| if (states.contains(MaterialState.selected)) { |
| return selectedColor; |
| } |
| |
| return defaultColor; |
| } |
| |
| Widget chipWidget({ bool enabled = true, bool selected = false }) { |
| return MaterialApp( |
| home: Scaffold( |
| body: Focus( |
| focusNode: focusNode, |
| child: ChoiceChip( |
| label: const Text('Chip'), |
| selected: selected, |
| onSelected: enabled ? (_) {} : null, |
| labelStyle: TextStyle(color: MaterialStateColor.resolveWith(getTextColor)), |
| ), |
| ), |
| ), |
| ); |
| } |
| Color textColor() { |
| return tester.renderObject<RenderParagraph>(find.text('Chip')).text.style!.color!; |
| } |
| |
| // Default, not disabled. |
| await tester.pumpWidget(chipWidget()); |
| expect(textColor(), equals(defaultColor)); |
| |
| // Selected. |
| await tester.pumpWidget(chipWidget(selected: true)); |
| expect(textColor(), selectedColor); |
| |
| // Focused. |
| final FocusNode chipFocusNode = focusNode.children.first; |
| chipFocusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| expect(textColor(), focusedColor); |
| |
| // Hovered. |
| final Offset center = tester.getCenter(find.byType(ChoiceChip)); |
| final TestGesture gesture = await tester.createGesture( |
| kind: PointerDeviceKind.mouse, |
| ); |
| await gesture.addPointer(); |
| await gesture.moveTo(center); |
| await tester.pumpAndSettle(); |
| expect(textColor(), hoverColor); |
| |
| // Pressed. |
| await gesture.down(center); |
| await tester.pumpAndSettle(); |
| expect(textColor(), pressedColor); |
| |
| // Disabled. |
| await tester.pumpWidget(chipWidget(enabled: false)); |
| await tester.pumpAndSettle(); |
| expect(textColor(), disabledColor); |
| }); |
| |
| testWidgets('Chip uses stateful border side color in different 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); |
| const Color selectedColor = Color(0x00000005); |
| const Color disabledColor = Color(0x00000006); |
| |
| BorderSide getBorderSide(Set<MaterialState> states) { |
| Color sideColor = defaultColor; |
| |
| if (states.contains(MaterialState.disabled)) { |
| sideColor = disabledColor; |
| } else if (states.contains(MaterialState.pressed)) { |
| sideColor = pressedColor; |
| } else if (states.contains(MaterialState.hovered)) { |
| sideColor = hoverColor; |
| } else if (states.contains(MaterialState.focused)) { |
| sideColor = focusedColor; |
| } else if (states.contains(MaterialState.selected)) { |
| sideColor = selectedColor; |
| } |
| |
| return BorderSide(color: sideColor); |
| } |
| |
| Widget chipWidget({ bool enabled = true, bool selected = false }) { |
| return MaterialApp( |
| home: Scaffold( |
| body: Focus( |
| focusNode: focusNode, |
| child: ChoiceChip( |
| label: const Text('Chip'), |
| selected: selected, |
| onSelected: enabled ? (_) {} : null, |
| side: _MaterialStateBorderSide(getBorderSide), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // Default, not disabled. |
| await tester.pumpWidget(chipWidget()); |
| expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor)); |
| |
| // Selected. |
| await tester.pumpWidget(chipWidget(selected: true)); |
| expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor)); |
| |
| // Focused. |
| final FocusNode chipFocusNode = focusNode.children.first; |
| chipFocusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor)); |
| |
| // Hovered. |
| final Offset center = tester.getCenter(find.byType(ChoiceChip)); |
| final TestGesture gesture = await tester.createGesture( |
| kind: PointerDeviceKind.mouse, |
| ); |
| await gesture.addPointer(); |
| await gesture.moveTo(center); |
| await tester.pumpAndSettle(); |
| expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor)); |
| |
| // Pressed. |
| await gesture.down(center); |
| await tester.pumpAndSettle(); |
| expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor)); |
| |
| // Disabled. |
| await tester.pumpWidget(chipWidget(enabled: false)); |
| await tester.pumpAndSettle(); |
| expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor)); |
| }); |
| |
| testWidgets('Chip uses stateful border side color from resolveWith', (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); |
| const Color selectedColor = Color(0x00000005); |
| const Color disabledColor = Color(0x00000006); |
| |
| BorderSide getBorderSide(Set<MaterialState> states) { |
| Color sideColor =<
|