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/foundation.dart';
6import 'package:flutter/gestures.dart';
7import 'package:flutter/material.dart';
8import 'package:flutter/services.dart';
9import 'package:flutter_test/flutter_test.dart';
10
11void main() {
12 RenderObject getOverlayColor(WidgetTester tester) {
13 return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
14 }
15
16 test('SegmentedButtonThemeData copyWith, ==, hashCode basics', () {
17 expect(const SegmentedButtonThemeData(), const SegmentedButtonThemeData().copyWith());
18 expect(const SegmentedButtonThemeData().hashCode, const SegmentedButtonThemeData().copyWith().hashCode);
19
20 const SegmentedButtonThemeData custom = SegmentedButtonThemeData(
21 style: ButtonStyle(backgroundColor: MaterialStatePropertyAll<Color>(Colors.green)),
22 selectedIcon: Icon(Icons.error),
23 );
24 final SegmentedButtonThemeData copy = const SegmentedButtonThemeData().copyWith(
25 style: custom.style,
26 selectedIcon: custom.selectedIcon,
27 );
28 expect(copy, custom);
29 });
30
31 test('SegmentedButtonThemeData lerp special cases', () {
32 expect(SegmentedButtonThemeData.lerp(null, null, 0), const SegmentedButtonThemeData());
33 const SegmentedButtonThemeData theme = SegmentedButtonThemeData();
34 expect(identical(SegmentedButtonThemeData.lerp(theme, theme, 0.5), theme), true);
35 });
36
37 testWidgets('Default SegmentedButtonThemeData debugFillProperties', (WidgetTester tester) async {
38 final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
39 const SegmentedButtonThemeData().debugFillProperties(builder);
40
41 final List<String> description = builder.properties
42 .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
43 .map((DiagnosticsNode node) => node.toString())
44 .toList();
45
46 expect(description, <String>[]);
47 });
48
49 testWidgets('With no other configuration, defaults are used', (WidgetTester tester) async {
50 final ThemeData theme = ThemeData(useMaterial3: true);
51 await tester.pumpWidget(
52 MaterialApp(
53 theme: theme,
54 home: Scaffold(
55 body: Center(
56 child: SegmentedButton<int>(
57 segments: const <ButtonSegment<int>>[
58 ButtonSegment<int>(value: 1, label: Text('1')),
59 ButtonSegment<int>(value: 2, label: Text('2')),
60 ButtonSegment<int>(value: 3, label: Text('3'), enabled: false),
61 ],
62 selected: const <int>{2},
63 onSelectionChanged: (Set<int> selected) { },
64 ),
65 ),
66 ),
67 ),
68 );
69
70 // Test first segment, should be enabled
71 {
72 final Finder text = find.text('1');
73 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
74 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.check));
75 final Material material = tester.widget<Material>(parent);
76 expect(material.color, Colors.transparent);
77 expect(material.shape, const RoundedRectangleBorder());
78 expect(material.textStyle!.color, theme.colorScheme.onSurface);
79 expect(material.textStyle!.fontFamily, 'Roboto');
80 expect(material.textStyle!.fontSize, 14);
81 expect(material.textStyle!.fontWeight, FontWeight.w500);
82 expect(selectedIcon, findsNothing);
83 }
84
85 // Test second segment, should be enabled and selected
86 {
87 final Finder text = find.text('2');
88 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
89 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.check));
90 final Material material = tester.widget<Material>(parent);
91 expect(material.color, theme.colorScheme.secondaryContainer);
92 expect(material.shape, const RoundedRectangleBorder());
93 expect(material.textStyle!.color, theme.colorScheme.onSecondaryContainer);
94 expect(material.textStyle!.fontFamily, 'Roboto');
95 expect(material.textStyle!.fontSize, 14);
96 expect(material.textStyle!.fontWeight, FontWeight.w500);
97 expect(selectedIcon, findsOneWidget);
98 }
99
100 // Test last segment, should be disabled
101 {
102 final Finder text = find.text('3');
103 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
104 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.check));
105 final Material material = tester.widget<Material>(parent);
106 expect(material.color, Colors.transparent);
107 expect(material.shape, const RoundedRectangleBorder());
108 expect(material.textStyle!.color, theme.colorScheme.onSurface.withOpacity(0.38));
109 expect(material.textStyle!.fontFamily, 'Roboto');
110 expect(material.textStyle!.fontSize, 14);
111 expect(material.textStyle!.fontWeight, FontWeight.w500);
112 expect(selectedIcon, findsNothing);
113 }
114 });
115
116 testWidgets('ThemeData.segmentedButtonTheme overrides defaults', (WidgetTester tester) async {
117 final ThemeData theme = ThemeData(
118 useMaterial3: true,
119 segmentedButtonTheme: SegmentedButtonThemeData(
120 style: ButtonStyle(
121 backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
122 if (states.contains(MaterialState.disabled)) {
123 return Colors.blue;
124 }
125 if (states.contains(MaterialState.selected)) {
126 return Colors.purple;
127 }
128 return null;
129 }),
130 foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
131 if (states.contains(MaterialState.disabled)) {
132 return Colors.yellow;
133 }
134 if (states.contains(MaterialState.selected)) {
135 return Colors.brown;
136 } else {
137 return Colors.cyan;
138 }
139 }),
140 ),
141 selectedIcon: const Icon(Icons.error),
142 ),
143 );
144 await tester.pumpWidget(
145 MaterialApp(
146 theme: theme,
147 home: Scaffold(
148 body: Center(
149 child: SegmentedButton<int>(
150 segments: const <ButtonSegment<int>>[
151 ButtonSegment<int>(value: 1, label: Text('1')),
152 ButtonSegment<int>(value: 2, label: Text('2')),
153 ButtonSegment<int>(value: 3, label: Text('3'), enabled: false),
154 ],
155 selected: const <int>{2},
156 onSelectionChanged: (Set<int> selected) { },
157 ),
158 ),
159 ),
160 ),
161 );
162
163 // Test first segment, should be enabled
164 {
165 final Finder text = find.text('1');
166 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
167 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.error));
168 final Material material = tester.widget<Material>(parent);
169 expect(material.color, Colors.transparent);
170 expect(material.shape, const RoundedRectangleBorder());
171 expect(material.textStyle!.color, Colors.cyan);
172 expect(material.textStyle!.fontFamily, 'Roboto');
173 expect(material.textStyle!.fontSize, 14);
174 expect(material.textStyle!.fontWeight, FontWeight.w500);
175 expect(selectedIcon, findsNothing);
176 }
177
178 // Test second segment, should be enabled and selected
179 {
180 final Finder text = find.text('2');
181 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
182 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.error));
183 final Material material = tester.widget<Material>(parent);
184 expect(material.color, Colors.purple);
185 expect(material.shape, const RoundedRectangleBorder());
186 expect(material.textStyle!.color, Colors.brown);
187 expect(material.textStyle!.fontFamily, 'Roboto');
188 expect(material.textStyle!.fontSize, 14);
189 expect(material.textStyle!.fontWeight, FontWeight.w500);
190 expect(selectedIcon, findsOneWidget);
191 }
192
193 // Test last segment, should be disabled
194 {
195 final Finder text = find.text('3');
196 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
197 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.error));
198 final Material material = tester.widget<Material>(parent);
199 expect(material.color, Colors.blue);
200 expect(material.shape, const RoundedRectangleBorder());
201 expect(material.textStyle!.color, Colors.yellow);
202 expect(material.textStyle!.fontFamily, 'Roboto');
203 expect(material.textStyle!.fontSize, 14);
204 expect(material.textStyle!.fontWeight, FontWeight.w500);
205 expect(selectedIcon, findsNothing);
206 }
207 });
208
209 testWidgets('SegmentedButtonTheme overrides ThemeData and defaults', (WidgetTester tester) async {
210 final SegmentedButtonThemeData global = SegmentedButtonThemeData(
211 style: ButtonStyle(
212 backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
213 if (states.contains(MaterialState.disabled)) {
214 return Colors.blue;
215 }
216 if (states.contains(MaterialState.selected)) {
217 return Colors.purple;
218 }
219 return null;
220 }),
221 foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
222 if (states.contains(MaterialState.disabled)) {
223 return Colors.yellow;
224 }
225 if (states.contains(MaterialState.selected)) {
226 return Colors.brown;
227 } else {
228 return Colors.cyan;
229 }
230 }),
231 ),
232 selectedIcon: const Icon(Icons.error),
233 );
234 final SegmentedButtonThemeData segmentedTheme = SegmentedButtonThemeData(
235 style: ButtonStyle(
236 backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
237 if (states.contains(MaterialState.disabled)) {
238 return Colors.lightBlue;
239 }
240 if (states.contains(MaterialState.selected)) {
241 return Colors.lightGreen;
242 }
243 return null;
244 }),
245 foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
246 if (states.contains(MaterialState.disabled)) {
247 return Colors.lime;
248 }
249 if (states.contains(MaterialState.selected)) {
250 return Colors.amber;
251 } else {
252 return Colors.deepPurple;
253 }
254 }),
255 ),
256 selectedIcon: const Icon(Icons.plus_one),
257 );
258 final ThemeData theme = ThemeData(
259 useMaterial3: true,
260 segmentedButtonTheme: global,
261 );
262 await tester.pumpWidget(
263 MaterialApp(
264 theme: theme,
265 home: SegmentedButtonTheme(
266 data: segmentedTheme,
267 child: Scaffold(
268 body: Center(
269 child: SegmentedButton<int>(
270 segments: const <ButtonSegment<int>>[
271 ButtonSegment<int>(value: 1, label: Text('1')),
272 ButtonSegment<int>(value: 2, label: Text('2')),
273 ButtonSegment<int>(value: 3, label: Text('3'), enabled: false),
274 ],
275 selected: const <int>{2},
276 onSelectionChanged: (Set<int> selected) { },
277 ),
278 ),
279 ),
280 ),
281 ),
282 );
283
284 // Test first segment, should be enabled
285 {
286 final Finder text = find.text('1');
287 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
288 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.plus_one));
289 final Material material = tester.widget<Material>(parent);
290 expect(material.animationDuration, const Duration(milliseconds: 200));
291 expect(material.borderRadius, null);
292 expect(material.color, Colors.transparent);
293 expect(material.shape, const RoundedRectangleBorder());
294 expect(material.textStyle!.color, Colors.deepPurple);
295 expect(material.textStyle!.fontFamily, 'Roboto');
296 expect(material.textStyle!.fontSize, 14);
297 expect(material.textStyle!.fontWeight, FontWeight.w500);
298 expect(selectedIcon, findsNothing);
299 }
300
301 // Test second segment, should be enabled and selected
302 {
303 final Finder text = find.text('2');
304 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
305 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.plus_one));
306 final Material material = tester.widget<Material>(parent);
307 expect(material.animationDuration, const Duration(milliseconds: 200));
308 expect(material.borderRadius, null);
309 expect(material.color, Colors.lightGreen);
310 expect(material.shape, const RoundedRectangleBorder());
311 expect(material.textStyle!.color, Colors.amber);
312 expect(material.textStyle!.fontFamily, 'Roboto');
313 expect(material.textStyle!.fontSize, 14);
314 expect(material.textStyle!.fontWeight, FontWeight.w500);
315 expect(selectedIcon, findsOneWidget);
316 }
317
318 // Test last segment, should be disabled
319 {
320 final Finder text = find.text('3');
321 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
322 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.plus_one));
323 final Material material = tester.widget<Material>(parent);
324 expect(material.animationDuration, const Duration(milliseconds: 200));
325 expect(material.borderRadius, null);
326 expect(material.color, Colors.lightBlue);
327 expect(material.shape, const RoundedRectangleBorder());
328 expect(material.textStyle!.color, Colors.lime);
329 expect(material.textStyle!.fontFamily, 'Roboto');
330 expect(material.textStyle!.fontSize, 14);
331 expect(material.textStyle!.fontWeight, FontWeight.w500);
332 expect(selectedIcon, findsNothing);
333 }
334 });
335
336 testWidgets('Widget parameters overrides SegmentedTheme, ThemeData and defaults', (WidgetTester tester) async {
337 final SegmentedButtonThemeData global = SegmentedButtonThemeData(
338 style: ButtonStyle(
339 backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
340 if (states.contains(MaterialState.disabled)) {
341 return Colors.blue;
342 }
343 if (states.contains(MaterialState.selected)) {
344 return Colors.purple;
345 }
346 return null;
347 }),
348 foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
349 if (states.contains(MaterialState.disabled)) {
350 return Colors.yellow;
351 }
352 if (states.contains(MaterialState.selected)) {
353 return Colors.brown;
354 } else {
355 return Colors.cyan;
356 }
357 }),
358 ),
359 selectedIcon: const Icon(Icons.error),
360 );
361 final SegmentedButtonThemeData segmentedTheme = SegmentedButtonThemeData(
362 style: ButtonStyle(
363 backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
364 if (states.contains(MaterialState.disabled)) {
365 return Colors.lightBlue;
366 }
367 if (states.contains(MaterialState.selected)) {
368 return Colors.lightGreen;
369 }
370 return null;
371 }),
372 foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
373 if (states.contains(MaterialState.disabled)) {
374 return Colors.lime;
375 }
376 if (states.contains(MaterialState.selected)) {
377 return Colors.amber;
378 } else {
379 return Colors.deepPurple;
380 }
381 }),
382 ),
383 selectedIcon: const Icon(Icons.plus_one),
384 );
385 final ThemeData theme = ThemeData(
386 useMaterial3: true,
387 segmentedButtonTheme: global,
388 );
389 await tester.pumpWidget(
390 MaterialApp(
391 theme: theme,
392 home: SegmentedButtonTheme(
393 data: segmentedTheme,
394 child: Scaffold(
395 body: Center(
396 child: SegmentedButton<int>(
397 segments: const <ButtonSegment<int>>[
398 ButtonSegment<int>(value: 1, label: Text('1')),
399 ButtonSegment<int>(value: 2, label: Text('2')),
400 ButtonSegment<int>(value: 3, label: Text('3'), enabled: false),
401 ],
402 selected: const <int>{2},
403 onSelectionChanged: (Set<int> selected) { },
404 style: ButtonStyle(
405 backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
406 if (states.contains(MaterialState.disabled)) {
407 return Colors.black12;
408 }
409 if (states.contains(MaterialState.selected)) {
410 return Colors.grey;
411 }
412 return null;
413 }),
414 foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
415 if (states.contains(MaterialState.disabled)) {
416 return Colors.amberAccent;
417 }
418 if (states.contains(MaterialState.selected)) {
419 return Colors.deepOrange;
420 } else {
421 return Colors.deepPurpleAccent;
422 }
423 }),
424 ),
425 selectedIcon: const Icon(Icons.alarm),
426 ),
427 ),
428 ),
429 ),
430 ),
431 );
432
433 // Test first segment, should be enabled
434 {
435 final Finder text = find.text('1');
436 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
437 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.alarm));
438 final Material material = tester.widget<Material>(parent);
439 expect(material.animationDuration, const Duration(milliseconds: 200));
440 expect(material.borderRadius, null);
441 expect(material.color, Colors.transparent);
442 expect(material.shape, const RoundedRectangleBorder());
443 expect(material.textStyle!.color, Colors.deepPurpleAccent);
444 expect(material.textStyle!.fontFamily, 'Roboto');
445 expect(material.textStyle!.fontSize, 14);
446 expect(material.textStyle!.fontWeight, FontWeight.w500);
447 expect(selectedIcon, findsNothing);
448 }
449
450 // Test second segment, should be enabled and selected
451 {
452 final Finder text = find.text('2');
453 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
454 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.alarm));
455 final Material material = tester.widget<Material>(parent);
456 expect(material.animationDuration, const Duration(milliseconds: 200));
457 expect(material.borderRadius, null);
458 expect(material.color, Colors.grey);
459 expect(material.shape, const RoundedRectangleBorder());
460 expect(material.textStyle!.color, Colors.deepOrange);
461 expect(material.textStyle!.fontFamily, 'Roboto');
462 expect(material.textStyle!.fontSize, 14);
463 expect(material.textStyle!.fontWeight, FontWeight.w500);
464 expect(selectedIcon, findsOneWidget);
465 }
466
467 // Test last segment, should be disabled
468 {
469 final Finder text = find.text('3');
470 final Finder parent = find.ancestor(of: text, matching: find.byType(Material)).first;
471 final Finder selectedIcon = find.descendant(of: parent, matching: find.byIcon(Icons.alarm));
472 final Material material = tester.widget<Material>(parent);
473 expect(material.animationDuration, const Duration(milliseconds: 200));
474 expect(material.borderRadius, null);
475 expect(material.color, Colors.black12);
476 expect(material.shape, const RoundedRectangleBorder());
477 expect(material.textStyle!.color, Colors.amberAccent);
478 expect(material.textStyle!.fontFamily, 'Roboto');
479 expect(material.textStyle!.fontSize, 14);
480 expect(material.textStyle!.fontWeight, FontWeight.w500);
481 expect(selectedIcon, findsNothing);
482 }
483 });
484
485 testWidgets('SegmentedButtonTheme SegmentedButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async {
486 const Color overlayColor = Color(0xffff0000);
487 await tester.pumpWidget(
488 MaterialApp(
489 theme: ThemeData(
490 segmentedButtonTheme: SegmentedButtonThemeData(
491 style: SegmentedButton.styleFrom(overlayColor: overlayColor),
492 ),
493 ),
494 home: Scaffold(
495 body: Center(
496 child: SegmentedButton<int>(
497 segments: const <ButtonSegment<int>>[
498 ButtonSegment<int>(
499 value: 0,
500 label: Text('Option 1'),
501 ),
502 ButtonSegment<int>(
503 value: 1,
504 label: Text('Option 2'),
505 ),
506 ],
507 onSelectionChanged: (Set<int> selected) {},
508 selected: const <int>{1},
509 ),
510 ),
511 ),
512 ),
513 );
514
515 // Hovered selected segment,
516 Offset center = tester.getCenter(find.text('Option 1'));
517 final TestGesture gesture = await tester.createGesture(
518 kind: PointerDeviceKind.mouse,
519 );
520 await gesture.addPointer();
521 await gesture.moveTo(center);
522 await tester.pumpAndSettle();
523 expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08)));
524
525 // Hovered unselected segment,
526 center = tester.getCenter(find.text('Option 2'));
527 await gesture.moveTo(center);
528 await tester.pumpAndSettle();
529 expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08)));
530
531 // Highlighted unselected segment (pressed).
532 center = tester.getCenter(find.text('Option 1'));
533 await gesture.down(center);
534 await tester.pumpAndSettle();
535 expect(
536 getOverlayColor(tester),
537 paints
538 ..rect(color: overlayColor.withOpacity(0.08))
539 ..rect(color: overlayColor.withOpacity(0.1)),
540 );
541 // Remove pressed and hovered states,
542 await gesture.up();
543 await tester.pumpAndSettle();
544 await gesture.moveTo(const Offset(0, 50));
545 await tester.pumpAndSettle();
546
547 // Highlighted selected segment (pressed)
548 center = tester.getCenter(find.text('Option 2'));
549 await gesture.down(center);
550 await tester.pumpAndSettle();
551 expect(
552 getOverlayColor(tester),
553 paints
554 ..rect(color: overlayColor.withOpacity(0.08))
555 ..rect(color: overlayColor.withOpacity(0.1)),
556 );
557 // Remove pressed and hovered states,
558 await gesture.up();
559 await tester.pumpAndSettle();
560 await gesture.moveTo(const Offset(0, 50));
561 await tester.pumpAndSettle();
562
563 // Focused unselected segment.
564 await tester.sendKeyEvent(LogicalKeyboardKey.tab);
565 await tester.pumpAndSettle();
566 expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1)));
567
568 // Focused selected segment.
569 await tester.sendKeyEvent(LogicalKeyboardKey.tab);
570 await tester.pumpAndSettle();
571 expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1)));
572 });
573}
574

Provided by KDAB

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