1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:ui'; |
6 | |
7 | import 'package:flutter/material.dart'; |
8 | import 'package:flutter/rendering.dart'; |
9 | import 'package:flutter/services.dart'; |
10 | import 'package:flutter_test/flutter_test.dart'; |
11 | |
12 | RenderBox getMaterialBox(WidgetTester tester, Finder type) { |
13 | return tester.firstRenderObject<RenderBox>( |
14 | find.descendant(of: type, matching: find.byType(CustomPaint)), |
15 | ); |
16 | } |
17 | |
18 | Material getMaterial(WidgetTester tester) { |
19 | return tester.widget<Material>( |
20 | find.descendant(of: find.byType(ChoiceChip), matching: find.byType(Material)), |
21 | ); |
22 | } |
23 | |
24 | IconThemeData getIconData(WidgetTester tester) { |
25 | final IconTheme iconTheme = tester.firstWidget( |
26 | find.descendant(of: find.byType(RawChip), matching: find.byType(IconTheme)), |
27 | ); |
28 | return iconTheme.data; |
29 | } |
30 | |
31 | DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { |
32 | return tester.widget( |
33 | find.ancestor(of: find.text(labelText), matching: find.byType(DefaultTextStyle)).first, |
34 | ); |
35 | } |
36 | |
37 | /// Adds the basic requirements for a Chip. |
38 | Widget wrapForChip({ |
39 | required Widget child, |
40 | TextDirection textDirection = TextDirection.ltr, |
41 | TextScaler textScaler = TextScaler.noScaling, |
42 | Brightness brightness = Brightness.light, |
43 | bool? useMaterial3, |
44 | }) { |
45 | return MaterialApp( |
46 | theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3), |
47 | home: Directionality( |
48 | textDirection: textDirection, |
49 | child: MediaQuery( |
50 | data: MediaQueryData(textScaler: textScaler), |
51 | child: Material(child: child), |
52 | ), |
53 | ), |
54 | ); |
55 | } |
56 | |
57 | void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { |
58 | final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material)); |
59 | // There should be two Material widgets, first Material is from the "_wrapForChip" and |
60 | // last Material is from the "RawChip". |
61 | expect(materials.length, 2); |
62 | // The last Material from `RawChip` should have the clip behavior. |
63 | expect(materials.last.clipBehavior, clipBehavior); |
64 | } |
65 | |
66 | void main() { |
67 | testWidgets('Material2 - ChoiceChip defaults' , (WidgetTester tester) async { |
68 | final ThemeData theme = ThemeData(useMaterial3: false); |
69 | const String label = 'choice chip' ; |
70 | |
71 | // Test enabled ChoiceChip defaults. |
72 | await tester.pumpWidget( |
73 | MaterialApp( |
74 | theme: theme, |
75 | home: Material( |
76 | child: Center( |
77 | child: ChoiceChip( |
78 | selected: false, |
79 | onSelected: (bool valueChanged) {}, |
80 | label: const Text(label), |
81 | ), |
82 | ), |
83 | ), |
84 | ), |
85 | ); |
86 | |
87 | // Test default chip size. |
88 | expect(tester.getSize(find.byType(ChoiceChip)), const Size(178.0, 48.0)); |
89 | // Test default label style. |
90 | expect( |
91 | getLabelStyle(tester, label).style.color, |
92 | theme.textTheme.bodyLarge!.color!.withAlpha(0xde), |
93 | ); |
94 | |
95 | Material chipMaterial = getMaterial(tester); |
96 | expect(chipMaterial.elevation, 0); |
97 | expect(chipMaterial.shadowColor, Colors.black); |
98 | expect(chipMaterial.shape, const StadiumBorder()); |
99 | |
100 | ShapeDecoration decoration = |
101 | tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
102 | expect(decoration.color, Colors.black.withAlpha(0x1f)); |
103 | |
104 | // Test disabled ChoiceChip defaults. |
105 | await tester.pumpWidget( |
106 | MaterialApp( |
107 | theme: theme, |
108 | home: const Material(child: ChoiceChip(selected: false, label: Text(label))), |
109 | ), |
110 | ); |
111 | await tester.pumpAndSettle(); |
112 | |
113 | chipMaterial = getMaterial(tester); |
114 | expect(chipMaterial.elevation, 0); |
115 | expect(chipMaterial.shadowColor, Colors.black); |
116 | expect(chipMaterial.shape, const StadiumBorder()); |
117 | |
118 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
119 | expect(decoration.color, Colors.black38); |
120 | |
121 | // Test selected enabled ChoiceChip defaults. |
122 | await tester.pumpWidget( |
123 | MaterialApp( |
124 | theme: theme, |
125 | home: Material( |
126 | child: ChoiceChip( |
127 | selected: true, |
128 | onSelected: (bool valueChanged) {}, |
129 | label: const Text(label), |
130 | ), |
131 | ), |
132 | ), |
133 | ); |
134 | await tester.pumpAndSettle(); |
135 | |
136 | chipMaterial = getMaterial(tester); |
137 | expect(chipMaterial.elevation, 0); |
138 | expect(chipMaterial.shadowColor, Colors.black); |
139 | expect(chipMaterial.shape, const StadiumBorder()); |
140 | |
141 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
142 | expect(decoration.color, Colors.black.withAlpha(0x3d)); |
143 | |
144 | // Test selected disabled ChoiceChip defaults. |
145 | await tester.pumpWidget( |
146 | MaterialApp( |
147 | theme: theme, |
148 | home: const Material(child: ChoiceChip(selected: true, label: Text(label))), |
149 | ), |
150 | ); |
151 | await tester.pumpAndSettle(); |
152 | |
153 | chipMaterial = getMaterial(tester); |
154 | expect(chipMaterial.elevation, 0); |
155 | expect(chipMaterial.shadowColor, Colors.black); |
156 | expect(chipMaterial.shape, const StadiumBorder()); |
157 | |
158 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
159 | expect(decoration.color, Colors.black.withAlpha(0x3d)); |
160 | }); |
161 | |
162 | testWidgets('Material3 - ChoiceChip defaults' , (WidgetTester tester) async { |
163 | final ThemeData theme = ThemeData(); |
164 | const String label = 'choice chip' ; |
165 | |
166 | // Test enabled ChoiceChip defaults. |
167 | await tester.pumpWidget( |
168 | MaterialApp( |
169 | theme: theme, |
170 | home: Material( |
171 | child: Center( |
172 | child: ChoiceChip( |
173 | selected: false, |
174 | onSelected: (bool valueChanged) {}, |
175 | label: const Text(label), |
176 | ), |
177 | ), |
178 | ), |
179 | ), |
180 | ); |
181 | |
182 | // Test default chip size. |
183 | expect( |
184 | tester.getSize(find.byType(ChoiceChip)), |
185 | within(distance: 0.01, from: const Size(189.1, 48.0)), |
186 | ); |
187 | // Test default label style. |
188 | expect( |
189 | getLabelStyle(tester, label).style.color!.value, |
190 | theme.colorScheme.onSurfaceVariant.value, |
191 | ); |
192 | |
193 | Material chipMaterial = getMaterial(tester); |
194 | expect(chipMaterial.elevation, 0); |
195 | expect(chipMaterial.shadowColor, Colors.transparent); |
196 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
197 | expect( |
198 | chipMaterial.shape, |
199 | RoundedRectangleBorder( |
200 | borderRadius: const BorderRadius.all(Radius.circular(8.0)), |
201 | side: BorderSide(color: theme.colorScheme.outlineVariant), |
202 | ), |
203 | ); |
204 | |
205 | ShapeDecoration decoration = |
206 | tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
207 | expect(decoration.color, null); |
208 | |
209 | // Test disabled ChoiceChip defaults. |
210 | await tester.pumpWidget( |
211 | MaterialApp( |
212 | theme: theme, |
213 | home: const Material(child: ChoiceChip(selected: false, label: Text(label))), |
214 | ), |
215 | ); |
216 | await tester.pumpAndSettle(); |
217 | |
218 | chipMaterial = getMaterial(tester); |
219 | expect(chipMaterial.elevation, 0); |
220 | expect(chipMaterial.shadowColor, Colors.transparent); |
221 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
222 | expect( |
223 | chipMaterial.shape, |
224 | RoundedRectangleBorder( |
225 | borderRadius: const BorderRadius.all(Radius.circular(8.0)), |
226 | side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), |
227 | ), |
228 | ); |
229 | |
230 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
231 | expect(decoration.color, null); |
232 | |
233 | // Test selected enabled ChoiceChip defaults. |
234 | await tester.pumpWidget( |
235 | MaterialApp( |
236 | theme: theme, |
237 | home: Material( |
238 | child: ChoiceChip( |
239 | selected: true, |
240 | onSelected: (bool valueChanged) {}, |
241 | label: const Text(label), |
242 | ), |
243 | ), |
244 | ), |
245 | ); |
246 | await tester.pumpAndSettle(); |
247 | |
248 | chipMaterial = getMaterial(tester); |
249 | expect(chipMaterial.elevation, 0); |
250 | expect(chipMaterial.shadowColor, null); |
251 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
252 | expect( |
253 | chipMaterial.shape, |
254 | const RoundedRectangleBorder( |
255 | borderRadius: BorderRadius.all(Radius.circular(8.0)), |
256 | side: BorderSide(color: Colors.transparent), |
257 | ), |
258 | ); |
259 | |
260 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
261 | expect(decoration.color, theme.colorScheme.secondaryContainer); |
262 | |
263 | // Test selected disabled ChoiceChip defaults. |
264 | await tester.pumpWidget( |
265 | MaterialApp( |
266 | theme: theme, |
267 | home: const Material(child: ChoiceChip(selected: true, label: Text(label))), |
268 | ), |
269 | ); |
270 | await tester.pumpAndSettle(); |
271 | |
272 | chipMaterial = getMaterial(tester); |
273 | expect(chipMaterial.elevation, 0); |
274 | expect(chipMaterial.shadowColor, null); |
275 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
276 | expect( |
277 | chipMaterial.shape, |
278 | const RoundedRectangleBorder( |
279 | borderRadius: BorderRadius.all(Radius.circular(8.0)), |
280 | side: BorderSide(color: Colors.transparent), |
281 | ), |
282 | ); |
283 | |
284 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
285 | expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); |
286 | }); |
287 | |
288 | testWidgets('Material3 - ChoiceChip.elevated defaults' , (WidgetTester tester) async { |
289 | final ThemeData theme = ThemeData(); |
290 | const String label = 'choice chip' ; |
291 | |
292 | // Test enabled ChoiceChip.elevated defaults. |
293 | await tester.pumpWidget( |
294 | MaterialApp( |
295 | theme: theme, |
296 | home: Material( |
297 | child: Center( |
298 | child: ChoiceChip.elevated( |
299 | selected: false, |
300 | onSelected: (bool valueChanged) {}, |
301 | label: const Text(label), |
302 | ), |
303 | ), |
304 | ), |
305 | ), |
306 | ); |
307 | |
308 | // Test default chip size. |
309 | expect( |
310 | tester.getSize(find.byType(ChoiceChip)), |
311 | within(distance: 0.01, from: const Size(189.1, 48.0)), |
312 | ); |
313 | // Test default label style. |
314 | expect( |
315 | getLabelStyle(tester, label).style.color!.value, |
316 | theme.colorScheme.onSurfaceVariant.value, |
317 | ); |
318 | |
319 | Material chipMaterial = getMaterial(tester); |
320 | expect(chipMaterial.elevation, 1); |
321 | expect(chipMaterial.shadowColor, theme.colorScheme.shadow); |
322 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
323 | expect( |
324 | chipMaterial.shape, |
325 | const RoundedRectangleBorder( |
326 | borderRadius: BorderRadius.all(Radius.circular(8.0)), |
327 | side: BorderSide(color: Colors.transparent), |
328 | ), |
329 | ); |
330 | |
331 | ShapeDecoration decoration = |
332 | tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
333 | expect(decoration.color, theme.colorScheme.surfaceContainerLow); |
334 | |
335 | // Test disabled ChoiceChip.elevated defaults. |
336 | await tester.pumpWidget( |
337 | MaterialApp( |
338 | theme: theme, |
339 | home: const Material(child: ChoiceChip.elevated(selected: false, label: Text(label))), |
340 | ), |
341 | ); |
342 | await tester.pumpAndSettle(); |
343 | |
344 | chipMaterial = getMaterial(tester); |
345 | expect(chipMaterial.elevation, 0); |
346 | expect(chipMaterial.shadowColor, theme.colorScheme.shadow); |
347 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
348 | expect( |
349 | chipMaterial.shape, |
350 | const RoundedRectangleBorder( |
351 | borderRadius: BorderRadius.all(Radius.circular(8.0)), |
352 | side: BorderSide(color: Colors.transparent), |
353 | ), |
354 | ); |
355 | |
356 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
357 | expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); |
358 | |
359 | // Test selected enabled ChoiceChip.elevated defaults. |
360 | await tester.pumpWidget( |
361 | MaterialApp( |
362 | theme: theme, |
363 | home: Material( |
364 | child: ChoiceChip.elevated( |
365 | selected: true, |
366 | onSelected: (bool valueChanged) {}, |
367 | label: const Text(label), |
368 | ), |
369 | ), |
370 | ), |
371 | ); |
372 | await tester.pumpAndSettle(); |
373 | |
374 | chipMaterial = getMaterial(tester); |
375 | expect(chipMaterial.elevation, 1); |
376 | expect(chipMaterial.shadowColor, null); |
377 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
378 | expect( |
379 | chipMaterial.shape, |
380 | const RoundedRectangleBorder( |
381 | borderRadius: BorderRadius.all(Radius.circular(8.0)), |
382 | side: BorderSide(color: Colors.transparent), |
383 | ), |
384 | ); |
385 | |
386 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
387 | expect(decoration.color, theme.colorScheme.secondaryContainer); |
388 | |
389 | // Test selected disabled ChoiceChip.elevated defaults. |
390 | await tester.pumpWidget( |
391 | MaterialApp( |
392 | theme: theme, |
393 | home: const Material(child: ChoiceChip.elevated(selected: false, label: Text(label))), |
394 | ), |
395 | ); |
396 | await tester.pumpAndSettle(); |
397 | |
398 | chipMaterial = getMaterial(tester); |
399 | expect(chipMaterial.elevation, 0); |
400 | expect(chipMaterial.shadowColor, theme.colorScheme.shadow); |
401 | expect(chipMaterial.surfaceTintColor, Colors.transparent); |
402 | expect( |
403 | chipMaterial.shape, |
404 | const RoundedRectangleBorder( |
405 | borderRadius: BorderRadius.all(Radius.circular(8.0)), |
406 | side: BorderSide(color: Colors.transparent), |
407 | ), |
408 | ); |
409 | |
410 | decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
411 | expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); |
412 | }); |
413 | |
414 | testWidgets('ChoiceChip.color resolves material states' , (WidgetTester tester) async { |
415 | const Color disabledSelectedColor = Color(0xffffff00); |
416 | const Color disabledColor = Color(0xff00ff00); |
417 | const Color backgroundColor = Color(0xff0000ff); |
418 | const Color selectedColor = Color(0xffff0000); |
419 | final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith(( |
420 | Set<MaterialState> states, |
421 | ) { |
422 | if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) { |
423 | return disabledSelectedColor; |
424 | } |
425 | if (states.contains(MaterialState.disabled)) { |
426 | return disabledColor; |
427 | } |
428 | if (states.contains(MaterialState.selected)) { |
429 | return selectedColor; |
430 | } |
431 | return backgroundColor; |
432 | }); |
433 | Widget buildApp({required bool enabled, required bool selected}) { |
434 | return wrapForChip( |
435 | child: Column( |
436 | children: <Widget>[ |
437 | ChoiceChip( |
438 | onSelected: enabled ? (bool value) {} : null, |
439 | selected: selected, |
440 | color: color, |
441 | label: const Text('ChoiceChip' ), |
442 | ), |
443 | ChoiceChip.elevated( |
444 | onSelected: enabled ? (bool value) {} : null, |
445 | selected: selected, |
446 | color: color, |
447 | label: const Text('ChoiceChip.elevated' ), |
448 | ), |
449 | ], |
450 | ), |
451 | ); |
452 | } |
453 | |
454 | // Test enabled state. |
455 | await tester.pumpWidget(buildApp(enabled: true, selected: false)); |
456 | |
457 | // Enabled ChoiceChip should have the provided backgroundColor. |
458 | expect( |
459 | getMaterialBox(tester, find.byType(RawChip).first), |
460 | paints..rrect(color: backgroundColor), |
461 | ); |
462 | // Enabled elevated ChoiceChip should have the provided backgroundColor. |
463 | expect( |
464 | getMaterialBox(tester, find.byType(RawChip).last), |
465 | paints..rrect(color: backgroundColor), |
466 | ); |
467 | |
468 | // Test disabled state. |
469 | await tester.pumpWidget(buildApp(enabled: false, selected: false)); |
470 | await tester.pumpAndSettle(); |
471 | |
472 | // Disabled ChoiceChip should have the provided disabledColor. |
473 | expect(getMaterialBox(tester, find.byType(RawChip).first), paints..rrect(color: disabledColor)); |
474 | // Disabled elevated ChoiceChip should have the provided disabledColor. |
475 | expect(getMaterialBox(tester, find.byType(RawChip).last), paints..rrect(color: disabledColor)); |
476 | |
477 | // Test enabled & selected state. |
478 | await tester.pumpWidget(buildApp(enabled: true, selected: true)); |
479 | await tester.pumpAndSettle(); |
480 | |
481 | // Enabled & selected ChoiceChip should have the provided selectedColor. |
482 | expect(getMaterialBox(tester, find.byType(RawChip).first), paints..rrect(color: selectedColor)); |
483 | // Enabled & selected elevated ChoiceChip should have the provided selectedColor. |
484 | expect(getMaterialBox(tester, find.byType(RawChip).last), paints..rrect(color: selectedColor)); |
485 | |
486 | // Test disabled & selected state. |
487 | await tester.pumpWidget(buildApp(enabled: false, selected: true)); |
488 | await tester.pumpAndSettle(); |
489 | |
490 | // Disabled & selected ChoiceChip should have the provided disabledSelectedColor. |
491 | expect( |
492 | getMaterialBox(tester, find.byType(RawChip).first), |
493 | paints..rrect(color: disabledSelectedColor), |
494 | ); |
495 | // Disabled & selected elevated ChoiceChip should have the provided disabledSelectedColor. |
496 | expect( |
497 | getMaterialBox(tester, find.byType(RawChip).last), |
498 | paints..rrect(color: disabledSelectedColor), |
499 | ); |
500 | }); |
501 | |
502 | testWidgets('ChoiceChip uses provided state color properties' , (WidgetTester tester) async { |
503 | const Color disabledColor = Color(0xff00ff00); |
504 | const Color backgroundColor = Color(0xff0000ff); |
505 | const Color selectedColor = Color(0xffff0000); |
506 | Widget buildApp({required bool enabled, required bool selected}) { |
507 | return wrapForChip( |
508 | child: Column( |
509 | children: <Widget>[ |
510 | ChoiceChip( |
511 | onSelected: enabled ? (bool value) {} : null, |
512 | selected: selected, |
513 | disabledColor: disabledColor, |
514 | backgroundColor: backgroundColor, |
515 | selectedColor: selectedColor, |
516 | label: const Text('ChoiceChip' ), |
517 | ), |
518 | ChoiceChip.elevated( |
519 | onSelected: enabled ? (bool value) {} : null, |
520 | selected: selected, |
521 | disabledColor: disabledColor, |
522 | backgroundColor: backgroundColor, |
523 | selectedColor: selectedColor, |
524 | label: const Text('ChoiceChip.elevated' ), |
525 | ), |
526 | ], |
527 | ), |
528 | ); |
529 | } |
530 | |
531 | // Test enabled chips. |
532 | await tester.pumpWidget(buildApp(enabled: true, selected: false)); |
533 | |
534 | // Enabled ChoiceChip should have the provided backgroundColor. |
535 | expect( |
536 | getMaterialBox(tester, find.byType(RawChip).first), |
537 | paints..rrect(color: backgroundColor), |
538 | ); |
539 | // Enabled elevated ChoiceChip should have the provided backgroundColor. |
540 | expect( |
541 | getMaterialBox(tester, find.byType(RawChip).last), |
542 | paints..rrect(color: backgroundColor), |
543 | ); |
544 | |
545 | // Test disabled chips. |
546 | await tester.pumpWidget(buildApp(enabled: false, selected: false)); |
547 | await tester.pumpAndSettle(); |
548 | |
549 | // Disabled ChoiceChip should have the provided disabledColor. |
550 | expect(getMaterialBox(tester, find.byType(RawChip).first), paints..rrect(color: disabledColor)); |
551 | // Disabled elevated ChoiceChip should have the provided disabledColor. |
552 | expect(getMaterialBox(tester, find.byType(RawChip).last), paints..rrect(color: disabledColor)); |
553 | |
554 | // Test enabled & selected chips. |
555 | await tester.pumpWidget(buildApp(enabled: true, selected: true)); |
556 | await tester.pumpAndSettle(); |
557 | |
558 | // Enabled & selected ChoiceChip should have the provided selectedColor. |
559 | expect(getMaterialBox(tester, find.byType(RawChip).first), paints..rrect(color: selectedColor)); |
560 | // Enabled & selected elevated ChoiceChip should have the provided selectedColor. |
561 | expect(getMaterialBox(tester, find.byType(RawChip).last), paints..rrect(color: selectedColor)); |
562 | }); |
563 | |
564 | testWidgets('ChoiceChip can be tapped' , (WidgetTester tester) async { |
565 | await tester.pumpWidget( |
566 | const MaterialApp( |
567 | home: Material(child: ChoiceChip(selected: false, label: Text('choice chip' ))), |
568 | ), |
569 | ); |
570 | |
571 | await tester.tap(find.byType(ChoiceChip)); |
572 | expect(tester.takeException(), null); |
573 | }); |
574 | |
575 | testWidgets('ChoiceChip clipBehavior properly passes through to the Material' , ( |
576 | WidgetTester tester, |
577 | ) async { |
578 | const Text label = Text('label' ); |
579 | await tester.pumpWidget(wrapForChip(child: const ChoiceChip(label: label, selected: false))); |
580 | checkChipMaterialClipBehavior(tester, Clip.none); |
581 | |
582 | await tester.pumpWidget( |
583 | wrapForChip( |
584 | child: const ChoiceChip(label: label, selected: false, clipBehavior: Clip.antiAlias), |
585 | ), |
586 | ); |
587 | checkChipMaterialClipBehavior(tester, Clip.antiAlias); |
588 | }); |
589 | |
590 | testWidgets('ChoiceChip passes iconTheme property to RawChip' , (WidgetTester tester) async { |
591 | const IconThemeData iconTheme = IconThemeData(color: Colors.red); |
592 | await tester.pumpWidget( |
593 | wrapForChip( |
594 | child: const ChoiceChip(label: Text('Test' ), selected: true, iconTheme: iconTheme), |
595 | ), |
596 | ); |
597 | final RawChip rawChip = tester.widget(find.byType(RawChip)); |
598 | expect(rawChip.iconTheme, iconTheme); |
599 | }); |
600 | |
601 | testWidgets('ChoiceChip passes showCheckmark from ChipTheme to RawChip' , ( |
602 | WidgetTester tester, |
603 | ) async { |
604 | const bool showCheckmark = false; |
605 | await tester.pumpWidget( |
606 | wrapForChip( |
607 | child: const ChipTheme( |
608 | data: ChipThemeData(showCheckmark: showCheckmark), |
609 | child: ChoiceChip(label: Text('Test' ), selected: true), |
610 | ), |
611 | ), |
612 | ); |
613 | final RawChip rawChip = tester.widget(find.byType(RawChip)); |
614 | expect(rawChip.showCheckmark, showCheckmark); |
615 | }); |
616 | |
617 | testWidgets('ChoiceChip passes checkmark properties to RawChip' , (WidgetTester tester) async { |
618 | const bool showCheckmark = false; |
619 | const Color checkmarkColor = Color(0xff0000ff); |
620 | await tester.pumpWidget( |
621 | wrapForChip( |
622 | child: const ChoiceChip( |
623 | label: Text('Test' ), |
624 | selected: true, |
625 | showCheckmark: showCheckmark, |
626 | checkmarkColor: checkmarkColor, |
627 | ), |
628 | ), |
629 | ); |
630 | final RawChip rawChip = tester.widget(find.byType(RawChip)); |
631 | expect(rawChip.showCheckmark, showCheckmark); |
632 | expect(rawChip.checkmarkColor, checkmarkColor); |
633 | }); |
634 | |
635 | testWidgets('ChoiceChip uses provided iconTheme' , (WidgetTester tester) async { |
636 | final ThemeData theme = ThemeData(); |
637 | |
638 | Widget buildChip({IconThemeData? iconTheme}) { |
639 | return MaterialApp( |
640 | theme: theme, |
641 | home: Material( |
642 | child: ChoiceChip( |
643 | iconTheme: iconTheme, |
644 | avatar: const Icon(Icons.add), |
645 | label: const Text('Test' ), |
646 | selected: false, |
647 | onSelected: (bool _) {}, |
648 | ), |
649 | ), |
650 | ); |
651 | } |
652 | |
653 | // Test default icon theme. |
654 | await tester.pumpWidget(buildChip()); |
655 | |
656 | expect(getIconData(tester).color, theme.colorScheme.primary); |
657 | |
658 | // Test provided icon theme. |
659 | await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff00ff00)))); |
660 | |
661 | expect(getIconData(tester).color, const Color(0xff00ff00)); |
662 | }); |
663 | |
664 | testWidgets('ChoiceChip avatar layout constraints can be customized' , ( |
665 | WidgetTester tester, |
666 | ) async { |
667 | const double border = 1.0; |
668 | const double iconSize = 18.0; |
669 | const double labelPadding = 8.0; |
670 | const double padding = 8.0; |
671 | const Size labelSize = Size(100, 100); |
672 | |
673 | Widget buildChip({BoxConstraints? avatarBoxConstraints}) { |
674 | return wrapForChip( |
675 | child: Center( |
676 | child: ChoiceChip( |
677 | avatarBoxConstraints: avatarBoxConstraints, |
678 | avatar: const Icon(Icons.favorite), |
679 | label: Container( |
680 | width: labelSize.width, |
681 | height: labelSize.width, |
682 | color: const Color(0xFFFF0000), |
683 | ), |
684 | selected: false, |
685 | ), |
686 | ), |
687 | ); |
688 | } |
689 | |
690 | // Test default avatar layout constraints. |
691 | await tester.pumpWidget(buildChip()); |
692 | |
693 | expect(tester.getSize(find.byType(ChoiceChip)).width, equals(234.0)); |
694 | expect(tester.getSize(find.byType(ChoiceChip)).height, equals(118.0)); |
695 | |
696 | // Calculate the distance between avatar and chip edges. |
697 | Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester))); |
698 | final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite)); |
699 | expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border); |
700 | expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); |
701 | |
702 | // Calculate the distance between avatar and label. |
703 | Offset labelTopLeft = tester.getTopLeft(find.byType(Container)); |
704 | expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding); |
705 | |
706 | // Test custom avatar layout constraints. |
707 | await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite())); |
708 | await tester.pump(); |
709 | |
710 | expect(tester.getSize(find.byType(ChoiceChip)).width, equals(152.0)); |
711 | expect(tester.getSize(find.byType(ChoiceChip)).height, equals(118.0)); |
712 | |
713 | // Calculate the distance between avatar and chip edges. |
714 | chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester))); |
715 | expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border); |
716 | expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); |
717 | |
718 | // Calculate the distance between avatar and label. |
719 | labelTopLeft = tester.getTopLeft(find.byType(Container)); |
720 | expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding); |
721 | }); |
722 | |
723 | testWidgets('ChoiceChip.chipAnimationStyle is passed to RawChip' , (WidgetTester tester) async { |
724 | final ChipAnimationStyle chipAnimationStyle = ChipAnimationStyle( |
725 | enableAnimation: const AnimationStyle(duration: Durations.extralong4), |
726 | selectAnimation: AnimationStyle.noAnimation, |
727 | ); |
728 | |
729 | await tester.pumpWidget( |
730 | wrapForChip( |
731 | child: Center( |
732 | child: ChoiceChip( |
733 | chipAnimationStyle: chipAnimationStyle, |
734 | selected: true, |
735 | label: const Text('ChoiceChip' ), |
736 | ), |
737 | ), |
738 | ), |
739 | ); |
740 | |
741 | expect(tester.widget<RawChip>(find.byType(RawChip)).chipAnimationStyle, chipAnimationStyle); |
742 | }); |
743 | |
744 | testWidgets('Elevated ChoiceChip.chipAnimationStyle is passed to RawChip' , ( |
745 | WidgetTester tester, |
746 | ) async { |
747 | final ChipAnimationStyle chipAnimationStyle = ChipAnimationStyle( |
748 | enableAnimation: const AnimationStyle(duration: Durations.extralong4), |
749 | selectAnimation: AnimationStyle.noAnimation, |
750 | ); |
751 | |
752 | await tester.pumpWidget( |
753 | wrapForChip( |
754 | child: Center( |
755 | child: ChoiceChip.elevated( |
756 | chipAnimationStyle: chipAnimationStyle, |
757 | selected: true, |
758 | label: const Text('ChoiceChip' ), |
759 | ), |
760 | ), |
761 | ), |
762 | ); |
763 | |
764 | expect(tester.widget<RawChip>(find.byType(RawChip)).chipAnimationStyle, chipAnimationStyle); |
765 | }); |
766 | |
767 | testWidgets('ChoiceChip mouse cursor behavior' , (WidgetTester tester) async { |
768 | const SystemMouseCursor customCursor = SystemMouseCursors.grab; |
769 | |
770 | await tester.pumpWidget( |
771 | wrapForChip( |
772 | child: const Center( |
773 | child: ChoiceChip(selected: false, mouseCursor: customCursor, label: Text('Chip' )), |
774 | ), |
775 | ), |
776 | ); |
777 | |
778 | final TestGesture gesture = await tester.createGesture( |
779 | kind: PointerDeviceKind.mouse, |
780 | pointer: 1, |
781 | ); |
782 | await gesture.addPointer(location: const Offset(10, 10)); |
783 | await tester.pump(); |
784 | expect( |
785 | RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), |
786 | SystemMouseCursors.basic, |
787 | ); |
788 | |
789 | final Offset chip = tester.getCenter(find.text('Chip' )); |
790 | await gesture.moveTo(chip); |
791 | await tester.pump(); |
792 | expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), customCursor); |
793 | }); |
794 | |
795 | testWidgets('Mouse cursor resolves in focused/unfocused/disabled states' , ( |
796 | WidgetTester tester, |
797 | ) async { |
798 | tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; |
799 | final FocusNode focusNode = FocusNode(debugLabel: 'Chip' ); |
800 | addTearDown(focusNode.dispose); |
801 | |
802 | Widget buildChip({required bool enabled}) { |
803 | return wrapForChip( |
804 | child: Center( |
805 | child: ChoiceChip( |
806 | mouseCursor: const WidgetStateMouseCursor.fromMap(<WidgetStatesConstraint, MouseCursor>{ |
807 | WidgetState.disabled: SystemMouseCursors.forbidden, |
808 | WidgetState.focused: SystemMouseCursors.grab, |
809 | WidgetState.selected: SystemMouseCursors.click, |
810 | WidgetState.any: SystemMouseCursors.basic, |
811 | }), |
812 | focusNode: focusNode, |
813 | label: const Text('Chip' ), |
814 | onSelected: enabled ? (bool value) {} : null, |
815 | selected: false, |
816 | ), |
817 | ), |
818 | ); |
819 | } |
820 | |
821 | // Unfocused case. |
822 | await tester.pumpWidget(buildChip(enabled: true)); |
823 | final TestGesture gesture1 = await tester.createGesture( |
824 | kind: PointerDeviceKind.mouse, |
825 | pointer: 1, |
826 | ); |
827 | addTearDown(gesture1.removePointer); |
828 | await gesture1.addPointer(location: tester.getCenter(find.text('Chip' ))); |
829 | await tester.pump(); |
830 | await gesture1.moveTo(tester.getCenter(find.text('Chip' ))); |
831 | expect( |
832 | RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), |
833 | SystemMouseCursors.basic, |
834 | ); |
835 | |
836 | // Focused case. |
837 | focusNode.requestFocus(); |
838 | await tester.pump(); |
839 | expect( |
840 | RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), |
841 | SystemMouseCursors.grab, |
842 | ); |
843 | |
844 | // Disabled case. |
845 | await tester.pumpWidget(buildChip(enabled: false)); |
846 | expect( |
847 | RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), |
848 | SystemMouseCursors.forbidden, |
849 | ); |
850 | }); |
851 | } |
852 | |