Skip to content

Commit 788614d

Browse files
authored
Fix "Delete" tooltip is shown disabled on chips with onDeleted callback (#141770)
fixes [Disabled chips with `onDeleted` callback shows "Delete" tooltip on hover](flutter/flutter#141336) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Example(), ); } } class Example extends StatefulWidget { const Example({super.key}); @OverRide State<Example> createState() => _ExampleState(); } class _ExampleState extends State<Example> { bool _isEnable = false; @OverRide Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ RawChip( label: const Text('RawChip'), onPressed: () {}, isEnabled: _isEnable, onDeleted: () {}, ), FilterChip( label: const Text('FilterChip'), selected: false, onSelected: _isEnable ? (bool value) {} : null, onDeleted: () {}, ), InputChip( label: const Text('InputChip'), isEnabled: _isEnable, onDeleted: () {}, ), ], ), ), floatingActionButton: FloatingActionButton.extended( onPressed: () { setState(() { _isEnable = !_isEnable; }); }, label: Text(_isEnable ? 'Disable' : 'Enable'), ), ); } } ``` </details> ### Preview | Before | After | | --------------- | --------------- | | <img src="https://github.com/flutter/flutter/assets/48603081/f80ae5f7-0a6d-4041-ade3-cbc2b5c78188" height="450" /> | <img src="https://github.com/flutter/flutter/assets/48603081/04e62854-e3f1-4b65-9753-183d288f3cfe" height="450" /> |
1 parent 6f7aed5 commit 788614d

4 files changed

Lines changed: 114 additions & 4 deletions

File tree

packages/flutter/lib/src/material/chip.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ abstract interface class DeletableChipAttributes {
280280
///
281281
/// If null, the default [MaterialLocalizations.deleteButtonTooltip] will be
282282
/// used.
283+
///
284+
/// If the chip is disabled, the delete button tooltip will not be shown.
283285
String? get deleteButtonTooltipMessage;
284286
}
285287

@@ -1128,7 +1130,7 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
11281130
child: _wrapWithTooltip(
11291131
tooltip: widget.deleteButtonTooltipMessage
11301132
?? MaterialLocalizations.of(context).deleteButtonTooltip,
1131-
enabled: widget.onDeleted != null,
1133+
enabled: widget.isEnabled && widget.onDeleted != null,
11321134
child: InkWell(
11331135
// Radius should be slightly less than the full size of the chip.
11341136
radius: (_kChipHeight + (widget.padding?.vertical ?? 0.0)) * .45,

packages/flutter/test/material/chip_test.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5112,7 +5112,7 @@ void main() {
51125112
expect(tester.takeException(), isNull);
51135113
});
51145114

5115-
testWidgets('Delete button is visible RawChip is disabled', (WidgetTester tester) async {
5115+
testWidgets('Delete button is visible on disabled RawChip', (WidgetTester tester) async {
51165116
await tester.pumpWidget(
51175117
wrapForChip(
51185118
child: RawChip(
@@ -5127,6 +5127,36 @@ void main() {
51275127
expectLater(find.byType(RawChip), matchesGoldenFile('raw_chip.disabled.delete_button.png'));
51285128
});
51295129

5130+
testWidgets('Delete button tooltip is not shown on disabled RawChip', (WidgetTester tester) async {
5131+
Widget buildChip({ bool enabled = true }) {
5132+
return wrapForChip(
5133+
child: RawChip(
5134+
isEnabled: enabled,
5135+
label: const Text('Label'),
5136+
onDeleted: () { },
5137+
)
5138+
);
5139+
}
5140+
5141+
// Test enabled chip.
5142+
await tester.pumpWidget(buildChip());
5143+
5144+
final Offset deleteButtonLocation = tester.getCenter(find.byType(Icon));
5145+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
5146+
await gesture.moveTo(deleteButtonLocation);
5147+
await tester.pump();
5148+
5149+
// Delete button tooltip should be visible.
5150+
expect(findTooltipContainer('Delete'), findsOneWidget);
5151+
5152+
// Test disabled chip.
5153+
await tester.pumpWidget(buildChip(enabled: false));
5154+
await tester.pump();
5155+
5156+
// Delete button tooltip should not be visible.
5157+
expect(findTooltipContainer('Delete'), findsNothing);
5158+
});
5159+
51305160
group('Material 2', () {
51315161
// These tests are only relevant for Material 2. Once Material 2
51325162
// support is deprecated and the APIs are removed, these tests

packages/flutter/test/material/filter_chip_test.dart

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
library;
99

1010
import 'package:flutter/foundation.dart';
11+
import 'package:flutter/gestures.dart';
1112
import 'package:flutter/material.dart';
1213
import 'package:flutter_test/flutter_test.dart';
1314

@@ -130,6 +131,14 @@ DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
130131
);
131132
}
132133

134+
// Finds any container of a tooltip.
135+
Finder findTooltipContainer(String tooltipText) {
136+
return find.ancestor(
137+
of: find.text(tooltipText),
138+
matching: find.byType(Container),
139+
);
140+
}
141+
133142
void main() {
134143
testWidgets('Material2 - FilterChip defaults', (WidgetTester tester) async {
135144
final ThemeData theme = ThemeData(useMaterial3: false);
@@ -1040,7 +1049,7 @@ void main() {
10401049
feedback.dispose();
10411050
});
10421051

1043-
testWidgets('Delete button is visible FilterChip is disabled', (WidgetTester tester) async {
1052+
testWidgets('Delete button is visible on disabled FilterChip', (WidgetTester tester) async {
10441053
await tester.pumpWidget(
10451054
wrapForChip(
10461055
child: FilterChip(
@@ -1054,4 +1063,34 @@ void main() {
10541063
// Delete button should be visible.
10551064
expectLater(find.byType(RawChip), matchesGoldenFile('filter_chip.disabled.delete_button.png'));
10561065
});
1066+
1067+
testWidgets('Delete button tooltip is not shown on disabled FilterChip', (WidgetTester tester) async {
1068+
Widget buildChip({ bool enabled = true }) {
1069+
return wrapForChip(
1070+
child: FilterChip(
1071+
onSelected: enabled ? (bool value) { } : null,
1072+
label: const Text('Label'),
1073+
onDeleted: () { },
1074+
)
1075+
);
1076+
}
1077+
1078+
// Test enabled chip.
1079+
await tester.pumpWidget(buildChip());
1080+
1081+
final Offset deleteButtonLocation = tester.getCenter(find.byType(Icon));
1082+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1083+
await gesture.moveTo(deleteButtonLocation);
1084+
await tester.pump();
1085+
1086+
// Delete button tooltip should be visible.
1087+
expect(findTooltipContainer('Delete'), findsOneWidget);
1088+
1089+
// Test disabled chip.
1090+
await tester.pumpWidget(buildChip(enabled: false));
1091+
await tester.pump();
1092+
1093+
// Delete button tooltip should not be visible.
1094+
expect(findTooltipContainer('Delete'), findsNothing);
1095+
});
10571096
}

packages/flutter/test/material/input_chip_test.dart

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@Tags(<String>['reduced-test-set'])
88
library;
99

10+
import 'package:flutter/gestures.dart';
1011
import 'package:flutter/material.dart';
1112
import 'package:flutter_test/flutter_test.dart';
1213

@@ -113,6 +114,14 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
113114
expect(materials.last.clipBehavior, clipBehavior);
114115
}
115116

117+
// Finds any container of a tooltip.
118+
Finder findTooltipContainer(String tooltipText) {
119+
return find.ancestor(
120+
of: find.text(tooltipText),
121+
matching: find.byType(Container),
122+
);
123+
}
124+
116125
void main() {
117126
testWidgets('InputChip.color resolves material states', (WidgetTester tester) async {
118127
const Color disabledSelectedColor = Color(0xffffff00);
@@ -417,7 +426,7 @@ void main() {
417426
expect(getIconData(tester).color, const Color(0xff00ff00));
418427
});
419428

420-
testWidgets('Delete button is visible InputChip is disabled', (WidgetTester tester) async {
429+
testWidgets('Delete button is visible on disabled InputChip', (WidgetTester tester) async {
421430
await tester.pumpWidget(
422431
wrapForChip(
423432
child: InputChip(
@@ -431,4 +440,34 @@ void main() {
431440
// Delete button should be visible.
432441
expectLater(find.byType(RawChip), matchesGoldenFile('input_chip.disabled.delete_button.png'));
433442
});
443+
444+
testWidgets('Delete button tooltip is not shown on disabled InputChip', (WidgetTester tester) async {
445+
Widget buildChip({ bool enabled = true }) {
446+
return wrapForChip(
447+
child: InputChip(
448+
isEnabled: enabled,
449+
label: const Text('Label'),
450+
onDeleted: () { },
451+
)
452+
);
453+
}
454+
455+
// Test enabled chip.
456+
await tester.pumpWidget(buildChip());
457+
458+
final Offset deleteButtonLocation = tester.getCenter(find.byType(Icon));
459+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
460+
await gesture.moveTo(deleteButtonLocation);
461+
await tester.pump();
462+
463+
// Delete button tooltip should be visible.
464+
expect(findTooltipContainer('Delete'), findsOneWidget);
465+
466+
// Test disabled chip.
467+
await tester.pumpWidget(buildChip(enabled: false));
468+
await tester.pump();
469+
470+
// Delete button tooltip should not be visible.
471+
expect(findTooltipContainer('Delete'), findsNothing);
472+
});
434473
}

0 commit comments

Comments
 (0)