• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 The Chromium 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/widgets.dart';
6import 'package:flutter/cupertino.dart';
7import 'package:flutter_test/flutter_test.dart';
8
9import '../widgets/semantics_tester.dart';
10
11dynamic getRenderSegmentedControl(WidgetTester tester) {
12  return tester.allRenderObjects.firstWhere(
13    (RenderObject currentObject) {
14      return currentObject.toStringShort().contains('_RenderSegmentedControl');
15    },
16  );
17}
18
19StatefulBuilder setupSimpleSegmentedControl() {
20  final Map<int, Widget> children = <int, Widget>{};
21  children[0] = const Text('Child 1');
22  children[1] = const Text('Child 2');
23  int sharedValue = 0;
24
25  return StatefulBuilder(
26    builder: (BuildContext context, StateSetter setState) {
27      return boilerplate(
28        child: CupertinoSegmentedControl<int>(
29          children: children,
30          onValueChanged: (int newValue) {
31            setState(() {
32              sharedValue = newValue;
33            });
34          },
35          groupValue: sharedValue,
36        ),
37      );
38    },
39  );
40}
41
42Widget boilerplate({ Widget child }) {
43  return Directionality(
44    textDirection: TextDirection.ltr,
45    child: Center(child: child),
46  );
47}
48
49Color getBackgroundColor(WidgetTester tester, int childIndex) {
50  return getRenderSegmentedControl(tester).backgroundColors[childIndex];
51}
52
53void main() {
54  testWidgets('Tap changes toggle state', (WidgetTester tester) async {
55    final Map<int, Widget> children = <int, Widget>{};
56    children[0] = const Text('Child 1');
57    children[1] = const Text('Child 2');
58    children[2] = const Text('Child 3');
59
60    int sharedValue = 0;
61
62    await tester.pumpWidget(
63      StatefulBuilder(
64        builder: (BuildContext context, StateSetter setState) {
65          return boilerplate(
66            child: CupertinoSegmentedControl<int>(
67              key: const ValueKey<String>('Segmented Control'),
68              children: children,
69              onValueChanged: (int newValue) {
70                setState(() {
71                  sharedValue = newValue;
72                });
73              },
74              groupValue: sharedValue,
75            ),
76          );
77        },
78      ),
79    );
80
81    expect(sharedValue, 0);
82
83    await tester.tap(find.byKey(const ValueKey<String>('Segmented Control')));
84
85    expect(sharedValue, 1);
86  });
87
88  testWidgets('Need at least 2 children', (WidgetTester tester) async {
89    final Map<int, Widget> children = <int, Widget>{};
90    try {
91      await tester.pumpWidget(
92        boilerplate(
93          child: CupertinoSegmentedControl<int>(
94            children: children,
95            onValueChanged: (int newValue) { },
96          ),
97        ),
98      );
99      fail('Should not be possible to create a segmented control with no children');
100    } on AssertionError catch (e) {
101      expect(e.toString(), contains('children.length'));
102    }
103    try {
104      children[0] = const Text('Child 1');
105
106      await tester.pumpWidget(
107        boilerplate(
108          child: CupertinoSegmentedControl<int>(
109            children: children,
110            onValueChanged: (int newValue) { },
111          ),
112        ),
113      );
114      fail('Should not be possible to create a segmented control with just one child');
115    } on AssertionError catch (e) {
116      expect(e.toString(), contains('children.length'));
117    }
118  });
119
120  testWidgets('Padding works', (WidgetTester tester) async {
121    const Key key = Key('Container');
122
123    final Map<int, Widget> children = <int, Widget>{};
124    children[0] = const SizedBox(
125      height: double.infinity,
126      child: Text('Child 1'),
127    ) ;
128    children[1] = const SizedBox(
129      height: double.infinity,
130      child: Text('Child 2'),
131    ) ;
132
133    Future<void> verifyPadding({ EdgeInsets padding }) async {
134      final EdgeInsets effectivePadding = padding ?? const EdgeInsets.symmetric(horizontal: 16);
135      final Rect segmentedControlRect = tester.getRect(find.byKey(key));
136      expect(
137          tester.getTopLeft(find.byWidget(children[0])),
138          segmentedControlRect.topLeft.translate(
139            effectivePadding.topLeft.dx,
140            effectivePadding.topLeft.dy,
141          )
142      );
143      expect(
144        tester.getBottomLeft(find.byWidget(children[0])),
145        segmentedControlRect.bottomLeft.translate(
146          effectivePadding.bottomLeft.dx,
147          effectivePadding.bottomLeft.dy,
148        ),
149      );
150
151      expect(
152        tester.getTopRight(find.byWidget(children[1])),
153        segmentedControlRect.topRight.translate(
154          effectivePadding.topRight.dx,
155          effectivePadding.topRight.dy,
156        ),
157      );
158      expect(
159        tester.getBottomRight(find.byWidget(children[1])),
160        segmentedControlRect.bottomRight.translate(
161          effectivePadding.bottomRight.dx,
162          effectivePadding.bottomRight.dy,
163        ),
164      );
165    }
166
167    await tester.pumpWidget(
168        boilerplate(
169          child: CupertinoSegmentedControl<int>(
170            key: key,
171            children: children,
172            onValueChanged: (int newValue) { },
173          ),
174        )
175    );
176
177    // Default padding works.
178    await verifyPadding();
179
180    // Switch to Child 2 padding should remain the same.
181    await tester.tap(find.text('Child 2'));
182    await tester.pumpAndSettle();
183
184    await verifyPadding();
185
186    await tester.pumpWidget(
187        boilerplate(
188          child: CupertinoSegmentedControl<int>(
189            key: key,
190            padding: const EdgeInsets.fromLTRB(1, 3, 5, 7),
191            children: children,
192            onValueChanged: (int newValue) { },
193          ),
194        )
195    );
196
197    // Custom padding works.
198    await verifyPadding(padding: const EdgeInsets.fromLTRB(1, 3, 5, 7));
199
200    // Switch back to Child 1 padding should remain the same.
201    await tester.tap(find.text('Child 1'));
202    await tester.pumpAndSettle();
203
204    await verifyPadding(padding: const EdgeInsets.fromLTRB(1, 3, 5, 7));
205  });
206
207  testWidgets('Value attribute must be the key of one of the children widgets', (WidgetTester tester) async {
208    final Map<int, Widget> children = <int, Widget>{};
209    children[0] = const Text('Child 1');
210    children[1] = const Text('Child 2');
211
212    try {
213      await tester.pumpWidget(
214        boilerplate(
215          child: CupertinoSegmentedControl<int>(
216            children: children,
217            onValueChanged: (int newValue) { },
218            groupValue: 2,
219          ),
220        ),
221      );
222      fail('Should not be possible to create segmented control in which '
223          'value is not the key of one of the children widgets');
224    } on AssertionError catch (e) {
225      expect(e.toString(), contains('children'));
226    }
227  });
228
229  testWidgets('Children and onValueChanged arguments can not be null', (WidgetTester tester) async {
230    try {
231      await tester.pumpWidget(
232        boilerplate(
233          child: CupertinoSegmentedControl<int>(
234            children: null,
235            onValueChanged: (int newValue) { },
236          ),
237        ),
238      );
239      fail('Should not be possible to create segmented control with null children');
240    } on AssertionError catch (e) {
241      expect(e.toString(), contains('children'));
242    }
243
244    final Map<int, Widget> children = <int, Widget>{};
245    children[0] = const Text('Child 1');
246    children[1] = const Text('Child 2');
247
248    try {
249      await tester.pumpWidget(
250        boilerplate(
251          child: CupertinoSegmentedControl<int>(
252            children: children,
253            onValueChanged: null,
254          ),
255        ),
256      );
257      fail('Should not be possible to create segmented control with null onValueChanged');
258    } on AssertionError catch (e) {
259      expect(e.toString(), contains('onValueChanged'));
260    }
261  });
262
263  testWidgets('Widgets have correct default text/icon styles, change correctly on selection', (WidgetTester tester) async {
264    final Map<int, Widget> children = <int, Widget>{};
265    children[0] = const Text('Child 1');
266    children[1] = const Icon(IconData(1));
267
268    int sharedValue = 0;
269
270    await tester.pumpWidget(
271      StatefulBuilder(
272        builder: (BuildContext context, StateSetter setState) {
273          return boilerplate(
274            child: CupertinoSegmentedControl<int>(
275              children: children,
276              onValueChanged: (int newValue) {
277                setState(() {
278                  sharedValue = newValue;
279                });
280              },
281              groupValue: sharedValue,
282            ),
283          );
284        },
285      ),
286    );
287
288    await tester.pumpAndSettle();
289
290    DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
291    IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
292
293    expect(textStyle.style.color, CupertinoColors.white);
294    expect(iconTheme.data.color, CupertinoColors.activeBlue);
295
296    await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
297    await tester.pumpAndSettle();
298
299    textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
300    iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
301
302    expect(textStyle.style.color, CupertinoColors.activeBlue);
303    expect(iconTheme.data.color, CupertinoColors.white);
304  });
305
306  testWidgets(
307    'Segmented controls respects themes',
308    (WidgetTester tester) async {
309      final Map<int, Widget> children = <int, Widget>{};
310      children[0] = const Text('Child 1');
311      children[1] = const Icon(IconData(1));
312
313      int sharedValue = 0;
314
315      await tester.pumpWidget(
316        CupertinoApp(
317          theme: const CupertinoThemeData(brightness: Brightness.dark),
318          home: StatefulBuilder(
319            builder: (BuildContext context, StateSetter setState) {
320              return CupertinoSegmentedControl<int>(
321                children: children,
322                onValueChanged: (int newValue) {
323                  setState(() {
324                    sharedValue = newValue;
325                  });
326                },
327                groupValue: sharedValue,
328              );
329            },
330          ),
331        ),
332      );
333
334      DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
335      IconThemeData iconTheme = IconTheme.of(tester.element(find.byIcon(const IconData(1))));
336
337      expect(textStyle.style.color, CupertinoColors.black);
338      expect(iconTheme.color, CupertinoColors.activeOrange);
339
340      await tester.tap(find.byIcon(const IconData(1)));
341      await tester.pump();
342      await tester.pump(const Duration(milliseconds: 500));
343
344      textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
345      iconTheme = IconTheme.of(tester.element(find.byIcon(const IconData(1))));
346
347      expect(textStyle.style.color, CupertinoColors.activeOrange);
348      expect(iconTheme.color, CupertinoColors.black);
349    },
350  );
351
352  testWidgets('SegmentedControl is correct when user provides custom colors', (WidgetTester tester) async {
353    final Map<int, Widget> children = <int, Widget>{};
354    children[0] = const Text('Child 1');
355    children[1] = const Icon(IconData(1));
356
357    int sharedValue = 0;
358
359    await tester.pumpWidget(
360      StatefulBuilder(
361        builder: (BuildContext context, StateSetter setState) {
362          return boilerplate(
363            child: CupertinoSegmentedControl<int>(
364              children: children,
365              onValueChanged: (int newValue) {
366                setState(() {
367                  sharedValue = newValue;
368                });
369              },
370              groupValue: sharedValue,
371              unselectedColor: CupertinoColors.lightBackgroundGray,
372              selectedColor: CupertinoColors.activeGreen,
373              borderColor: CupertinoColors.black,
374              pressedColor: const Color(0x638CFC7B),
375            ),
376          );
377        },
378      ),
379    );
380
381    DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
382    IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
383
384    expect(getRenderSegmentedControl(tester).borderColor, CupertinoColors.black);
385    expect(textStyle.style.color, CupertinoColors.lightBackgroundGray);
386    expect(iconTheme.data.color, CupertinoColors.activeGreen);
387    expect(getBackgroundColor(tester, 0), CupertinoColors.activeGreen);
388    expect(getBackgroundColor(tester, 1), CupertinoColors.lightBackgroundGray);
389
390    await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
391    await tester.pumpAndSettle();
392
393    textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
394    iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
395
396    expect(textStyle.style.color, CupertinoColors.activeGreen);
397    expect(iconTheme.data.color, CupertinoColors.lightBackgroundGray);
398    expect(getBackgroundColor(tester, 0), CupertinoColors.lightBackgroundGray);
399    expect(getBackgroundColor(tester, 1), CupertinoColors.activeGreen);
400
401    final Offset center = tester.getCenter(find.text('Child 1'));
402    await tester.startGesture(center);
403    await tester.pumpAndSettle();
404
405    expect(getBackgroundColor(tester, 0), const Color(0x638CFC7B));
406    expect(getBackgroundColor(tester, 1), CupertinoColors.activeGreen);
407  });
408
409  testWidgets('Widgets are centered within segments', (WidgetTester tester) async {
410    final Map<int, Widget> children = <int, Widget>{};
411    children[0] = const Text('Child 1');
412    children[1] = const Text('Child 2');
413
414    await tester.pumpWidget(
415      Directionality(
416        textDirection: TextDirection.ltr,
417        child: Align(
418          alignment: Alignment.topLeft,
419          child: SizedBox(
420            width: 200.0,
421            height: 200.0,
422            child: CupertinoSegmentedControl<int>(
423              children: children,
424              onValueChanged: (int newValue) { },
425            ),
426          ),
427        ),
428      ),
429    );
430
431    // Widgets are centered taking into account 16px of horizontal padding
432    expect(tester.getCenter(find.text('Child 1')), const Offset(58.0, 100.0));
433    expect(tester.getCenter(find.text('Child 2')), const Offset(142.0, 100.0));
434  });
435
436  testWidgets('Tap calls onValueChanged', (WidgetTester tester) async {
437    final Map<int, Widget> children = <int, Widget>{};
438    children[0] = const Text('Child 1');
439    children[1] = const Text('Child 2');
440
441    bool value = false;
442
443    await tester.pumpWidget(
444      StatefulBuilder(
445        builder: (BuildContext context, StateSetter setState) {
446          return boilerplate(
447            child: CupertinoSegmentedControl<int>(
448              children: children,
449              onValueChanged: (int newValue) {
450                value = true;
451              },
452            ),
453          );
454        },
455      ),
456    );
457
458    expect(value, isFalse);
459
460    await tester.tap(find.text('Child 2'));
461
462    expect(value, isTrue);
463  });
464
465  testWidgets('State does not change if onValueChanged does not call setState()', (WidgetTester tester) async {
466    final Map<int, Widget> children = <int, Widget>{};
467    children[0] = const Text('Child 1');
468    children[1] = const Text('Child 2');
469
470    const int sharedValue = 0;
471
472    await tester.pumpWidget(
473      StatefulBuilder(
474        builder: (BuildContext context, StateSetter setState) {
475          return boilerplate(
476            child: CupertinoSegmentedControl<int>(
477              children: children,
478              onValueChanged: (int newValue) { },
479              groupValue: sharedValue,
480            ),
481          );
482        },
483      ),
484    );
485
486    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
487    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
488
489    await tester.tap(find.text('Child 2'));
490    await tester.pump();
491
492    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
493    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
494  });
495
496  testWidgets(
497      'Background color of child should change on selection, '
498      'and should not change when tapped again', (WidgetTester tester) async {
499    await tester.pumpWidget(setupSimpleSegmentedControl());
500
501    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
502
503    await tester.tap(find.text('Child 2'));
504    await tester.pumpAndSettle(const Duration(milliseconds: 200));
505
506    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
507
508    await tester.tap(find.text('Child 2'));
509    await tester.pump();
510
511    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
512  });
513
514  testWidgets(
515    'Children can be non-Text or Icon widgets (in this case, '
516        'a Container or Placeholder widget)',
517    (WidgetTester tester) async {
518      final Map<int, Widget> children = <int, Widget>{};
519      children[0] = const Text('Child 1');
520      children[1] = Container(
521        constraints: const BoxConstraints.tightFor(width: 50.0, height: 50.0),
522      );
523      children[2] = const Placeholder();
524
525      int sharedValue = 0;
526
527      await tester.pumpWidget(
528        StatefulBuilder(
529          builder: (BuildContext context, StateSetter setState) {
530            return boilerplate(
531              child: CupertinoSegmentedControl<int>(
532                children: children,
533                onValueChanged: (int newValue) {
534                  setState(() {
535                    sharedValue = newValue;
536                  });
537                },
538                groupValue: sharedValue,
539              ),
540            );
541          },
542        ),
543      );
544    },
545  );
546
547  testWidgets('Passed in value is child initially selected', (WidgetTester tester) async {
548    await tester.pumpWidget(setupSimpleSegmentedControl());
549
550    expect(getRenderSegmentedControl(tester).selectedIndex, 0);
551
552    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
553    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
554  });
555
556  testWidgets('Null input for value results in no child initially selected', (WidgetTester tester) async {
557    final Map<int, Widget> children = <int, Widget>{};
558    children[0] = const Text('Child 1');
559    children[1] = const Text('Child 2');
560
561    int sharedValue;
562
563    await tester.pumpWidget(
564      StatefulBuilder(
565        builder: (BuildContext context, StateSetter setState) {
566          return boilerplate(
567            child: CupertinoSegmentedControl<int>(
568              children: children,
569              onValueChanged: (int newValue) {
570                setState(() {
571                  sharedValue = newValue;
572                });
573              },
574              groupValue: sharedValue,
575            ),
576          );
577        },
578      ),
579    );
580
581    expect(getRenderSegmentedControl(tester).selectedIndex, null);
582
583    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
584    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
585  });
586
587  testWidgets('Long press changes background color of not-selected child', (WidgetTester tester) async {
588    await tester.pumpWidget(setupSimpleSegmentedControl());
589
590    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
591    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
592
593    final Offset center = tester.getCenter(find.text('Child 2'));
594    await tester.startGesture(center);
595    await tester.pumpAndSettle();
596
597    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
598    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
599  });
600
601  testWidgets('Long press does not change background color of currently-selected child', (WidgetTester tester) async {
602    await tester.pumpWidget(setupSimpleSegmentedControl());
603
604    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
605    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
606
607    final Offset center = tester.getCenter(find.text('Child 1'));
608    await tester.startGesture(center);
609    await tester.pumpAndSettle();
610
611    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
612    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
613  });
614
615  testWidgets('Height of segmented control is determined by tallest widget', (WidgetTester tester) async {
616    final Map<int, Widget> children = <int, Widget>{};
617    children[0] = Container(
618      constraints: const BoxConstraints.tightFor(height: 100.0),
619    );
620    children[1] = Container(
621      constraints: const BoxConstraints.tightFor(height: 400.0),
622    );
623    children[2] = Container(
624      constraints: const BoxConstraints.tightFor(height: 200.0),
625    );
626
627    await tester.pumpWidget(
628      StatefulBuilder(
629        builder: (BuildContext context, StateSetter setState) {
630          return boilerplate(
631            child: CupertinoSegmentedControl<int>(
632              key: const ValueKey<String>('Segmented Control'),
633              children: children,
634              onValueChanged: (int newValue) { },
635            ),
636          );
637        },
638      ),
639    );
640
641    final RenderBox buttonBox = tester.renderObject(
642        find.byKey(const ValueKey<String>('Segmented Control')));
643
644    expect(buttonBox.size.height, 400.0);
645  });
646
647  testWidgets('Width of each segmented control segment is determined by widest widget', (WidgetTester tester) async {
648    final Map<int, Widget> children = <int, Widget>{};
649    children[0] = Container(
650      constraints: const BoxConstraints.tightFor(width: 50.0),
651    );
652    children[1] = Container(
653      constraints: const BoxConstraints.tightFor(width: 100.0),
654    );
655    children[2] = Container(
656      constraints: const BoxConstraints.tightFor(width: 200.0),
657    );
658
659    await tester.pumpWidget(
660      StatefulBuilder(
661        builder: (BuildContext context, StateSetter setState) {
662          return boilerplate(
663            child: CupertinoSegmentedControl<int>(
664              key: const ValueKey<String>('Segmented Control'),
665              children: children,
666              onValueChanged: (int newValue) { },
667            ),
668          );
669        },
670      ),
671    );
672
673    final RenderBox segmentedControl = tester.renderObject(
674        find.byKey(const ValueKey<String>('Segmented Control')));
675
676    // Subtract the 16.0px from each side. Remaining width should be allocated
677    // to each child equally.
678    final double childWidth = (segmentedControl.size.width - 32.0) / 3;
679
680    expect(childWidth, 200.0);
681
682    expect(childWidth,
683        getRenderSegmentedControl(tester).getChildrenAsList()[0].parentData.surroundingRect.width);
684    expect(childWidth,
685        getRenderSegmentedControl(tester).getChildrenAsList()[1].parentData.surroundingRect.width);
686    expect(childWidth,
687        getRenderSegmentedControl(tester).getChildrenAsList()[2].parentData.surroundingRect.width);
688  });
689
690  testWidgets('Width is finite in unbounded space', (WidgetTester tester) async {
691    final Map<int, Widget> children = <int, Widget>{};
692    children[0] = const Text('Child 1');
693    children[1] = const Text('Child 2');
694
695    await tester.pumpWidget(
696      StatefulBuilder(
697        builder: (BuildContext context, StateSetter setState) {
698          return boilerplate(
699            child: Row(
700              children: <Widget>[
701                CupertinoSegmentedControl<int>(
702                  key: const ValueKey<String>('Segmented Control'),
703                  children: children,
704                  onValueChanged: (int newValue) { },
705                ),
706              ],
707            ),
708          );
709        },
710      ),
711    );
712
713    final RenderBox segmentedControl = tester.renderObject(
714        find.byKey(const ValueKey<String>('Segmented Control')));
715
716    expect(segmentedControl.size.width.isFinite, isTrue);
717  });
718
719  testWidgets('Directionality test - RTL should reverse order of widgets', (WidgetTester tester) async {
720    final Map<int, Widget> children = <int, Widget>{};
721    children[0] = const Text('Child 1');
722    children[1] = const Text('Child 2');
723
724    await tester.pumpWidget(
725      Directionality(
726        textDirection: TextDirection.rtl,
727        child: Center(
728          child: CupertinoSegmentedControl<int>(
729            children: children,
730            onValueChanged: (int newValue) { },
731          ),
732        ),
733      ),
734    );
735
736    expect(tester.getTopRight(find.text('Child 1')).dx >
737        tester.getTopRight(find.text('Child 2')).dx, isTrue);
738  });
739
740  testWidgets('Correct initial selection and toggling behavior - RTL', (WidgetTester tester) async {
741    final Map<int, Widget> children = <int, Widget>{};
742    children[0] = const Text('Child 1');
743    children[1] = const Text('Child 2');
744
745    int sharedValue = 0;
746
747    await tester.pumpWidget(
748      StatefulBuilder(
749        builder: (BuildContext context, StateSetter setState) {
750          return Directionality(
751            textDirection: TextDirection.rtl,
752            child: Center(
753              child: CupertinoSegmentedControl<int>(
754                children: children,
755                onValueChanged: (int newValue) {
756                  setState(() {
757                    sharedValue = newValue;
758                  });
759                },
760                groupValue: sharedValue,
761              ),
762            ),
763          );
764        },
765      ),
766    );
767
768    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
769    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
770
771    await tester.tap(find.text('Child 2'));
772    await tester.pumpAndSettle();
773
774    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
775    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
776
777    await tester.tap(find.text('Child 2'));
778    await tester.pump();
779
780    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
781  });
782
783  testWidgets('Segmented control semantics', (WidgetTester tester) async {
784    final SemanticsTester semantics = SemanticsTester(tester);
785
786    final Map<int, Widget> children = <int, Widget>{};
787    children[0] = const Text('Child 1');
788    children[1] = const Text('Child 2');
789    int sharedValue = 0;
790
791    await tester.pumpWidget(
792      StatefulBuilder(
793        builder: (BuildContext context, StateSetter setState) {
794          return Directionality(
795            textDirection: TextDirection.ltr,
796            child: Center(
797              child: CupertinoSegmentedControl<int>(
798                children: children,
799                onValueChanged: (int newValue) {
800                  setState(() {
801                    sharedValue = newValue;
802                  });
803                },
804                groupValue: sharedValue,
805              ),
806            ),
807          );
808        },
809      ),
810    );
811
812    expect(
813      semantics,
814        hasSemantics(
815          TestSemantics.root(
816            children: <TestSemantics>[
817              TestSemantics.rootChild(
818                label: 'Child 1',
819                flags: <SemanticsFlag>[
820                  SemanticsFlag.isButton,
821                  SemanticsFlag.isInMutuallyExclusiveGroup,
822                  SemanticsFlag.isSelected,
823                ],
824                actions: <SemanticsAction>[
825                  SemanticsAction.tap,
826                ],
827              ),
828              TestSemantics.rootChild(
829                label: 'Child 2',
830                flags: <SemanticsFlag>[
831                  SemanticsFlag.isButton,
832                  SemanticsFlag.isInMutuallyExclusiveGroup,
833                ],
834                actions: <SemanticsAction>[
835                  SemanticsAction.tap,
836                ],
837              ),
838            ],
839          ),
840          ignoreId: true,
841          ignoreRect: true,
842          ignoreTransform: true,
843        ),
844    );
845
846    await tester.tap(find.text('Child 2'));
847    await tester.pump();
848
849    expect(
850        semantics,
851        hasSemantics(
852          TestSemantics.root(
853            children: <TestSemantics>[
854              TestSemantics.rootChild(
855                label: 'Child 1',
856                flags: <SemanticsFlag>[
857                  SemanticsFlag.isButton,
858                  SemanticsFlag.isInMutuallyExclusiveGroup,
859                ],
860                actions: <SemanticsAction>[
861                  SemanticsAction.tap,
862                ],
863              ),
864              TestSemantics.rootChild(
865                label: 'Child 2',
866                flags: <SemanticsFlag>[
867                  SemanticsFlag.isButton,
868                  SemanticsFlag.isInMutuallyExclusiveGroup,
869                  SemanticsFlag.isSelected,
870                ],
871                actions: <SemanticsAction>[
872                  SemanticsAction.tap,
873                ],
874              ),
875            ],
876          ),
877          ignoreId: true,
878          ignoreRect: true,
879          ignoreTransform: true,
880        ));
881
882    semantics.dispose();
883  });
884
885  testWidgets('Non-centered taps work on smaller widgets', (WidgetTester tester) async {
886    final Map<int, Widget> children = <int, Widget>{};
887    children[0] = const Text('Child 1');
888    children[1] = const Text('Child 2');
889
890    int sharedValue = 1;
891
892    await tester.pumpWidget(
893      StatefulBuilder(
894        builder: (BuildContext context, StateSetter setState) {
895          return boilerplate(
896            child: CupertinoSegmentedControl<int>(
897              key: const ValueKey<String>('Segmented Control'),
898              children: children,
899              onValueChanged: (int newValue) {
900                setState(() {
901                  sharedValue = newValue;
902                });
903              },
904              groupValue: sharedValue,
905            ),
906          );
907        },
908      ),
909    );
910
911    expect(sharedValue, 1);
912
913    final double childWidth = getRenderSegmentedControl(tester).firstChild.size.width;
914    final Offset centerOfSegmentedControl = tester.getCenter(find.text('Child 1'));
915
916    // Tap just inside segment bounds
917    await tester.tapAt(
918      Offset(
919        centerOfSegmentedControl.dx + (childWidth / 2) - 10.0,
920        centerOfSegmentedControl.dy,
921      ),
922    );
923
924    expect(sharedValue, 0);
925  });
926
927  testWidgets('Animation is correct when the selected segment changes', (WidgetTester tester) async {
928    await tester.pumpWidget(setupSimpleSegmentedControl());
929
930    await tester.tap(find.text('Child 2'));
931
932    await tester.pump();
933    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
934    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
935
936    await tester.pump(const Duration(milliseconds: 40));
937    expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
938    expect(getBackgroundColor(tester, 1), const Color(0x64007aff));
939
940    await tester.pump(const Duration(milliseconds: 40));
941    expect(getBackgroundColor(tester, 0), const Color(0xff7bbaff));
942    expect(getBackgroundColor(tester, 1), const Color(0x95007aff));
943
944    await tester.pump(const Duration(milliseconds: 40));
945    expect(getBackgroundColor(tester, 0), const Color(0xffb9daff));
946    expect(getBackgroundColor(tester, 1), const Color(0xc7007aff));
947
948    await tester.pump(const Duration(milliseconds: 40));
949    expect(getBackgroundColor(tester, 0), const Color(0xfff7faff));
950    expect(getBackgroundColor(tester, 1), const Color(0xf8007aff));
951
952    await tester.pump(const Duration(milliseconds: 40));
953    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
954    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
955  });
956
957  testWidgets('Animation is correct when widget is rebuilt', (WidgetTester tester) async {
958    final Map<int, Widget> children = <int, Widget>{};
959    children[0] = const Text('Child 1');
960    children[1] = const Text('Child 2');
961    int sharedValue = 0;
962
963    await tester.pumpWidget(
964      StatefulBuilder(
965        builder: (BuildContext context, StateSetter setState) {
966          return boilerplate(
967            child: CupertinoSegmentedControl<int>(
968              children: children,
969              onValueChanged: (int newValue) {
970                setState(() {
971                  sharedValue = newValue;
972                });
973              },
974              groupValue: sharedValue,
975            ),
976          );
977        },
978      ),
979    );
980
981    await tester.tap(find.text('Child 2'));
982
983    await tester.pumpWidget(
984      StatefulBuilder(
985        builder: (BuildContext context, StateSetter setState) {
986          return boilerplate(
987            child: CupertinoSegmentedControl<int>(
988              children: children,
989              onValueChanged: (int newValue) {
990                setState(() {
991                  sharedValue = newValue;
992                });
993              },
994              groupValue: sharedValue,
995            ),
996          );
997        },
998      ),
999    );
1000    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
1001    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
1002
1003    await tester.pumpWidget(
1004      StatefulBuilder(
1005        builder: (BuildContext context, StateSetter setState) {
1006          return boilerplate(
1007            child: CupertinoSegmentedControl<int>(
1008              children: children,
1009              onValueChanged: (int newValue) {
1010                setState(() {
1011                  sharedValue = newValue;
1012                });
1013              },
1014              groupValue: sharedValue,
1015            ),
1016          );
1017        },
1018      ),
1019      const Duration(milliseconds: 40),
1020    );
1021    expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
1022    expect(getBackgroundColor(tester, 1), const Color(0x64007aff));
1023
1024    await tester.pumpWidget(
1025      StatefulBuilder(
1026        builder: (BuildContext context, StateSetter setState) {
1027          return boilerplate(
1028            child: CupertinoSegmentedControl<int>(
1029              children: children,
1030              onValueChanged: (int newValue) {
1031                setState(() {
1032                  sharedValue = newValue;
1033                });
1034              },
1035              groupValue: sharedValue,
1036            ),
1037          );
1038        },
1039      ),
1040      const Duration(milliseconds: 40),
1041    );
1042    expect(getBackgroundColor(tester, 0), const Color(0xff7bbaff));
1043    expect(getBackgroundColor(tester, 1), const Color(0x95007aff));
1044
1045    await tester.pumpWidget(
1046      StatefulBuilder(
1047        builder: (BuildContext context, StateSetter setState) {
1048          return boilerplate(
1049            child: CupertinoSegmentedControl<int>(
1050              children: children,
1051              onValueChanged: (int newValue) {
1052                setState(() {
1053                  sharedValue = newValue;
1054                });
1055              },
1056              groupValue: sharedValue,
1057            ),
1058          );
1059        },
1060      ),
1061      const Duration(milliseconds: 40),
1062    );
1063    expect(getBackgroundColor(tester, 0), const Color(0xffb9daff));
1064    expect(getBackgroundColor(tester, 1), const Color(0xc7007aff));
1065
1066    await tester.pumpWidget(
1067      StatefulBuilder(
1068        builder: (BuildContext context, StateSetter setState) {
1069          return boilerplate(
1070            child: CupertinoSegmentedControl<int>(
1071              children: children,
1072              onValueChanged: (int newValue) {
1073                setState(() {
1074                  sharedValue = newValue;
1075                });
1076              },
1077              groupValue: sharedValue,
1078            ),
1079          );
1080        },
1081      ),
1082      const Duration(milliseconds: 40),
1083    );
1084    expect(getBackgroundColor(tester, 0), const Color(0xfff7faff));
1085    expect(getBackgroundColor(tester, 1), const Color(0xf8007aff));
1086
1087    await tester.pumpWidget(
1088      StatefulBuilder(
1089        builder: (BuildContext context, StateSetter setState) {
1090          return boilerplate(
1091            child: CupertinoSegmentedControl<int>(
1092              children: children,
1093              onValueChanged: (int newValue) {
1094                setState(() {
1095                  sharedValue = newValue;
1096                });
1097              },
1098              groupValue: sharedValue,
1099            ),
1100          );
1101        },
1102      ),
1103      const Duration(milliseconds: 40),
1104    );
1105    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
1106    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
1107  });
1108
1109  testWidgets('Multiple segments are pressed', (WidgetTester tester) async {
1110    final Map<int, Widget> children = <int, Widget>{};
1111    children[0] = const Text('A');
1112    children[1] = const Text('B');
1113    children[2] = const Text('C');
1114    int sharedValue = 0;
1115
1116    await tester.pumpWidget(
1117      StatefulBuilder(
1118        builder: (BuildContext context, StateSetter setState) {
1119          return boilerplate(
1120            child: CupertinoSegmentedControl<int>(
1121              key: const ValueKey<String>('Segmented Control'),
1122              children: children,
1123              onValueChanged: (int newValue) {
1124                setState(() {
1125                  sharedValue = newValue;
1126                });
1127              },
1128              groupValue: sharedValue,
1129            ),
1130          );
1131        },
1132      ),
1133    );
1134
1135    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
1136
1137    await tester.startGesture(tester.getCenter(find.text('B')));
1138    await tester.pumpAndSettle(const Duration(milliseconds: 200));
1139
1140    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
1141    expect(getBackgroundColor(tester, 2), CupertinoColors.white);
1142
1143    await tester.startGesture(tester.getCenter(find.text('C')));
1144    await tester.pumpAndSettle(const Duration(milliseconds: 200));
1145
1146    // Press on C has no effect while B is held down.
1147    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
1148    expect(getBackgroundColor(tester, 2), CupertinoColors.white);
1149  });
1150
1151  testWidgets('Transition is triggered while a transition is already occurring', (WidgetTester tester) async {
1152    final Map<int, Widget> children = <int, Widget>{};
1153    children[0] = const Text('A');
1154    children[1] = const Text('B');
1155    children[2] = const Text('C');
1156    int sharedValue = 0;
1157
1158    await tester.pumpWidget(
1159      StatefulBuilder(
1160        builder: (BuildContext context, StateSetter setState) {
1161          return boilerplate(
1162            child: CupertinoSegmentedControl<int>(
1163              key: const ValueKey<String>('Segmented Control'),
1164              children: children,
1165              onValueChanged: (int newValue) {
1166                setState(() {
1167                  sharedValue = newValue;
1168                });
1169              },
1170              groupValue: sharedValue,
1171            ),
1172          );
1173        },
1174      ),
1175    );
1176
1177    await tester.tap(find.text('B'));
1178
1179    await tester.pump();
1180    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
1181    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
1182
1183    await tester.pump(const Duration(milliseconds: 40));
1184    expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
1185    expect(getBackgroundColor(tester, 1), const Color(0x64007aff));
1186
1187    // While A to B transition is occurring, press on C.
1188    await tester.tap(find.text('C'));
1189
1190    await tester.pump();
1191
1192    // A and B are now both transitioning to white.
1193    expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
1194    expect(getBackgroundColor(tester, 1), const Color(0xffc1deff));
1195    expect(getBackgroundColor(tester, 2), const Color(0x33007aff));
1196
1197    await tester.pump(const Duration(milliseconds: 40));
1198    // B background color has reached unselected state.
1199    expect(getBackgroundColor(tester, 0), const Color(0xff7bbaff));
1200    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
1201    expect(getBackgroundColor(tester, 2), const Color(0x64007aff));
1202
1203    await tester.pump(const Duration(milliseconds: 100));
1204    // A background color has reached unselected state.
1205    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
1206    expect(getBackgroundColor(tester, 2), const Color(0xe0007aff));
1207
1208    await tester.pump(const Duration(milliseconds: 40));
1209    // C background color has reached selected state.
1210    expect(getBackgroundColor(tester, 2), CupertinoColors.activeBlue);
1211  });
1212
1213  testWidgets('Segment is selected while it is transitioning to unselected state', (WidgetTester tester) async {
1214    await tester.pumpWidget(setupSimpleSegmentedControl());
1215
1216    await tester.tap(find.text('Child 2'));
1217
1218    await tester.pump();
1219    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
1220    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
1221
1222    await tester.pump(const Duration(milliseconds: 40));
1223    expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
1224    expect(getBackgroundColor(tester, 1), const Color(0x64007aff));
1225
1226    // While A to B transition is occurring, press on A again.
1227    await tester.tap(find.text('Child 1'));
1228
1229    await tester.pump();
1230
1231    // Both transitions start to reverse.
1232    expect(getBackgroundColor(tester, 0), const Color(0xcd007aff));
1233    expect(getBackgroundColor(tester, 1), const Color(0xffc1deff));
1234
1235    await tester.pump(const Duration(milliseconds: 40));
1236    // A and B finish transitioning.
1237    expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue);
1238    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
1239  });
1240
1241  testWidgets('Add segment while animation is running', (WidgetTester tester) async {
1242    Map<int, Widget> children = <int, Widget>{};
1243    children[0] = const Text('A');
1244    children[1] = const Text('B');
1245    children[2] = const Text('C');
1246    int sharedValue = 0;
1247
1248    await tester.pumpWidget(
1249      StatefulBuilder(
1250        builder: (BuildContext context, StateSetter setState) {
1251          return boilerplate(
1252            child: CupertinoSegmentedControl<int>(
1253              key: const ValueKey<String>('Segmented Control'),
1254              children: children,
1255              onValueChanged: (int newValue) {
1256                setState(() {
1257                  sharedValue = newValue;
1258                });
1259                if (sharedValue == 1) {
1260                  children = Map<int, Widget>.from(children);
1261                  children[3] = const Text('D');
1262                }
1263              },
1264              groupValue: sharedValue,
1265            ),
1266          );
1267        },
1268      ),
1269    );
1270
1271    await tester.tap(find.text('B'));
1272
1273    await tester.pump();
1274    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
1275    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
1276    expect(getBackgroundColor(tester, 3), CupertinoColors.white);
1277
1278    await tester.pump(const Duration(milliseconds: 40));
1279    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
1280    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
1281    expect(getBackgroundColor(tester, 3), CupertinoColors.white);
1282
1283    await tester.pump(const Duration(milliseconds: 150));
1284    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
1285    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
1286    expect(getBackgroundColor(tester, 3), CupertinoColors.white);
1287  });
1288
1289  testWidgets('Remove segment while animation is running', (WidgetTester tester) async {
1290    Map<int, Widget> children = <int, Widget>{};
1291    children[0] = const Text('A');
1292    children[1] = const Text('B');
1293    children[2] = const Text('C');
1294    int sharedValue = 0;
1295
1296    await tester.pumpWidget(
1297      StatefulBuilder(
1298        builder: (BuildContext context, StateSetter setState) {
1299          return boilerplate(
1300            child: CupertinoSegmentedControl<int>(
1301              key: const ValueKey<String>('Segmented Control'),
1302              children: children,
1303              onValueChanged: (int newValue) {
1304                setState(() {
1305                  sharedValue = newValue;
1306                });
1307                if (sharedValue == 1) {
1308                  children.remove(2);
1309                  children = Map<int, Widget>.from(children);
1310                }
1311              },
1312              groupValue: sharedValue,
1313            ),
1314          );
1315        },
1316      ),
1317    );
1318
1319    expect(getRenderSegmentedControl(tester).getChildrenAsList().length, 3);
1320
1321    await tester.tap(find.text('B'));
1322
1323    await tester.pump();
1324    expect(getBackgroundColor(tester, 1), const Color(0x33007aff));
1325    expect(getRenderSegmentedControl(tester).getChildrenAsList().length, 2);
1326
1327    await tester.pump(const Duration(milliseconds: 40));
1328    expect(getBackgroundColor(tester, 1), const Color(0x64007aff));
1329
1330    await tester.pump(const Duration(milliseconds: 150));
1331    expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue);
1332  });
1333
1334  testWidgets('Remove currently animating segment', (WidgetTester tester) async {
1335    Map<int, Widget> children = <int, Widget>{};
1336    children[0] = const Text('A');
1337    children[1] = const Text('B');
1338    children[2] = const Text('C');
1339    int sharedValue = 0;
1340
1341    await tester.pumpWidget(
1342      StatefulBuilder(
1343        builder: (BuildContext context, StateSetter setState) {
1344          return boilerplate(
1345            child: CupertinoSegmentedControl<int>(
1346              key: const ValueKey<String>('Segmented Control'),
1347              children: children,
1348              onValueChanged: (int newValue) {
1349                setState(() {
1350                  sharedValue = newValue;
1351                });
1352                if (sharedValue == 1) {
1353                  children.remove(1);
1354                  children = Map<int, Widget>.from(children);
1355                  sharedValue = null;
1356                }
1357              },
1358              groupValue: sharedValue,
1359            ),
1360          );
1361        },
1362      ),
1363    );
1364
1365    expect(getRenderSegmentedControl(tester).getChildrenAsList().length, 3);
1366
1367    await tester.tap(find.text('B'));
1368
1369    await tester.pump();
1370    expect(getRenderSegmentedControl(tester).getChildrenAsList().length, 2);
1371
1372    await tester.pump(const Duration(milliseconds: 40));
1373    expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff));
1374    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
1375
1376    await tester.pump(const Duration(milliseconds: 40));
1377    expect(getBackgroundColor(tester, 0), const Color(0xff7bbaff));
1378    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
1379
1380    await tester.pump(const Duration(milliseconds: 100));
1381    expect(getBackgroundColor(tester, 0), CupertinoColors.white);
1382    expect(getBackgroundColor(tester, 1), CupertinoColors.white);
1383  });
1384
1385  testWidgets('Golden Test Placeholder Widget', (WidgetTester tester) async {
1386    final Map<int, Widget> children = <int, Widget>{};
1387    children[0] = Container();
1388    children[1] = const Placeholder();
1389    children[2] = Container();
1390
1391    const int currentValue = 0;
1392
1393    await tester.pumpWidget(
1394      RepaintBoundary(
1395        child: StatefulBuilder(
1396          builder: (BuildContext context, StateSetter setState) {
1397            return boilerplate(
1398              child: SizedBox(
1399                width: 800.0,
1400                child: CupertinoSegmentedControl<int>(
1401                  key: const ValueKey<String>('Segmented Control'),
1402                  children: children,
1403                  onValueChanged: (int newValue) { },
1404                  groupValue: currentValue,
1405                ),
1406              ),
1407            );
1408          },
1409        ),
1410      ),
1411    );
1412
1413    await expectLater(
1414      find.byType(RepaintBoundary),
1415      matchesGoldenFile(
1416        'segmented_control_test.0.png',
1417        version: 0,
1418      ),
1419    );
1420  });
1421
1422  testWidgets('Golden Test Pressed State', (WidgetTester tester) async {
1423    final Map<int, Widget> children = <int, Widget>{};
1424    children[0] = const Text('A');
1425    children[1] = const Text('B');
1426    children[2] = const Text('C');
1427
1428    const int currentValue = 0;
1429
1430    await tester.pumpWidget(
1431      RepaintBoundary(
1432        child: StatefulBuilder(
1433          builder: (BuildContext context, StateSetter setState) {
1434            return boilerplate(
1435              child: SizedBox(
1436                width: 800.0,
1437                child: CupertinoSegmentedControl<int>(
1438                  key: const ValueKey<String>('Segmented Control'),
1439                  children: children,
1440                  onValueChanged: (int newValue) { },
1441                  groupValue: currentValue,
1442                ),
1443              ),
1444            );
1445          },
1446        ),
1447      ),
1448    );
1449
1450    final Offset center = tester.getCenter(find.text('B'));
1451    await tester.startGesture(center);
1452    await tester.pumpAndSettle();
1453
1454    await expectLater(
1455      find.byType(RepaintBoundary),
1456      matchesGoldenFile(
1457        'segmented_control_test.1.png',
1458        version: 0,
1459      ),
1460    );
1461  });
1462}
1463