Unverified Commit cf89a787 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fix various problems with Chip delete button. (#90531)

parent 6fb73814
...@@ -11,7 +11,6 @@ import 'chip_theme.dart'; ...@@ -11,7 +11,6 @@ import 'chip_theme.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart'; import 'debug.dart';
import 'feedback.dart';
import 'icons.dart'; import 'icons.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
...@@ -1589,8 +1588,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1589,8 +1588,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
late Animation<double> enableAnimation; late Animation<double> enableAnimation;
late Animation<double> selectionFade; late Animation<double> selectionFade;
final GlobalKey deleteIconKey = GlobalKey();
bool get hasDeleteButton => widget.onDeleted != null; bool get hasDeleteButton => widget.onDeleted != null;
bool get hasAvatar => widget.avatar != null; bool get hasAvatar => widget.avatar != null;
...@@ -1795,7 +1792,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1795,7 +1792,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
BuildContext context, BuildContext context,
ThemeData theme, ThemeData theme,
ChipThemeData chipTheme, ChipThemeData chipTheme,
GlobalKey deleteIconKey,
) { ) {
if (!hasDeleteButton) { if (!hasDeleteButton) {
return null; return null;
...@@ -1806,15 +1802,12 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1806,15 +1802,12 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
child: _wrapWithTooltip( child: _wrapWithTooltip(
widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip, widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip,
widget.onDeleted, widget.onDeleted,
GestureDetector( InkWell(
key: deleteIconKey, // Radius should be slightly less than the full size of the chip.
behavior: HitTestBehavior.opaque, radius: (_kChipHeight + (widget.padding?.vertical ?? 0.0)) * .45,
onTap: widget.isEnabled // Keeps the splash from being constrained to the icon alone.
? () { splashFactory: _UnconstrainedInkSplashFactory(Theme.of(context).splashFactory),
Feedback.forTap(context); onTap: widget.isEnabled ? widget.onDeleted : null,
widget.onDeleted!();
}
: null,
child: IconTheme( child: IconTheme(
data: theme.iconTheme.copyWith( data: theme.iconTheme.copyWith(
color: widget.deleteIconColor ?? chipTheme.deleteIconColor, color: widget.deleteIconColor ?? chipTheme.deleteIconColor,
...@@ -1878,11 +1871,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1878,11 +1871,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
onTapDown: canTap ? _handleTapDown : null, onTapDown: canTap ? _handleTapDown : null,
onTapCancel: canTap ? _handleTapCancel : null, onTapCancel: canTap ? _handleTapCancel : null,
onHover: canTap ? updateMaterialState(MaterialState.hovered) : null, onHover: canTap ? updateMaterialState(MaterialState.hovered) : null,
splashFactory: _LocationAwareInkRippleFactory(
hasDeleteButton,
context,
deleteIconKey,
),
customBorder: resolvedShape, customBorder: resolvedShape,
child: AnimatedBuilder( child: AnimatedBuilder(
animation: Listenable.merge(<Listenable>[selectController, enableController]), animation: Listenable.merge(<Listenable>[selectController, enableController]),
...@@ -1916,7 +1904,7 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1916,7 +1904,7 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
deleteIcon: AnimatedSwitcher( deleteIcon: AnimatedSwitcher(
duration: _kDrawerDuration, duration: _kDrawerDuration,
switchInCurve: Curves.fastOutSlowIn, switchInCurve: Curves.fastOutSlowIn,
child: _buildDeleteIcon(context, theme, chipTheme, deleteIconKey), child: _buildDeleteIcon(context, theme, chipTheme),
), ),
brightness: chipTheme.brightness, brightness: chipTheme.brightness,
padding: (widget.padding ?? chipTheme.padding).resolve(textDirection), padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
...@@ -2531,13 +2519,14 @@ class _RenderChip extends RenderBox { ...@@ -2531,13 +2519,14 @@ class _RenderChip extends RenderBox {
if (!size.contains(position)) { if (!size.contains(position)) {
return false; return false;
} }
final bool tapIsOnDeleteIcon = _tapIsOnDeleteIcon( final bool hitIsOnDeleteIcon = deleteIcon != null && _hitIsOnDeleteIcon(
hasDeleteButton: deleteIcon != null, padding: theme.padding,
tapPosition: position, tapPosition: position,
chipSize: size, chipSize: size,
deleteButtonSize: deleteIcon!.size,
textDirection: textDirection!, textDirection: textDirection!,
); );
final RenderBox? hitTestChild = tapIsOnDeleteIcon final RenderBox? hitTestChild = hitIsOnDeleteIcon
? (deleteIcon ?? label ?? avatar) ? (deleteIcon ?? label ?? avatar)
: (label ?? avatar); : (label ?? avatar);
...@@ -2924,16 +2913,10 @@ class _ChipSizes { ...@@ -2924,16 +2913,10 @@ class _ChipSizes {
final Offset densityAdjustment; final Offset densityAdjustment;
} }
class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory { class _UnconstrainedInkSplashFactory extends InteractiveInkFeatureFactory {
const _LocationAwareInkRippleFactory( const _UnconstrainedInkSplashFactory(this.parentFactory);
this.hasDeleteButton,
this.chipContext,
this.deleteIconKey,
);
final bool hasDeleteButton; final InteractiveInkFeatureFactory parentFactory;
final BuildContext chipContext;
final GlobalKey deleteIconKey;
@override @override
InteractiveInkFeature create({ InteractiveInkFeature create({
...@@ -2949,61 +2932,55 @@ class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory { ...@@ -2949,61 +2932,55 @@ class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory {
double? radius, double? radius,
VoidCallback? onRemoved, VoidCallback? onRemoved,
}) { }) {
return parentFactory.create(
final bool tapIsOnDeleteIcon = _tapIsOnDeleteIcon(
hasDeleteButton: hasDeleteButton,
tapPosition: position,
chipSize: chipContext.size!,
textDirection: textDirection,
);
final BuildContext splashContext = tapIsOnDeleteIcon
? deleteIconKey.currentContext!
: chipContext;
final InteractiveInkFeatureFactory splashFactory = Theme.of(splashContext).splashFactory;
if (tapIsOnDeleteIcon) {
final RenderBox currentBox = referenceBox;
referenceBox = deleteIconKey.currentContext!.findRenderObject()! as RenderBox;
position = referenceBox.globalToLocal(currentBox.localToGlobal(position));
containedInkWell = false;
}
return splashFactory.create(
controller: controller, controller: controller,
referenceBox: referenceBox, referenceBox: referenceBox,
position: position, position: position,
color: color, color: color,
textDirection: textDirection, containedInkWell: false,
containedInkWell: containedInkWell,
rectCallback: rectCallback, rectCallback: rectCallback,
borderRadius: borderRadius, borderRadius: borderRadius,
customBorder: customBorder, customBorder: customBorder,
radius: radius, radius: radius,
onRemoved: onRemoved, onRemoved: onRemoved,
textDirection: textDirection,
); );
} }
} }
bool _tapIsOnDeleteIcon({ bool _hitIsOnDeleteIcon({
required bool hasDeleteButton, required EdgeInsetsGeometry padding,
required Offset tapPosition, required Offset tapPosition,
required Size chipSize, required Size chipSize,
required Size deleteButtonSize,
required TextDirection textDirection, required TextDirection textDirection,
}) { }) {
bool tapIsOnDeleteIcon; // The chipSize includes the padding, so we need to deflate the size and adjust the
if (!hasDeleteButton) { // tap position to account for the padding.
tapIsOnDeleteIcon = false; final EdgeInsets resolvedPadding = padding.resolve(textDirection);
} else { final Size deflatedSize = resolvedPadding.deflateSize(chipSize);
switch (textDirection) { final Offset adjustedPosition = tapPosition - Offset(resolvedPadding.left, resolvedPadding.top);
case TextDirection.ltr: // The delete button hit area should be at least the width of the delete button,
tapIsOnDeleteIcon = tapPosition.dx / chipSize.width > 0.66; // but, if there's room, up to 24 pixels from the center of the delete icon
break; // (corresponding to part of a 48x48 square that Material would prefer for touch
case TextDirection.rtl: // targets), but no more than half of the overall size of the chip when the chip is
tapIsOnDeleteIcon = tapPosition.dx / chipSize.width < 0.33; // small.
break; //
} // This isn't affected by materialTapTargetSize because it only applies to the
// width of the tappable region within the chip, not outside of the chip, which is
// handled elsewhere. Also because delete buttons aren't specified to be used on
// touch devices, only desktop devices.
final double accessibleDeleteButtonWidth = math.max(
deleteButtonSize.width,
math.min(
deflatedSize.width * 0.5,
24.0 + deleteButtonSize.width / 2.0,
),
);
switch (textDirection) {
case TextDirection.ltr:
return adjustedPosition.dx >= deflatedSize.width - accessibleDeleteButtonWidth;
case TextDirection.rtl:
return adjustedPosition.dx <= accessibleDeleteButtonWidth;
} }
return tapIsOnDeleteIcon;
} }
...@@ -191,20 +191,23 @@ void _expectCheckmarkColor(Finder finder, Color color) { ...@@ -191,20 +191,23 @@ void _expectCheckmarkColor(Finder finder, Color color) {
); );
} }
void _doNothing() {}
Widget _chipWithOptionalDeleteButton({ Widget _chipWithOptionalDeleteButton({
UniqueKey? deleteButtonKey, Key? deleteButtonKey,
UniqueKey? labelKey, Key? labelKey,
required bool deletable, required bool deletable,
TextDirection textDirection = TextDirection.ltr, TextDirection textDirection = TextDirection.ltr,
bool hasDeleteButtonTooltip = true, bool hasDeleteButtonTooltip = true,
VoidCallback? onPressed = _doNothing,
}) { }) {
return _wrapForChip( return _wrapForChip(
textDirection: textDirection, textDirection: textDirection,
child: Wrap( child: Wrap(
children: <Widget>[ children: <Widget>[
RawChip( RawChip(
onPressed: () {}, onPressed: onPressed,
onDeleted: deletable ? () {} : null, onDeleted: deletable ? _doNothing : null,
deleteIcon: Icon(Icons.close, key: deleteButtonKey), deleteIcon: Icon(Icons.close, key: deleteButtonKey),
useDeleteButtonTooltip: hasDeleteButtonTooltip, useDeleteButtonTooltip: hasDeleteButtonTooltip,
label: Text( label: Text(
...@@ -526,6 +529,59 @@ void main() { ...@@ -526,6 +529,59 @@ void main() {
}, },
); );
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.delete, key: deleteKey),
onDeleted: () {
calledDelete = true;
},
),
],
),
),
);
await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(12.0, 0.0));
await tester.pump();
expect(calledDelete, isTrue);
calledDelete = false;
await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(13.0, 0.0));
await tester.pump();
expect(calledDelete, isFalse);
});
testWidgets('Chip elements are ordered horizontally for locale', (WidgetTester tester) async { testWidgets('Chip elements are ordered horizontally for locale', (WidgetTester tester) async {
final UniqueKey iconKey = UniqueKey(); final UniqueKey iconKey = UniqueKey();
final Widget test = Overlay( final Widget test = Overlay(
...@@ -1022,7 +1078,6 @@ void main() { ...@@ -1022,7 +1078,6 @@ void main() {
}); });
testWidgets('Chip creates centered, unique ripple when label is tapped', (WidgetTester tester) async { testWidgets('Chip creates centered, unique ripple when label is tapped', (WidgetTester tester) async {
// Creates a chip with a delete button.
final UniqueKey labelKey = UniqueKey(); final UniqueKey labelKey = UniqueKey();
final UniqueKey deleteButtonKey = UniqueKey(); final UniqueKey deleteButtonKey = UniqueKey();
...@@ -1045,10 +1100,6 @@ void main() { ...@@ -1045,10 +1100,6 @@ void main() {
// Waits for 100 ms. // Waits for 100 ms.
await tester.pump(const Duration(milliseconds: 100)); 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. // There should be one unique, centered ink ripple.
expect(box, ripplePattern(const Offset(163.0, 6.0), 20.9)); expect(box, ripplePattern(const Offset(163.0, 6.0), 20.9));
expect(box, uniqueRipplePattern(const Offset(163.0, 6.0), 20.9)); expect(box, uniqueRipplePattern(const Offset(163.0, 6.0), 20.9));
...@@ -1075,8 +1126,40 @@ void main() { ...@@ -1075,8 +1126,40 @@ void main() {
await gesture.up(); 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 { testWidgets('Delete button creates non-centered, unique ripple when tapped', (WidgetTester tester) async {
// Creates a chip with a delete button.
final UniqueKey labelKey = UniqueKey(); final UniqueKey labelKey = UniqueKey();
final UniqueKey deleteButtonKey = UniqueKey(); final UniqueKey deleteButtonKey = UniqueKey();
...@@ -1100,13 +1183,63 @@ void main() { ...@@ -1100,13 +1183,63 @@ void main() {
await tester.pump(const Duration(milliseconds: 100)); await tester.pump(const Duration(milliseconds: 100));
await tester.pump(const Duration(milliseconds: 100)); await tester.pump(const Duration(milliseconds: 100));
// There should be exactly one ink-creating widget. // There should be one unique ink ripple.
expect(find.byType(InkWell), findsOneWidget); expect(box, ripplePattern(const Offset(3.0, 3.0), 1.44));
expect(find.byType(InkResponse), findsNothing); 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. // There should be one unique ink ripple.
expect(box, ripplePattern(const Offset(3.0, 3.0), 3.5)); expect(box, ripplePattern(const Offset(3.0, 3.0), 1.44));
expect(box, uniqueRipplePattern(const Offset(3.0, 3.0), 3.5)); expect(box, uniqueRipplePattern(const Offset(3.0, 3.0), 1.44));
// There should be no tooltip. // There should be no tooltip.
expect(findTooltipContainer('Delete'), findsNothing); expect(findTooltipContainer('Delete'), findsNothing);
...@@ -1117,8 +1250,8 @@ void main() { ...@@ -1117,8 +1250,8 @@ void main() {
// The ripple should grow, but the center should move, // The ripple should grow, but the center should move,
// Towards the center of the delete icon. // Towards the center of the delete icon.
expect(box, ripplePattern(const Offset(5.0, 5.0), 10.5)); expect(box, ripplePattern(const Offset(5.0, 5.0), 4.32));
expect(box, uniqueRipplePattern(const Offset(5.0, 5.0), 10.5)); expect(box, uniqueRipplePattern(const Offset(5.0, 5.0), 4.32));
// There should be no tooltip. // There should be no tooltip.
expect(findTooltipContainer('Delete'), findsNothing); expect(findTooltipContainer('Delete'), findsNothing);
...@@ -1149,7 +1282,7 @@ void main() { ...@@ -1149,7 +1282,7 @@ void main() {
// Taps at a location close to the center of the delete icon, // Taps at a location close to the center of the delete icon,
// Which is on the left side of the chip. // Which is on the left side of the chip.
final Offset topLeftOfInkWell = tester.getTopLeft(find.byType(InkWell)); final Offset topLeftOfInkWell = tester.getTopLeft(find.byType(InkWell).first);
final Offset tapLocation = topLeftOfInkWell + const Offset(8, 8); final Offset tapLocation = topLeftOfInkWell + const Offset(8, 8);
final TestGesture gesture = await tester.startGesture(tapLocation); final TestGesture gesture = await tester.startGesture(tapLocation);
await tester.pump(); await tester.pump();
...@@ -1236,7 +1369,7 @@ void main() { ...@@ -1236,7 +1369,7 @@ void main() {
} }
: null, : null,
selected: selected, selected: selected,
label: Text('Chip', key: labelKey), label: Text('Long Chip Label', key: labelKey),
shape: const StadiumBorder(), shape: const StadiumBorder(),
showCheckmark: true, showCheckmark: true,
tapEnabled: true, tapEnabled: true,
...@@ -1254,7 +1387,7 @@ void main() { ...@@ -1254,7 +1387,7 @@ void main() {
await pushChip( await pushChip(
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey), avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
); );
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0))); expect(tester.getSize(find.byType(RawChip)), equals(const Size(258.0, 48.0)));
// Turn on selection. // Turn on selection.
await pushChip( await pushChip(
...@@ -1318,7 +1451,7 @@ void main() { ...@@ -1318,7 +1451,7 @@ void main() {
} }
: null, : null,
selected: selected, selected: selected,
label: Text('Chip', key: labelKey), label: Text('Long Chip Label', key: labelKey),
shape: const StadiumBorder(), shape: const StadiumBorder(),
showCheckmark: true, showCheckmark: true,
tapEnabled: true, tapEnabled: true,
...@@ -1333,7 +1466,7 @@ void main() { ...@@ -1333,7 +1466,7 @@ void main() {
// Without avatar, but not selectable. // Without avatar, but not selectable.
await pushChip(); await pushChip();
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0))); expect(tester.getSize(find.byType(RawChip)), equals(const Size(234.0, 48.0)));
// Turn on selection. // Turn on selection.
await pushChip(selectable: true); await pushChip(selectable: true);
...@@ -1395,7 +1528,7 @@ void main() { ...@@ -1395,7 +1528,7 @@ void main() {
} }
: null, : null,
selected: selected, selected: selected,
label: Text('Chip', key: labelKey), label: Text('Long Chip Label', key: labelKey),
shape: const StadiumBorder(), shape: const StadiumBorder(),
showCheckmark: false, showCheckmark: false,
tapEnabled: true, tapEnabled: true,
...@@ -1737,6 +1870,7 @@ void main() { ...@@ -1737,6 +1870,7 @@ void main() {
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
flags: <SemanticsFlag>[ flags: <SemanticsFlag>[
SemanticsFlag.isButton, SemanticsFlag.isButton,
SemanticsFlag.isFocusable,
], ],
), ),
], ],
...@@ -3196,7 +3330,7 @@ void main() { ...@@ -3196,7 +3330,7 @@ void main() {
// Tap at the delete icon of the chip, which is at the right // Tap at the delete icon of the chip, which is at the right
// side of the chip // side of the chip
final Offset topRightOfInkwell = tester.getTopLeft(find.byType(InkWell)); final Offset topRightOfInkwell = tester.getTopLeft(find.byType(InkWell).first);
final Offset tapLocationOfDeleteButton = topRightOfInkwell + const Offset(8, 8); final Offset tapLocationOfDeleteButton = topRightOfInkwell + const Offset(8, 8);
final TestGesture tapGesture = await tester.startGesture(tapLocationOfDeleteButton); final TestGesture tapGesture = await tester.startGesture(tapLocationOfDeleteButton);
...@@ -3212,7 +3346,7 @@ void main() { ...@@ -3212,7 +3346,7 @@ void main() {
}); });
testWidgets('intrinsicHeight implementation meets constraints', (WidgetTester tester) async { testWidgets('intrinsicHeight implementation meets constraints', (WidgetTester tester) async {
// Regression text for https://github.com/flutter/flutter/issues/49478. // Regression test for https://github.com/flutter/flutter/issues/49478.
await tester.pumpWidget(_wrapForChip( await tester.pumpWidget(_wrapForChip(
child: const Chip( child: const Chip(
label: Text('text'), label: Text('text'),
......
...@@ -1399,8 +1399,7 @@ class _SomethingPaintPredicate extends _PaintPredicate { ...@@ -1399,8 +1399,7 @@ class _SomethingPaintPredicate extends _PaintPredicate {
currentCall = call.current; currentCall = call.current;
if (!currentCall.invocation.isMethod) if (!currentCall.invocation.isMethod)
throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call'; throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call';
call.moveNext(); } while (call.moveNext() && !_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments));
} while (!_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments));
} }
bool _runPredicate(Symbol methodName, List<dynamic> arguments) { bool _runPredicate(Symbol methodName, List<dynamic> arguments) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment