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
5import 'package:flutter/cupertino.dart';
6import 'package:flutter/gestures.dart';
7import 'package:flutter/material.dart';
8import 'package:flutter/rendering.dart';
9import 'package:flutter/services.dart';
10import 'package:flutter_test/flutter_test.dart';
11
12import '../widgets/feedback_tester.dart';
13import '../widgets/semantics_tester.dart';
14
15Widget wrap({Widget? child}) {
16 return MediaQuery(
17 data: const MediaQueryData(),
18 child: Directionality(textDirection: TextDirection.ltr, child: Material(child: child)),
19 );
20}
21
22void main() {
23 testWidgets('RadioListTile should initialize according to groupValue', (
24 WidgetTester tester,
25 ) async {
26 final List<int> values = <int>[0, 1, 2];
27 int? selectedValue;
28 // Constructor parameters are required for [RadioListTile], but they are
29 // irrelevant when searching with [find.byType].
30 final Type radioListTileType =
31 const RadioListTile<int>(value: 0, groupValue: 0, onChanged: null).runtimeType;
32
33 List<RadioListTile<int>> generatedRadioListTiles;
34 List<RadioListTile<int>> findTiles() =>
35 find
36 .byType(radioListTileType)
37 .evaluate()
38 .map<Widget>((Element element) => element.widget)
39 .cast<RadioListTile<int>>()
40 .toList();
41
42 Widget buildFrame() {
43 return wrap(
44 child: StatefulBuilder(
45 builder: (BuildContext context, StateSetter setState) {
46 return Scaffold(
47 body: ListView.builder(
48 itemCount: values.length,
49 itemBuilder:
50 (BuildContext context, int index) => RadioListTile<int>(
51 onChanged: (int? value) {
52 setState(() {
53 selectedValue = value;
54 });
55 },
56 value: values[index],
57 groupValue: selectedValue,
58 title: Text(values[index].toString()),
59 ),
60 ),
61 );
62 },
63 ),
64 );
65 }
66
67 await tester.pumpWidget(buildFrame());
68 generatedRadioListTiles = findTiles();
69
70 expect(generatedRadioListTiles[0].checked, equals(false));
71 expect(generatedRadioListTiles[1].checked, equals(false));
72 expect(generatedRadioListTiles[2].checked, equals(false));
73
74 selectedValue = 1;
75
76 await tester.pumpWidget(buildFrame());
77 generatedRadioListTiles = findTiles();
78
79 expect(generatedRadioListTiles[0].checked, equals(false));
80 expect(generatedRadioListTiles[1].checked, equals(true));
81 expect(generatedRadioListTiles[2].checked, equals(false));
82 });
83
84 testWidgets('RadioListTile simple control test', (WidgetTester tester) async {
85 final Key key = UniqueKey();
86 final Key titleKey = UniqueKey();
87 final List<int?> log = <int?>[];
88
89 await tester.pumpWidget(
90 wrap(
91 child: RadioListTile<int>(
92 key: key,
93 value: 1,
94 groupValue: 2,
95 onChanged: log.add,
96 title: Text('Title', key: titleKey),
97 ),
98 ),
99 );
100
101 await tester.tap(find.byKey(key));
102
103 expect(log, equals(<int>[1]));
104 log.clear();
105
106 await tester.pumpWidget(
107 wrap(
108 child: RadioListTile<int>(
109 key: key,
110 value: 1,
111 groupValue: 1,
112 onChanged: log.add,
113 activeColor: Colors.green[500],
114 title: Text('Title', key: titleKey),
115 ),
116 ),
117 );
118
119 await tester.tap(find.byKey(key));
120
121 expect(log, isEmpty);
122
123 await tester.pumpWidget(
124 wrap(
125 child: RadioListTile<int>(
126 key: key,
127 value: 1,
128 groupValue: 2,
129 onChanged: null,
130 title: Text('Title', key: titleKey),
131 ),
132 ),
133 );
134
135 await tester.tap(find.byKey(key));
136
137 expect(log, isEmpty);
138
139 await tester.pumpWidget(
140 wrap(
141 child: RadioListTile<int>(
142 key: key,
143 value: 1,
144 groupValue: 2,
145 onChanged: log.add,
146 title: Text('Title', key: titleKey),
147 ),
148 ),
149 );
150
151 await tester.tap(find.byKey(titleKey));
152
153 expect(log, equals(<int>[1]));
154 });
155
156 testWidgets('RadioListTile control tests', (WidgetTester tester) async {
157 final List<int> values = <int>[0, 1, 2];
158 int? selectedValue;
159 // Constructor parameters are required for [Radio], but they are irrelevant
160 // when searching with [find.byType].
161 final Type radioType = const Radio<int>(value: 0, groupValue: 0, onChanged: null).runtimeType;
162 final List<dynamic> log = <dynamic>[];
163
164 Widget buildFrame() {
165 return wrap(
166 child: StatefulBuilder(
167 builder: (BuildContext context, StateSetter setState) {
168 return Scaffold(
169 body: ListView.builder(
170 itemCount: values.length,
171 itemBuilder:
172 (BuildContext context, int index) => RadioListTile<int>(
173 onChanged: (int? value) {
174 log.add(value);
175 setState(() {
176 selectedValue = value;
177 });
178 },
179 value: values[index],
180 groupValue: selectedValue,
181 title: Text(values[index].toString()),
182 ),
183 ),
184 );
185 },
186 ),
187 );
188 }
189
190 // Tests for tapping between [Radio] and [ListTile]
191 await tester.pumpWidget(buildFrame());
192 await tester.tap(find.text('1'));
193 log.add('-');
194 await tester.tap(find.byType(radioType).at(2));
195 expect(log, equals(<dynamic>[1, '-', 2]));
196 log.add('-');
197 await tester.tap(find.text('1'));
198
199 log.clear();
200 selectedValue = null;
201
202 // Tests for tapping across [Radio]s exclusively
203 await tester.pumpWidget(buildFrame());
204 await tester.tap(find.byType(radioType).at(1));
205 log.add('-');
206 await tester.tap(find.byType(radioType).at(2));
207 expect(log, equals(<dynamic>[1, '-', 2]));
208
209 log.clear();
210 selectedValue = null;
211
212 // Tests for tapping across [ListTile]s exclusively
213 await tester.pumpWidget(buildFrame());
214 await tester.tap(find.text('1'));
215 log.add('-');
216 await tester.tap(find.text('2'));
217 expect(log, equals(<dynamic>[1, '-', 2]));
218 });
219
220 testWidgets('Selected RadioListTile should not trigger onChanged', (WidgetTester tester) async {
221 // Regression test for https://github.com/flutter/flutter/issues/30311
222 final List<int> values = <int>[0, 1, 2];
223 int? selectedValue;
224 // Constructor parameters are required for [Radio], but they are irrelevant
225 // when searching with [find.byType].
226 final Type radioType = const Radio<int>(value: 0, groupValue: 0, onChanged: null).runtimeType;
227 final List<dynamic> log = <dynamic>[];
228
229 Widget buildFrame() {
230 return wrap(
231 child: StatefulBuilder(
232 builder: (BuildContext context, StateSetter setState) {
233 return Scaffold(
234 body: ListView.builder(
235 itemCount: values.length,
236 itemBuilder:
237 (BuildContext context, int index) => RadioListTile<int>(
238 onChanged: (int? value) {
239 log.add(value);
240 setState(() {
241 selectedValue = value;
242 });
243 },
244 value: values[index],
245 groupValue: selectedValue,
246 title: Text(values[index].toString()),
247 ),
248 ),
249 );
250 },
251 ),
252 );
253 }
254
255 await tester.pumpWidget(buildFrame());
256 await tester.tap(find.text('0'));
257 await tester.pump();
258 expect(log, equals(<int>[0]));
259
260 await tester.tap(find.text('0'));
261 await tester.pump();
262 expect(log, equals(<int>[0]));
263
264 await tester.tap(find.byType(radioType).at(0));
265 await tester.pump();
266 expect(log, equals(<int>[0]));
267 });
268
269 testWidgets('Selected RadioListTile should trigger onChanged when toggleable', (
270 WidgetTester tester,
271 ) async {
272 final List<int> values = <int>[0, 1, 2];
273 int? selectedValue;
274 // Constructor parameters are required for [Radio], but they are irrelevant
275 // when searching with [find.byType].
276 final Type radioType = const Radio<int>(value: 0, groupValue: 0, onChanged: null).runtimeType;
277 final List<dynamic> log = <dynamic>[];
278
279 Widget buildFrame() {
280 return wrap(
281 child: StatefulBuilder(
282 builder: (BuildContext context, StateSetter setState) {
283 return Scaffold(
284 body: ListView.builder(
285 itemCount: values.length,
286 itemBuilder: (BuildContext context, int index) {
287 return RadioListTile<int>(
288 onChanged: (int? value) {
289 log.add(value);
290 setState(() {
291 selectedValue = value;
292 });
293 },
294 toggleable: true,
295 value: values[index],
296 groupValue: selectedValue,
297 title: Text(values[index].toString()),
298 );
299 },
300 ),
301 );
302 },
303 ),
304 );
305 }
306
307 await tester.pumpWidget(buildFrame());
308 await tester.tap(find.text('0'));
309 await tester.pump();
310 expect(log, equals(<int>[0]));
311
312 await tester.tap(find.text('0'));
313 await tester.pump();
314 expect(log, equals(<int?>[0, null]));
315
316 await tester.tap(find.byType(radioType).at(0));
317 await tester.pump();
318 expect(log, equals(<int?>[0, null, 0]));
319 });
320
321 testWidgets('RadioListTile can be toggled when toggleable is set', (WidgetTester tester) async {
322 final Key key = UniqueKey();
323 final List<int?> log = <int?>[];
324
325 await tester.pumpWidget(
326 Material(
327 child: Center(
328 child: Radio<int>(
329 key: key,
330 value: 1,
331 groupValue: 2,
332 onChanged: log.add,
333 toggleable: true,
334 ),
335 ),
336 ),
337 );
338
339 await tester.tap(find.byKey(key));
340
341 expect(log, equals(<int>[1]));
342 log.clear();
343
344 await tester.pumpWidget(
345 Material(
346 child: Center(
347 child: Radio<int>(
348 key: key,
349 value: 1,
350 groupValue: 1,
351 onChanged: log.add,
352 toggleable: true,
353 ),
354 ),
355 ),
356 );
357
358 await tester.tap(find.byKey(key));
359
360 expect(log, equals(<int?>[null]));
361 log.clear();
362
363 await tester.pumpWidget(
364 Material(
365 child: Center(
366 child: Radio<int>(
367 key: key,
368 value: 1,
369 groupValue: null,
370 onChanged: log.add,
371 toggleable: true,
372 ),
373 ),
374 ),
375 );
376
377 await tester.tap(find.byKey(key));
378
379 expect(log, equals(<int>[1]));
380 });
381
382 testWidgets('RadioListTile semantics', (WidgetTester tester) async {
383 final SemanticsTester semantics = SemanticsTester(tester);
384
385 await tester.pumpWidget(
386 wrap(
387 child: RadioListTile<int>(
388 value: 1,
389 groupValue: 2,
390 onChanged: (int? i) {},
391 title: const Text('Title'),
392 internalAddSemanticForOnTap: true,
393 ),
394 ),
395 );
396
397 expect(
398 semantics,
399 hasSemantics(
400 TestSemantics.root(
401 children: <TestSemantics>[
402 TestSemantics(
403 id: 1,
404 flags: <SemanticsFlag>[
405 SemanticsFlag.isButton,
406 SemanticsFlag.hasCheckedState,
407 SemanticsFlag.hasEnabledState,
408 SemanticsFlag.isEnabled,
409 SemanticsFlag.isInMutuallyExclusiveGroup,
410 SemanticsFlag.isFocusable,
411 SemanticsFlag.hasSelectedState,
412 ],
413 actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
414 label: 'Title',
415 textDirection: TextDirection.ltr,
416 ),
417 ],
418 ),
419 ignoreRect: true,
420 ignoreTransform: true,
421 ),
422 );
423
424 await tester.pumpWidget(
425 wrap(
426 child: RadioListTile<int>(
427 value: 2,
428 groupValue: 2,
429 onChanged: (int? i) {},
430 title: const Text('Title'),
431 internalAddSemanticForOnTap: true,
432 ),
433 ),
434 );
435
436 expect(
437 semantics,
438 hasSemantics(
439 TestSemantics.root(
440 children: <TestSemantics>[
441 TestSemantics(
442 id: 1,
443 flags: <SemanticsFlag>[
444 SemanticsFlag.isButton,
445 SemanticsFlag.hasCheckedState,
446 SemanticsFlag.isChecked,
447 SemanticsFlag.hasEnabledState,
448 SemanticsFlag.isEnabled,
449 SemanticsFlag.isInMutuallyExclusiveGroup,
450 SemanticsFlag.isFocusable,
451 SemanticsFlag.hasSelectedState,
452 ],
453 actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
454 label: 'Title',
455 textDirection: TextDirection.ltr,
456 ),
457 ],
458 ),
459 ignoreRect: true,
460 ignoreTransform: true,
461 ),
462 );
463
464 await tester.pumpWidget(
465 wrap(
466 child: const RadioListTile<int>(
467 value: 1,
468 groupValue: 2,
469 onChanged: null,
470 title: Text('Title'),
471 internalAddSemanticForOnTap: true,
472 ),
473 ),
474 );
475
476 expect(
477 semantics,
478 hasSemantics(
479 TestSemantics.root(
480 children: <TestSemantics>[
481 TestSemantics(
482 id: 1,
483 flags: <SemanticsFlag>[
484 SemanticsFlag.hasCheckedState,
485 SemanticsFlag.hasEnabledState,
486 SemanticsFlag.isInMutuallyExclusiveGroup,
487 SemanticsFlag.isFocusable,
488 SemanticsFlag.hasSelectedState,
489 ],
490 actions: <SemanticsAction>[SemanticsAction.focus],
491 label: 'Title',
492 textDirection: TextDirection.ltr,
493 ),
494 ],
495 ),
496 ignoreId: true,
497 ignoreRect: true,
498 ignoreTransform: true,
499 ),
500 );
501
502 await tester.pumpWidget(
503 wrap(
504 child: const RadioListTile<int>(
505 value: 2,
506 groupValue: 2,
507 onChanged: null,
508 title: Text('Title'),
509 internalAddSemanticForOnTap: true,
510 ),
511 ),
512 );
513
514 expect(
515 semantics,
516 hasSemantics(
517 TestSemantics.root(
518 children: <TestSemantics>[
519 TestSemantics(
520 id: 1,
521 flags: <SemanticsFlag>[
522 SemanticsFlag.hasCheckedState,
523 SemanticsFlag.isChecked,
524 SemanticsFlag.hasEnabledState,
525 SemanticsFlag.isInMutuallyExclusiveGroup,
526 SemanticsFlag.hasSelectedState,
527 ],
528 label: 'Title',
529 textDirection: TextDirection.ltr,
530 ),
531 ],
532 ),
533 ignoreId: true,
534 ignoreRect: true,
535 ignoreTransform: true,
536 ),
537 );
538
539 semantics.dispose();
540 });
541
542 testWidgets('RadioListTile has semantic events', (WidgetTester tester) async {
543 final SemanticsTester semantics = SemanticsTester(tester);
544 final Key key = UniqueKey();
545 dynamic semanticEvent;
546 int? radioValue = 2;
547 tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
548 SystemChannels.accessibility,
549 (dynamic message) async {
550 semanticEvent = message;
551 },
552 );
553
554 await tester.pumpWidget(
555 wrap(
556 child: RadioListTile<int>(
557 key: key,
558 value: 1,
559 groupValue: radioValue,
560 onChanged: (int? i) {
561 radioValue = i;
562 },
563 title: const Text('Title'),
564 ),
565 ),
566 );
567
568 await tester.tap(find.byKey(key));
569 await tester.pump();
570 final RenderObject object = tester.firstRenderObject(find.byKey(key));
571
572 expect(radioValue, 1);
573 expect(semanticEvent, <String, dynamic>{
574 'type': 'tap',
575 'nodeId': object.debugSemantics!.id,
576 'data': <String, dynamic>{},
577 });
578 expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
579
580 semantics.dispose();
581 tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
582 SystemChannels.accessibility,
583 null,
584 );
585 });
586
587 testWidgets('RadioListTile can autofocus unless disabled.', (WidgetTester tester) async {
588 final GlobalKey childKey = GlobalKey();
589
590 await tester.pumpWidget(
591 wrap(
592 child: RadioListTile<int>(
593 value: 1,
594 groupValue: 2,
595 onChanged: (_) {},
596 title: Text('Title', key: childKey),
597 autofocus: true,
598 ),
599 ),
600 );
601
602 await tester.pump();
603 expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isTrue);
604
605 await tester.pumpWidget(
606 wrap(
607 child: RadioListTile<int>(
608 value: 1,
609 groupValue: 2,
610 onChanged: null,
611 title: Text('Title', key: childKey),
612 autofocus: true,
613 ),
614 ),
615 );
616
617 await tester.pump();
618 expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isFalse);
619 });
620
621 testWidgets('RadioListTile contentPadding test', (WidgetTester tester) async {
622 final Type radioType =
623 const Radio<bool>(groupValue: true, value: true, onChanged: null).runtimeType;
624
625 await tester.pumpWidget(
626 wrap(
627 child: Center(
628 child: RadioListTile<bool>(
629 groupValue: true,
630 value: true,
631 title: const Text('Title'),
632 onChanged: (_) {},
633 contentPadding: const EdgeInsets.fromLTRB(8, 10, 15, 20),
634 ),
635 ),
636 ),
637 );
638
639 final Rect paddingRect = tester.getRect(find.byType(SafeArea));
640 final Rect radioRect = tester.getRect(find.byType(radioType));
641 final Rect titleRect = tester.getRect(find.text('Title'));
642
643 // Get the taller Rect of the Radio and Text widgets
644 final Rect tallerRect = radioRect.height > titleRect.height ? radioRect : titleRect;
645
646 // Get the extra height between the tallerRect and ListTile height
647 final double extraHeight = 56 - tallerRect.height;
648
649 // Check for correct top and bottom padding
650 expect(paddingRect.top, tallerRect.top - extraHeight / 2 - 10); //top padding
651 expect(paddingRect.bottom, tallerRect.bottom + extraHeight / 2 + 20); //bottom padding
652
653 // Check for correct left and right padding
654 expect(paddingRect.left, radioRect.left - 8); //left padding
655 expect(paddingRect.right, titleRect.right + 15); //right padding
656 });
657
658 testWidgets('RadioListTile respects shape', (WidgetTester tester) async {
659 const ShapeBorder shapeBorder = RoundedRectangleBorder(
660 borderRadius: BorderRadius.horizontal(right: Radius.circular(100)),
661 );
662
663 await tester.pumpWidget(
664 const MaterialApp(
665 home: Material(
666 child: RadioListTile<bool>(
667 value: true,
668 groupValue: true,
669 onChanged: null,
670 title: Text('Title'),
671 shape: shapeBorder,
672 ),
673 ),
674 ),
675 );
676
677 expect(tester.widget<InkWell>(find.byType(InkWell)).customBorder, shapeBorder);
678 });
679
680 testWidgets('RadioListTile respects tileColor', (WidgetTester tester) async {
681 final Color tileColor = Colors.red.shade500;
682
683 await tester.pumpWidget(
684 wrap(
685 child: Center(
686 child: RadioListTile<bool>(
687 value: false,
688 groupValue: true,
689 onChanged: null,
690 title: const Text('Title'),
691 tileColor: tileColor,
692 ),
693 ),
694 ),
695 );
696
697 expect(find.byType(Material), paints..rect(color: tileColor));
698 });
699
700 testWidgets('RadioListTile respects selectedTileColor', (WidgetTester tester) async {
701 final Color selectedTileColor = Colors.green.shade500;
702
703 await tester.pumpWidget(
704 wrap(
705 child: Center(
706 child: RadioListTile<bool>(
707 value: false,
708 groupValue: true,
709 onChanged: null,
710 title: const Text('Title'),
711 selected: true,
712 selectedTileColor: selectedTileColor,
713 ),
714 ),
715 ),
716 );
717
718 expect(find.byType(Material), paints..rect(color: selectedTileColor));
719 });
720
721 testWidgets('RadioListTile selected item text Color', (WidgetTester tester) async {
722 // Regression test for https://github.com/flutter/flutter/pull/76906
723
724 const Color activeColor = Color(0xff00ff00);
725
726 Widget buildFrame({Color? activeColor, Color? fillColor}) {
727 return MaterialApp(
728 theme: ThemeData(
729 radioTheme: RadioThemeData(
730 fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
731 return states.contains(MaterialState.selected) ? fillColor : null;
732 }),
733 ),
734 ),
735 home: Scaffold(
736 body: Center(
737 child: RadioListTile<bool>(
738 activeColor: activeColor,
739 selected: true,
740 title: const Text('title'),
741 value: false,
742 groupValue: true,
743 onChanged: (bool? newValue) {},
744 ),
745 ),
746 ),
747 );
748 }
749
750 Color? textColor(String text) {
751 return tester.renderObject<RenderParagraph>(find.text(text)).text.style?.color;
752 }
753
754 await tester.pumpWidget(buildFrame(fillColor: activeColor));
755 expect(textColor('title'), activeColor);
756
757 await tester.pumpWidget(buildFrame(activeColor: activeColor));
758 expect(textColor('title'), activeColor);
759 });
760
761 testWidgets('RadioListTile respects visualDensity', (WidgetTester tester) async {
762 const Key key = Key('test');
763 Future<void> buildTest(VisualDensity visualDensity) async {
764 return tester.pumpWidget(
765 wrap(
766 child: Center(
767 child: RadioListTile<bool>(
768 key: key,
769 value: false,
770 groupValue: true,
771 onChanged: (bool? value) {},
772 autofocus: true,
773 visualDensity: visualDensity,
774 ),
775 ),
776 ),
777 );
778 }
779
780 await buildTest(VisualDensity.standard);
781 final RenderBox box = tester.renderObject(find.byKey(key));
782 await tester.pumpAndSettle();
783 expect(box.size, equals(const Size(800, 56)));
784 });
785
786 testWidgets('RadioListTile respects focusNode', (WidgetTester tester) async {
787 final GlobalKey childKey = GlobalKey();
788 await tester.pumpWidget(
789 wrap(
790 child: Center(
791 child: RadioListTile<bool>(
792 value: false,
793 groupValue: true,
794 title: Text('A', key: childKey),
795 onChanged: (bool? value) {},
796 ),
797 ),
798 ),
799 );
800
801 await tester.pump();
802 final FocusNode tileNode = Focus.of(childKey.currentContext!);
803 tileNode.requestFocus();
804 await tester.pump(); // Let the focus take effect.
805 expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isTrue);
806 expect(tileNode.hasPrimaryFocus, isTrue);
807 });
808
809 testWidgets('RadioListTile onFocusChange callback', (WidgetTester tester) async {
810 final FocusNode node = FocusNode(debugLabel: 'RadioListTile onFocusChange');
811 addTearDown(node.dispose);
812
813 bool gotFocus = false;
814 await tester.pumpWidget(
815 MaterialApp(
816 home: Material(
817 child: RadioListTile<bool>(
818 value: true,
819 focusNode: node,
820 onFocusChange: (bool focused) {
821 gotFocus = focused;
822 },
823 onChanged: (bool? value) {},
824 groupValue: true,
825 ),
826 ),
827 ),
828 );
829
830 node.requestFocus();
831 await tester.pump();
832 expect(gotFocus, isTrue);
833 expect(node.hasFocus, isTrue);
834
835 node.unfocus();
836 await tester.pump();
837 expect(gotFocus, isFalse);
838 expect(node.hasFocus, isFalse);
839 });
840
841 testWidgets('Radio changes mouse cursor when hovered', (WidgetTester tester) async {
842 // Test Radio() constructor
843 await tester.pumpWidget(
844 wrap(
845 child: MouseRegion(
846 cursor: SystemMouseCursors.forbidden,
847 child: RadioListTile<int>(
848 mouseCursor: SystemMouseCursors.text,
849 value: 1,
850 onChanged: (int? v) {},
851 groupValue: 2,
852 ),
853 ),
854 ),
855 );
856
857 final TestGesture gesture = await tester.createGesture(
858 kind: PointerDeviceKind.mouse,
859 pointer: 1,
860 );
861 await gesture.addPointer(location: tester.getCenter(find.byType(Radio<int>)));
862
863 await tester.pump();
864
865 expect(
866 RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
867 SystemMouseCursors.text,
868 );
869
870 // Test default cursor
871 await tester.pumpWidget(
872 wrap(
873 child: MouseRegion(
874 cursor: SystemMouseCursors.forbidden,
875 child: RadioListTile<int>(value: 1, onChanged: (int? v) {}, groupValue: 2),
876 ),
877 ),
878 );
879
880 expect(
881 RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
882 SystemMouseCursors.click,
883 );
884
885 // Test default cursor when disabled
886 await tester.pumpWidget(
887 wrap(
888 child: const MouseRegion(
889 cursor: SystemMouseCursors.forbidden,
890 child: RadioListTile<int>(value: 1, onChanged: null, groupValue: 2),
891 ),
892 ),
893 );
894
895 expect(
896 RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
897 SystemMouseCursors.basic,
898 );
899 });
900
901 testWidgets('RadioListTile respects fillColor in enabled/disabled states', (
902 WidgetTester tester,
903 ) async {
904 const Color activeEnabledFillColor = Color(0xFF000001);
905 const Color activeDisabledFillColor = Color(0xFF000002);
906 const Color inactiveEnabledFillColor = Color(0xFF000003);
907 const Color inactiveDisabledFillColor = Color(0xFF000004);
908
909 Color getFillColor(Set<MaterialState> states) {
910 if (states.contains(MaterialState.disabled)) {
911 if (states.contains(MaterialState.selected)) {
912 return activeDisabledFillColor;
913 }
914 return inactiveDisabledFillColor;
915 }
916 if (states.contains(MaterialState.selected)) {
917 return activeEnabledFillColor;
918 }
919 return inactiveEnabledFillColor;
920 }
921
922 final MaterialStateProperty<Color> fillColor = MaterialStateColor.resolveWith(getFillColor);
923
924 int? groupValue = 0;
925 Widget buildApp({required bool enabled}) {
926 return wrap(
927 child: StatefulBuilder(
928 builder: (BuildContext context, StateSetter setState) {
929 return RadioListTile<int>(
930 value: 0,
931 fillColor: fillColor,
932 onChanged:
933 enabled
934 ? (int? newValue) {
935 setState(() {
936 groupValue = newValue;
937 });
938 }
939 : null,
940 groupValue: groupValue,
941 );
942 },
943 ),
944 );
945 }
946
947 await tester.pumpWidget(buildApp(enabled: true));
948
949 // Selected and enabled.
950 await tester.pumpAndSettle();
951 expect(
952 Material.of(tester.element(find.byType(Radio<int>))),
953 paints
954 ..rect()
955 ..circle(color: activeEnabledFillColor)
956 ..circle(color: activeEnabledFillColor),
957 );
958
959 // Check when the radio isn't selected.
960 groupValue = 1;
961 await tester.pumpWidget(buildApp(enabled: true));
962 await tester.pumpAndSettle();
963 expect(
964 Material.of(tester.element(find.byType(Radio<int>))),
965 paints
966 ..rect()
967 ..circle(color: inactiveEnabledFillColor, style: PaintingStyle.stroke, strokeWidth: 2.0),
968 );
969
970 // Check when the radio is selected, but disabled.
971 groupValue = 0;
972 await tester.pumpWidget(buildApp(enabled: false));
973 await tester.pumpAndSettle();
974 expect(
975 Material.of(tester.element(find.byType(Radio<int>))),
976 paints
977 ..rect()
978 ..circle(color: activeDisabledFillColor)
979 ..circle(color: activeDisabledFillColor),
980 );
981
982 // Check when the radio is unselected and disabled.
983 groupValue = 1;
984 await tester.pumpWidget(buildApp(enabled: false));
985 await tester.pumpAndSettle();
986 expect(
987 Material.of(tester.element(find.byType(Radio<int>))),
988 paints
989 ..rect()
990 ..circle(color: inactiveDisabledFillColor, style: PaintingStyle.stroke, strokeWidth: 2.0),
991 );
992 });
993
994 testWidgets('RadioListTile respects fillColor in hovered state', (WidgetTester tester) async {
995 tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
996 const Color hoveredFillColor = Color(0xFF000001);
997
998 Color getFillColor(Set<MaterialState> states) {
999 if (states.contains(MaterialState.hovered)) {
1000 return hoveredFillColor;
1001 }
1002 return Colors.transparent;
1003 }
1004
1005 final MaterialStateProperty<Color> fillColor = MaterialStateColor.resolveWith(getFillColor);
1006
1007 int? groupValue = 0;
1008 Widget buildApp() {
1009 return wrap(
1010 child: StatefulBuilder(
1011 builder: (BuildContext context, StateSetter setState) {
1012 return RadioListTile<int>(
1013 value: 0,
1014 fillColor: fillColor,
1015 onChanged: (int? newValue) {
1016 setState(() {
1017 groupValue = newValue;
1018 });
1019 },
1020 groupValue: groupValue,
1021 );
1022 },
1023 ),
1024 );
1025 }
1026
1027 await tester.pumpWidget(buildApp());
1028 await tester.pumpAndSettle();
1029
1030 // Start hovering
1031 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1032 await gesture.addPointer();
1033 await gesture.moveTo(tester.getCenter(find.byType(Radio<int>)));
1034 await tester.pumpAndSettle();
1035
1036 expect(
1037 Material.of(tester.element(find.byType(Radio<int>))),
1038 paints
1039 ..rect()
1040 ..circle()
1041 ..circle(color: hoveredFillColor),
1042 );
1043 });
1044
1045 testWidgets('Material3 - RadioListTile respects hoverColor', (WidgetTester tester) async {
1046 tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
1047 int? groupValue = 0;
1048 final Color? hoverColor = Colors.orange[500];
1049 final ThemeData theme = ThemeData();
1050 Widget buildApp({bool enabled = true}) {
1051 return wrap(
1052 child: MaterialApp(
1053 theme: theme,
1054 home: StatefulBuilder(
1055 builder: (BuildContext context, StateSetter setState) {
1056 return RadioListTile<int>(
1057 value: 0,
1058 onChanged:
1059 enabled
1060 ? (int? newValue) {
1061 setState(() {
1062 groupValue = newValue;
1063 });
1064 }
1065 : null,
1066 hoverColor: hoverColor,
1067 groupValue: groupValue,
1068 );
1069 },
1070 ),
1071 ),
1072 );
1073 }
1074
1075 await tester.pumpWidget(buildApp());
1076
1077 await tester.pump();
1078 await tester.pumpAndSettle();
1079 expect(
1080 Material.of(tester.element(find.byType(Radio<int>))),
1081 paints
1082 ..rect()
1083 ..circle(color: theme.colorScheme.primary)
1084 ..circle(color: theme.colorScheme.primary),
1085 );
1086
1087 // Start hovering
1088 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1089 await gesture.moveTo(tester.getCenter(find.byType(Radio<int>)));
1090
1091 // Check when the radio isn't selected.
1092 groupValue = 1;
1093 await tester.pumpWidget(buildApp());
1094 await tester.pump();
1095 await tester.pumpAndSettle();
1096 expect(
1097 Material.of(tester.element(find.byType(Radio<int>))),
1098 paints
1099 ..rect()
1100 ..circle(color: hoverColor),
1101 );
1102
1103 // Check when the radio is selected, but disabled.
1104 groupValue = 0;
1105 await tester.pumpWidget(buildApp(enabled: false));
1106 await tester.pump();
1107 await tester.pumpAndSettle();
1108 expect(
1109 Material.of(tester.element(find.byType(Radio<int>))),
1110 paints
1111 ..rect()
1112 ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38))
1113 ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)),
1114 );
1115 });
1116
1117 testWidgets('Material3 - RadioListTile respects overlayColor in active/pressed/hovered states', (
1118 WidgetTester tester,
1119 ) async {
1120 tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
1121
1122 const Color fillColor = Color(0xFF000000);
1123 const Color activePressedOverlayColor = Color(0xFF000001);
1124 const Color inactivePressedOverlayColor = Color(0xFF000002);
1125 const Color hoverOverlayColor = Color(0xFF000003);
1126 const Color hoverColor = Color(0xFF000005);
1127
1128 Color? getOverlayColor(Set<MaterialState> states) {
1129 if (states.contains(MaterialState.pressed)) {
1130 if (states.contains(MaterialState.selected)) {
1131 return activePressedOverlayColor;
1132 }
1133 return inactivePressedOverlayColor;
1134 }
1135 if (states.contains(MaterialState.hovered)) {
1136 return hoverOverlayColor;
1137 }
1138 return null;
1139 }
1140
1141 Widget buildRadio({bool active = false, bool useOverlay = true}) {
1142 return MaterialApp(
1143 home: Material(
1144 child: RadioListTile<bool>(
1145 value: active,
1146 groupValue: true,
1147 onChanged: (_) {},
1148 fillColor: const MaterialStatePropertyAll<Color>(fillColor),
1149 overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
1150 hoverColor: hoverColor,
1151 ),
1152 ),
1153 );
1154 }
1155
1156 await tester.pumpWidget(buildRadio(useOverlay: false));
1157 await tester.press(find.byType(Radio<bool>));
1158 await tester.pumpAndSettle();
1159
1160 expect(
1161 Material.of(tester.element(find.byType(Radio<bool>))),
1162 paints
1163 ..rect(color: const Color(0x00000000))
1164 ..rect(color: const Color(0x66bcbcbc))
1165 ..circle(color: fillColor.withAlpha(kRadialReactionAlpha), radius: 20.0),
1166 reason: 'Default inactive pressed Radio should have overlay color from fillColor',
1167 );
1168
1169 await tester.pumpWidget(buildRadio(active: true, useOverlay: false));
1170 await tester.press(find.byType(Radio<bool>));
1171 await tester.pumpAndSettle();
1172
1173 expect(
1174 Material.of(tester.element(find.byType(Radio<bool>))),
1175 paints
1176 ..rect(color: const Color(0x00000000))
1177 ..rect(color: const Color(0x66bcbcbc))
1178 ..circle(color: fillColor.withAlpha(kRadialReactionAlpha), radius: 20.0),
1179 reason: 'Default active pressed Radio should have overlay color from fillColor',
1180 );
1181
1182 await tester.pumpWidget(buildRadio());
1183 await tester.press(find.byType(Radio<bool>));
1184 await tester.pumpAndSettle();
1185
1186 expect(
1187 Material.of(tester.element(find.byType(Radio<bool>))),
1188 paints
1189 ..rect(color: const Color(0x00000000))
1190 ..rect(color: const Color(0x66bcbcbc))
1191 ..circle(color: inactivePressedOverlayColor, radius: 20.0),
1192 reason: 'Inactive pressed Radio should have overlay color: $inactivePressedOverlayColor',
1193 );
1194
1195 await tester.pumpWidget(buildRadio(active: true));
1196 await tester.press(find.byType(Radio<bool>));
1197 await tester.pumpAndSettle();
1198
1199 expect(
1200 Material.of(tester.element(find.byType(Radio<bool>))),
1201 paints
1202 ..rect(color: const Color(0x00000000))
1203 ..rect(color: const Color(0x66bcbcbc))
1204 ..circle(color: activePressedOverlayColor, radius: 20.0),
1205 reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor',
1206 );
1207
1208 // Start hovering.
1209 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1210 await gesture.addPointer();
1211 await gesture.moveTo(tester.getCenter(find.byType(Radio<bool>)));
1212 await tester.pumpAndSettle();
1213
1214 await tester.pumpWidget(Container());
1215 await tester.pumpWidget(buildRadio());
1216 await tester.pumpAndSettle();
1217
1218 expect(
1219 Material.of(tester.element(find.byType(Radio<bool>))),
1220 paints
1221 ..rect(color: const Color(0x00000000))
1222 ..rect(color: const Color(0x0a000000))
1223 ..circle(color: hoverOverlayColor, radius: 20.0),
1224 reason: 'Hovered Radio should use overlay color $hoverOverlayColor over $hoverColor',
1225 );
1226 });
1227
1228 testWidgets('RadioListTile respects splashRadius', (WidgetTester tester) async {
1229 tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
1230 const double splashRadius = 30;
1231 Widget buildApp() {
1232 return wrap(
1233 child: StatefulBuilder(
1234 builder: (BuildContext context, StateSetter setState) {
1235 return RadioListTile<int>(
1236 value: 0,
1237 onChanged: (_) {},
1238 hoverColor: Colors.orange[500],
1239 groupValue: 0,
1240 splashRadius: splashRadius,
1241 );
1242 },
1243 ),
1244 );
1245 }
1246
1247 await tester.pumpWidget(buildApp());
1248 await tester.pumpAndSettle();
1249
1250 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1251 await gesture.addPointer();
1252 await gesture.moveTo(tester.getCenter(find.byType(Radio<int>)));
1253 await tester.pumpAndSettle();
1254
1255 expect(
1256 Material.of(tester.element(find.byWidgetPredicate((Widget widget) => widget is Radio<int>))),
1257 paints..circle(color: Colors.orange[500], radius: splashRadius),
1258 );
1259 });
1260
1261 testWidgets('Radio respects materialTapTargetSize', (WidgetTester tester) async {
1262 await tester.pumpWidget(
1263 wrap(
1264 child: RadioListTile<bool>(groupValue: true, value: true, onChanged: (bool? newValue) {}),
1265 ),
1266 );
1267
1268 // default test
1269 expect(tester.getSize(find.byType(Radio<bool>)), const Size(40.0, 40.0));
1270
1271 await tester.pumpWidget(
1272 wrap(
1273 child: RadioListTile<bool>(
1274 materialTapTargetSize: MaterialTapTargetSize.padded,
1275 groupValue: true,
1276 value: true,
1277 onChanged: (bool? newValue) {},
1278 ),
1279 ),
1280 );
1281
1282 expect(tester.getSize(find.byType(Radio<bool>)), const Size(48.0, 48.0));
1283 });
1284
1285 testWidgets('RadioListTile.control widget should not request focus on traversal', (
1286 WidgetTester tester,
1287 ) async {
1288 final GlobalKey firstChildKey = GlobalKey();
1289 final GlobalKey secondChildKey = GlobalKey();
1290
1291 await tester.pumpWidget(
1292 MaterialApp(
1293 home: Material(
1294 child: Column(
1295 children: <Widget>[
1296 RadioListTile<bool>(
1297 value: true,
1298 groupValue: true,
1299 onChanged: (bool? value) {},
1300 title: Text('Hey', key: firstChildKey),
1301 ),
1302 RadioListTile<bool>(
1303 value: true,
1304 groupValue: true,
1305 onChanged: (bool? value) {},
1306 title: Text('There', key: secondChildKey),
1307 ),
1308 ],
1309 ),
1310 ),
1311 ),
1312 );
1313
1314 await tester.pump();
1315 Focus.of(firstChildKey.currentContext!).requestFocus();
1316 await tester.pump();
1317 expect(Focus.of(firstChildKey.currentContext!).hasPrimaryFocus, isTrue);
1318 Focus.of(firstChildKey.currentContext!).nextFocus();
1319 await tester.pump();
1320 expect(Focus.of(firstChildKey.currentContext!).hasPrimaryFocus, isFalse);
1321 expect(Focus.of(secondChildKey.currentContext!).hasPrimaryFocus, isTrue);
1322 });
1323
1324 testWidgets('RadioListTile.adaptive shows the correct radio platform widget', (
1325 WidgetTester tester,
1326 ) async {
1327 Widget buildApp(TargetPlatform platform) {
1328 return MaterialApp(
1329 theme: ThemeData(platform: platform),
1330 home: Material(
1331 child: Center(
1332 child: RadioListTile<int>.adaptive(value: 1, groupValue: 2, onChanged: (_) {}),
1333 ),
1334 ),
1335 );
1336 }
1337
1338 for (final TargetPlatform platform in <TargetPlatform>[
1339 TargetPlatform.iOS,
1340 TargetPlatform.macOS,
1341 ]) {
1342 await tester.pumpWidget(buildApp(platform));
1343 await tester.pumpAndSettle();
1344
1345 expect(find.byType(CupertinoRadio<int>), findsOneWidget);
1346 }
1347
1348 for (final TargetPlatform platform in <TargetPlatform>[
1349 TargetPlatform.android,
1350 TargetPlatform.fuchsia,
1351 TargetPlatform.linux,
1352 TargetPlatform.windows,
1353 ]) {
1354 await tester.pumpWidget(buildApp(platform));
1355 await tester.pumpAndSettle();
1356
1357 expect(find.byType(CupertinoRadio<int>), findsNothing);
1358 }
1359 });
1360
1361 group('feedback', () {
1362 late FeedbackTester feedback;
1363
1364 setUp(() {
1365 feedback = FeedbackTester();
1366 });
1367
1368 tearDown(() {
1369 feedback.dispose();
1370 });
1371
1372 testWidgets('RadioListTile respects enableFeedback', (WidgetTester tester) async {
1373 const Key key = Key('test');
1374 Future<void> buildTest(bool enableFeedback) async {
1375 return tester.pumpWidget(
1376 wrap(
1377 child: Center(
1378 child: RadioListTile<bool>(
1379 key: key,
1380 value: false,
1381 groupValue: true,
1382 selected: true,
1383 onChanged: (bool? value) {},
1384 enableFeedback: enableFeedback,
1385 ),
1386 ),
1387 ),
1388 );
1389 }
1390
1391 await buildTest(false);
1392 await tester.tap(find.byKey(key));
1393 await tester.pump(const Duration(seconds: 1));
1394 expect(feedback.clickSoundCount, 0);
1395 expect(feedback.hapticCount, 0);
1396
1397 await buildTest(true);
1398 await tester.tap(find.byKey(key));
1399 await tester.pump(const Duration(seconds: 1));
1400 expect(feedback.clickSoundCount, 1);
1401 expect(feedback.hapticCount, 0);
1402 });
1403 });
1404
1405 group('Material 2', () {
1406 // These tests are only relevant for Material 2. Once Material 2
1407 // support is deprecated and the APIs are removed, these tests
1408 // can be deleted.
1409
1410 testWidgets(
1411 'Material2 - RadioListTile respects overlayColor in active/pressed/hovered states',
1412 (WidgetTester tester) async {
1413 tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
1414
1415 const Color fillColor = Color(0xFF000000);
1416 const Color activePressedOverlayColor = Color(0xFF000001);
1417 const Color inactivePressedOverlayColor = Color(0xFF000002);
1418 const Color hoverOverlayColor = Color(0xFF000003);
1419 const Color hoverColor = Color(0xFF000005);
1420
1421 Color? getOverlayColor(Set<MaterialState> states) {
1422 if (states.contains(MaterialState.pressed)) {
1423 if (states.contains(MaterialState.selected)) {
1424 return activePressedOverlayColor;
1425 }
1426 return inactivePressedOverlayColor;
1427 }
1428 if (states.contains(MaterialState.hovered)) {
1429 return hoverOverlayColor;
1430 }
1431 return null;
1432 }
1433
1434 Widget buildRadio({bool active = false, bool useOverlay = true}) {
1435 return MaterialApp(
1436 theme: ThemeData(useMaterial3: false),
1437 home: Material(
1438 child: RadioListTile<bool>(
1439 value: active,
1440 groupValue: true,
1441 onChanged: (_) {},
1442 fillColor: const MaterialStatePropertyAll<Color>(fillColor),
1443 overlayColor:
1444 useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
1445 hoverColor: hoverColor,
1446 ),
1447 ),
1448 );
1449 }
1450
1451 await tester.pumpWidget(buildRadio(useOverlay: false));
1452 await tester.press(find.byType(Radio<bool>));
1453 await tester.pumpAndSettle();
1454
1455 expect(
1456 Material.of(tester.element(find.byType(Radio<bool>))),
1457 paints
1458 ..circle()
1459 ..circle(color: fillColor.withAlpha(kRadialReactionAlpha), radius: 20),
1460 reason: 'Default inactive pressed Radio should have overlay color from fillColor',
1461 );
1462
1463 await tester.pumpWidget(buildRadio(active: true, useOverlay: false));
1464 await tester.press(find.byType(Radio<bool>));
1465 await tester.pumpAndSettle();
1466
1467 expect(
1468 Material.of(tester.element(find.byType(Radio<bool>))),
1469 paints
1470 ..circle()
1471 ..circle(color: fillColor.withAlpha(kRadialReactionAlpha), radius: 20),
1472 reason: 'Default active pressed Radio should have overlay color from fillColor',
1473 );
1474
1475 await tester.pumpWidget(buildRadio());
1476 await tester.press(find.byType(Radio<bool>));
1477 await tester.pumpAndSettle();
1478
1479 expect(
1480 Material.of(tester.element(find.byType(Radio<bool>))),
1481 paints
1482 ..circle()
1483 ..circle(color: inactivePressedOverlayColor, radius: 20),
1484 reason: 'Inactive pressed Radio should have overlay color: $inactivePressedOverlayColor',
1485 );
1486
1487 // Start hovering.
1488 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1489 await gesture.addPointer();
1490 await gesture.moveTo(tester.getCenter(find.byType(Radio<bool>)));
1491 await tester.pumpAndSettle();
1492
1493 await tester.pumpWidget(Container());
1494 await tester.pumpWidget(buildRadio());
1495 await tester.pumpAndSettle();
1496
1497 expect(
1498 Material.of(tester.element(find.byType(Radio<bool>))),
1499 paints..circle(color: hoverOverlayColor, radius: 20),
1500 reason: 'Hovered Radio should use overlay color $hoverOverlayColor over $hoverColor',
1501 );
1502 },
1503 );
1504
1505 testWidgets('Material2 - RadioListTile respects hoverColor', (WidgetTester tester) async {
1506 tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
1507 int? groupValue = 0;
1508 final Color? hoverColor = Colors.orange[500];
1509 Widget buildApp({bool enabled = true}) {
1510 return wrap(
1511 child: MaterialApp(
1512 theme: ThemeData(useMaterial3: false),
1513 home: StatefulBuilder(
1514 builder: (BuildContext context, StateSetter setState) {
1515 return RadioListTile<int>(
1516 value: 0,
1517 onChanged:
1518 enabled
1519 ? (int? newValue) {
1520 setState(() {
1521 groupValue = newValue;
1522 });
1523 }
1524 : null,
1525 hoverColor: hoverColor,
1526 groupValue: groupValue,
1527 );
1528 },
1529 ),
1530 ),
1531 );
1532 }
1533
1534 await tester.pumpWidget(buildApp());
1535
1536 await tester.pump();
1537 await tester.pumpAndSettle();
1538 expect(
1539 Material.of(tester.element(find.byType(Radio<int>))),
1540 paints
1541 ..rect()
1542 ..circle(color: const Color(0xff2196f3))
1543 ..circle(color: const Color(0xff2196f3)),
1544 );
1545
1546 // Start hovering
1547 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
1548 await gesture.moveTo(tester.getCenter(find.byType(Radio<int>)));
1549
1550 // Check when the radio isn't selected.
1551 groupValue = 1;
1552 await tester.pumpWidget(buildApp());
1553 await tester.pump();
1554 await tester.pumpAndSettle();
1555 expect(
1556 Material.of(tester.element(find.byType(Radio<int>))),
1557 paints
1558 ..rect()
1559 ..circle(color: hoverColor),
1560 );
1561
1562 // Check when the radio is selected, but disabled.
1563 groupValue = 0;
1564 await tester.pumpWidget(buildApp(enabled: false));
1565 await tester.pump();
1566 await tester.pumpAndSettle();
1567 expect(
1568 Material.of(tester.element(find.byType(Radio<int>))),
1569 paints
1570 ..rect()
1571 ..circle(color: const Color(0x61000000))
1572 ..circle(color: const Color(0x61000000)),
1573 );
1574 });
1575 });
1576
1577 testWidgets('RadioListTile uses ListTileTheme controlAffinity', (WidgetTester tester) async {
1578 Widget buildListTile(ListTileControlAffinity controlAffinity) {
1579 return MaterialApp(
1580 home: Material(
1581 child: ListTileTheme(
1582 data: ListTileThemeData(controlAffinity: controlAffinity),
1583 child: RadioListTile<double>(
1584 value: 0.5,
1585 groupValue: 1.0,
1586 title: const Text('RadioListTile'),
1587 onChanged: (double? value) {},
1588 ),
1589 ),
1590 ),
1591 );
1592 }
1593
1594 await tester.pumpWidget(buildListTile(ListTileControlAffinity.leading));
1595 final Finder leading = find.text('RadioListTile');
1596 final Offset offsetLeading = tester.getTopLeft(leading);
1597 expect(offsetLeading, const Offset(72.0, 16.0));
1598
1599 await tester.pumpWidget(buildListTile(ListTileControlAffinity.trailing));
1600 final Finder trailing = find.text('RadioListTile');
1601 final Offset offsetTrailing = tester.getTopLeft(trailing);
1602 expect(offsetTrailing, const Offset(16.0, 16.0));
1603
1604 await tester.pumpWidget(buildListTile(ListTileControlAffinity.platform));
1605 final Finder platform = find.text('RadioListTile');
1606 final Offset offsetPlatform = tester.getTopLeft(platform);
1607 expect(offsetPlatform, const Offset(72.0, 16.0));
1608 });
1609
1610 testWidgets('RadioListTile renders with default scale', (WidgetTester tester) async {
1611 await tester.pumpWidget(
1612 const MaterialApp(
1613 home: Material(
1614 child: RadioListTile<bool>(value: false, groupValue: false, onChanged: null),
1615 ),
1616 ),
1617 );
1618
1619 final Finder transformFinder = find.ancestor(
1620 of: find.byType(Radio<bool>),
1621 matching: find.byType(Transform),
1622 );
1623
1624 expect(transformFinder, findsNothing);
1625 });
1626
1627 testWidgets('RadioListTile respects radioScaleFactor', (WidgetTester tester) async {
1628 const double scale = 1.4;
1629 await tester.pumpWidget(
1630 const MaterialApp(
1631 home: Material(
1632 child: RadioListTile<bool>(
1633 value: false,
1634 groupValue: false,
1635 onChanged: null,
1636 radioScaleFactor: scale,
1637 ),
1638 ),
1639 ),
1640 );
1641
1642 final Transform widget = tester.widget(
1643 find.ancestor(of: find.byType(Radio<bool>), matching: find.byType(Transform)),
1644 );
1645
1646 expect(widget.transform.getMaxScaleOnAxis(), scale);
1647 });
1648}
1649

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com