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/foundation.dart';
7import 'package:flutter/gestures.dart';
8import 'package:flutter/rendering.dart';
9import 'package:flutter_test/flutter_test.dart';
10
11import '../image_data.dart';
12import '../widgets/semantics_tester.dart';
13
14Future<void> pumpWidgetWithBoilerplate(WidgetTester tester, Widget widget) async {
15 await tester.pumpWidget(
16 Localizations(
17 locale: const Locale('en', 'US'),
18 delegates: const <LocalizationsDelegate<dynamic>>[
19 DefaultWidgetsLocalizations.delegate,
20 DefaultCupertinoLocalizations.delegate,
21 ],
22 child: Directionality(
23 textDirection: TextDirection.ltr,
24 child: widget,
25 ),
26 ),
27 );
28}
29
30Future<void> main() async {
31
32 testWidgets('Need at least 2 tabs', (WidgetTester tester) async {
33 await expectLater(
34 () => pumpWidgetWithBoilerplate(tester, CupertinoTabBar(
35 items: <BottomNavigationBarItem>[
36 BottomNavigationBarItem(
37 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
38 label: 'Tab 1',
39 ),
40 ],
41 )),
42 throwsA(isAssertionError.having(
43 (AssertionError error) => error.toString(),
44 '.toString()',
45 contains('items.length'),
46 )),
47 );
48 });
49
50 testWidgets('Active and inactive colors', (WidgetTester tester) async {
51 await pumpWidgetWithBoilerplate(tester, MediaQuery(
52 data: const MediaQueryData(),
53 child: CupertinoTabBar(
54 items: <BottomNavigationBarItem>[
55 BottomNavigationBarItem(
56 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
57 label: 'Tab 1',
58 ),
59 BottomNavigationBarItem(
60 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
61 label: 'Tab 2',
62 ),
63 ],
64 currentIndex: 1,
65 activeColor: const Color(0xFF123456),
66 inactiveColor: const Color(0xFF654321),
67 ),
68 ));
69
70 final RichText actualInactive = tester.widget(find.descendant(
71 of: find.text('Tab 1'),
72 matching: find.byType(RichText),
73 ));
74 expect(actualInactive.text.style!.color, const Color(0xFF654321));
75
76 final RichText actualActive = tester.widget(find.descendant(
77 of: find.text('Tab 2'),
78 matching: find.byType(RichText),
79 ));
80 expect(actualActive.text.style!.color, const Color(0xFF123456));
81 });
82
83
84 testWidgets('BottomNavigationBar.label will create a text widget', (WidgetTester tester) async {
85 await pumpWidgetWithBoilerplate(tester, MediaQuery(
86 data: const MediaQueryData(),
87 child: CupertinoTabBar(
88 items: <BottomNavigationBarItem>[
89 BottomNavigationBarItem(
90 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
91 label: 'Tab 1',
92 ),
93 BottomNavigationBarItem(
94 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
95 label: 'Tab 2',
96 ),
97 ],
98 currentIndex: 1,
99 ),
100 ));
101
102 expect(find.text('Tab 1'), findsOneWidget);
103 expect(find.text('Tab 2'), findsOneWidget);
104 });
105
106 testWidgets('Active and inactive colors dark mode', (WidgetTester tester) async {
107 const CupertinoDynamicColor dynamicActiveColor = CupertinoDynamicColor.withBrightness(
108 color: Color(0xFF000000),
109 darkColor: Color(0xFF000001),
110 );
111
112 const CupertinoDynamicColor dynamicInactiveColor = CupertinoDynamicColor.withBrightness(
113 color: Color(0xFF000002),
114 darkColor: Color(0xFF000003),
115 );
116
117 await pumpWidgetWithBoilerplate(tester, MediaQuery(
118 data: const MediaQueryData(),
119 child: CupertinoTabBar(
120 items: <BottomNavigationBarItem>[
121 BottomNavigationBarItem(
122 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
123 label: 'Tab 1',
124 ),
125 BottomNavigationBarItem(
126 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
127 label: 'Tab 2',
128 ),
129 ],
130 currentIndex: 1,
131 activeColor: dynamicActiveColor,
132 inactiveColor: dynamicInactiveColor,
133 ),
134 ));
135
136 RichText actualInactive = tester.widget(find.descendant(
137 of: find.text('Tab 1'),
138 matching: find.byType(RichText),
139 ));
140 expect(actualInactive.text.style!.color!.value, 0xFF000002);
141
142 RichText actualActive = tester.widget(find.descendant(
143 of: find.text('Tab 2'),
144 matching: find.byType(RichText),
145 ));
146 expect(actualActive.text.style!.color!.value, 0xFF000000);
147
148 final RenderDecoratedBox renderDecoratedBox = tester.renderObject(find.descendant(
149 of: find.byType(BackdropFilter),
150 matching: find.byType(DecoratedBox),
151 ));
152
153 // Border color is resolved correctly.
154 final BoxDecoration decoration1 = renderDecoratedBox.decoration as BoxDecoration;
155 expect(decoration1.border!.top.color.value, 0x4D000000);
156
157 // Switch to dark mode.
158 await pumpWidgetWithBoilerplate(tester, MediaQuery(
159 data: const MediaQueryData(platformBrightness: Brightness.dark),
160 child: CupertinoTabBar(
161 items: <BottomNavigationBarItem>[
162 BottomNavigationBarItem(
163 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
164 label: 'Tab 1',
165 ),
166 BottomNavigationBarItem(
167 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
168 label: 'Tab 2',
169 ),
170 ],
171 currentIndex: 1,
172 activeColor: dynamicActiveColor,
173 inactiveColor: dynamicInactiveColor,
174 ),
175 ));
176
177 actualInactive = tester.widget(find.descendant(
178 of: find.text('Tab 1'),
179 matching: find.byType(RichText),
180 ));
181 expect(actualInactive.text.style!.color!.value, 0xFF000003);
182
183 actualActive = tester.widget(find.descendant(
184 of: find.text('Tab 2'),
185 matching: find.byType(RichText),
186 ));
187 expect(actualActive.text.style!.color!.value, 0xFF000001);
188
189 // Border color is resolved correctly.
190 final BoxDecoration decoration2 = renderDecoratedBox.decoration as BoxDecoration;
191 expect(decoration2.border!.top.color.value, 0x29000000);
192 });
193
194 testWidgets('Tabs respects themes', (WidgetTester tester) async {
195 await tester.pumpWidget(
196 CupertinoApp(
197 home: CupertinoTabBar(
198 items: <BottomNavigationBarItem>[
199 BottomNavigationBarItem(
200 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
201 label: 'Tab 1',
202 ),
203 BottomNavigationBarItem(
204 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
205 label: 'Tab 2',
206 ),
207 ],
208 currentIndex: 1,
209 ),
210 ),
211 );
212
213 RichText actualInactive = tester.widget(find.descendant(
214 of: find.text('Tab 1'),
215 matching: find.byType(RichText),
216 ));
217 expect(actualInactive.text.style!.color!.value, 0xFF999999);
218
219 RichText actualActive = tester.widget(find.descendant(
220 of: find.text('Tab 2'),
221 matching: find.byType(RichText),
222 ));
223 expect(actualActive.text.style!.color, CupertinoColors.activeBlue);
224
225 await tester.pumpWidget(
226 CupertinoApp(
227 theme: const CupertinoThemeData(brightness: Brightness.dark),
228 home: CupertinoTabBar(
229 items: <BottomNavigationBarItem>[
230 BottomNavigationBarItem(
231 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
232 label: 'Tab 1',
233 ),
234 BottomNavigationBarItem(
235 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
236 label: 'Tab 2',
237 ),
238 ],
239 currentIndex: 1,
240 ),
241 ),
242 );
243
244 actualInactive = tester.widget(find.descendant(
245 of: find.text('Tab 1'),
246 matching: find.byType(RichText),
247 ));
248 expect(actualInactive.text.style!.color!.value, 0xFF757575);
249
250 actualActive = tester.widget(find.descendant(
251 of: find.text('Tab 2'),
252 matching: find.byType(RichText),
253 ));
254
255 expect(actualActive.text.style!.color, isSameColorAs(CupertinoColors.activeBlue.darkColor));
256 });
257
258 testWidgets('Use active icon', (WidgetTester tester) async {
259 final MemoryImage activeIcon = MemoryImage(Uint8List.fromList(kBlueSquarePng));
260 final MemoryImage inactiveIcon = MemoryImage(Uint8List.fromList(kTransparentImage));
261
262 await pumpWidgetWithBoilerplate(tester, MediaQuery(
263 data: const MediaQueryData(),
264 child: CupertinoTabBar(
265 items: <BottomNavigationBarItem>[
266 BottomNavigationBarItem(
267 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
268 label: 'Tab 1',
269 ),
270 BottomNavigationBarItem(
271 icon: ImageIcon(inactiveIcon),
272 activeIcon: ImageIcon(activeIcon),
273 label: 'Tab 2',
274 ),
275 ],
276 currentIndex: 1,
277 activeColor: const Color(0xFF123456),
278 inactiveColor: const Color(0xFF654321),
279 ),
280 ));
281
282 final Image image = tester.widget(find.descendant(
283 of: find.widgetWithText(GestureDetector, 'Tab 2'),
284 matching: find.byType(Image),
285 ));
286
287 expect(image.color, const Color(0xFF123456));
288 expect(image.image, activeIcon);
289 });
290
291 testWidgets('Adjusts height to account for bottom padding', (WidgetTester tester) async {
292 final CupertinoTabBar tabBar = CupertinoTabBar(
293 items: <BottomNavigationBarItem>[
294 BottomNavigationBarItem(
295 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
296 label: 'Aka',
297 ),
298 BottomNavigationBarItem(
299 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
300 label: 'Shiro',
301 ),
302 ],
303 );
304
305 // Verify height with no bottom padding.
306 await pumpWidgetWithBoilerplate(tester, MediaQuery(
307 data: const MediaQueryData(),
308 child: CupertinoTabScaffold(
309 tabBar: tabBar,
310 tabBuilder: (BuildContext context, int index) {
311 return const Placeholder();
312 },
313 ),
314 ));
315 expect(tester.getSize(find.byType(CupertinoTabBar)).height, 50.0);
316
317 // Verify height with bottom padding.
318 await pumpWidgetWithBoilerplate(tester, MediaQuery(
319 data: const MediaQueryData(viewPadding: EdgeInsets.only(bottom: 40.0)),
320 child: CupertinoTabScaffold(
321 tabBar: tabBar,
322 tabBuilder: (BuildContext context, int index) {
323 return const Placeholder();
324 },
325 ),
326 ));
327 expect(tester.getSize(find.byType(CupertinoTabBar)).height, 90.0);
328 });
329
330 testWidgets('Set custom height', (WidgetTester tester) async {
331 // Regression test for https://github.com/flutter/flutter/issues/51704
332 const double tabBarHeight = 56.0;
333 final CupertinoTabBar tabBar = CupertinoTabBar(
334 height: tabBarHeight,
335 items: <BottomNavigationBarItem>[
336 BottomNavigationBarItem(
337 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
338 label: 'Aka',
339 ),
340 BottomNavigationBarItem(
341 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
342 label: 'Shiro',
343 ),
344 ],
345 );
346
347 // Verify height with no bottom padding.
348 await pumpWidgetWithBoilerplate(tester, MediaQuery(
349 data: const MediaQueryData(),
350 child: CupertinoTabScaffold(
351 tabBar: tabBar,
352 tabBuilder: (BuildContext context, int index) {
353 return const Placeholder();
354 },
355 ),
356 ));
357 expect(tester.getSize(find.byType(CupertinoTabBar)).height, tabBarHeight);
358
359 // Verify height with bottom padding.
360 const double bottomPadding = 40.0;
361 await pumpWidgetWithBoilerplate(tester, MediaQuery(
362 data: const MediaQueryData(viewPadding: EdgeInsets.only(bottom: bottomPadding)),
363 child: CupertinoTabScaffold(
364 tabBar: tabBar,
365 tabBuilder: (BuildContext context, int index) {
366 return const Placeholder();
367 },
368 ),
369 ));
370 expect(tester.getSize(find.byType(CupertinoTabBar)).height, tabBarHeight + bottomPadding);
371 });
372
373 testWidgets('Ensure bar height will not change when toggle keyboard', (WidgetTester tester) async {
374 const double tabBarHeight = 56.0;
375 final CupertinoTabBar tabBar = CupertinoTabBar(
376 height: tabBarHeight,
377 items: <BottomNavigationBarItem>[
378 BottomNavigationBarItem(
379 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
380 label: 'Aka',
381 ),
382 BottomNavigationBarItem(
383 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
384 label: 'Shiro',
385 ),
386 ],
387 );
388
389 const double bottomPadding = 34.0;
390
391 // Test the height is correct when keyboard not showing.
392 // So viewInset should be 0.0.
393 await pumpWidgetWithBoilerplate(tester, MediaQuery(
394 data: const MediaQueryData(
395 padding: EdgeInsets.only(bottom: bottomPadding),
396 viewPadding: EdgeInsets.only(bottom: bottomPadding),
397 ),
398 child: CupertinoTabScaffold(
399 tabBar: tabBar,
400 tabBuilder: (BuildContext context, int index) {
401 return const Placeholder();
402 },
403 ),
404 ));
405 expect(tester.getSize(find.byType(CupertinoTabBar)).height, tabBarHeight + bottomPadding);
406
407 // Now show keyboard, and test the bar height will not change.
408 await pumpWidgetWithBoilerplate(tester,
409 MediaQuery(
410 data: const MediaQueryData(
411 viewPadding: EdgeInsets.only(bottom: bottomPadding),
412 viewInsets: EdgeInsets.only(bottom: 336.0),
413 ),
414 child: CupertinoTabScaffold(
415 tabBar: tabBar,
416 tabBuilder: (BuildContext context, int index) {
417 return const Placeholder();
418 },
419 ),
420 ),
421 );
422
423 // Expect the bar height should not change.
424 expect(tester.getSize(find.byType(CupertinoTabBar)).height, tabBarHeight + bottomPadding);
425 });
426
427 testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async {
428 await pumpWidgetWithBoilerplate(tester, MediaQuery(
429 data: const MediaQueryData(),
430 child: CupertinoTabBar(
431 items: <BottomNavigationBarItem>[
432 BottomNavigationBarItem(
433 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
434 label: 'Tab 1',
435 ),
436 BottomNavigationBarItem(
437 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
438 label: 'Tab 2',
439 ),
440 ],
441 ),
442 ));
443
444 expect(find.byType(BackdropFilter), findsOneWidget);
445
446 await pumpWidgetWithBoilerplate(tester, MediaQuery(
447 data: const MediaQueryData(),
448 child: CupertinoTabBar(
449 items: <BottomNavigationBarItem>[
450 BottomNavigationBarItem(
451 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
452 label: 'Tab 1',
453 ),
454 BottomNavigationBarItem(
455 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
456 label: 'Tab 2',
457 ),
458 ],
459 backgroundColor: const Color(0xFFFFFFFF), // Opaque white.
460 ),
461 ));
462
463 expect(find.byType(BackdropFilter), findsNothing);
464 });
465
466 testWidgets('Tap callback', (WidgetTester tester) async {
467 late int callbackTab;
468
469 await pumpWidgetWithBoilerplate(tester, MediaQuery(
470 data: const MediaQueryData(),
471 child: CupertinoTabBar(
472 items: <BottomNavigationBarItem>[
473 BottomNavigationBarItem(
474 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
475 label: 'Tab 1',
476 ),
477 BottomNavigationBarItem(
478 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
479 label: 'Tab 2',
480 ),
481 ],
482 currentIndex: 1,
483 onTap: (int tab) { callbackTab = tab; },
484 ),
485 ));
486
487 await tester.tap(find.text('Tab 1'));
488 expect(callbackTab, 0);
489
490 await tester.tap(find.text('Tab 2'));
491 expect(callbackTab, 1);
492 });
493
494 testWidgets('tabs announce semantics', (WidgetTester tester) async {
495 final SemanticsTester semantics = SemanticsTester(tester);
496
497 await pumpWidgetWithBoilerplate(tester, MediaQuery(
498 data: const MediaQueryData(),
499 child: CupertinoTabBar(
500 items: <BottomNavigationBarItem>[
501 BottomNavigationBarItem(
502 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
503 label: 'Tab 1',
504 ),
505 BottomNavigationBarItem(
506 icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
507 label: 'Tab 2',
508 ),
509 ],
510 ),
511 ));
512
513 expect(semantics, includesNodeWith(
514 label: 'Tab 1',
515 hint: 'Tab 1 of 2',
516 flags: <SemanticsFlag>[SemanticsFlag.isSelected],
517 textDirection: TextDirection.ltr,
518 ));
519
520 expect(semantics, includesNodeWith(
521 label: 'Tab 2',
522 hint: 'Tab 2 of 2',
523 textDirection: TextDirection.ltr,
524 ));
525
526 semantics.dispose();
527 });
528
529 testWidgets('Label of items should be nullable', (WidgetTester tester) async {
530 final MemoryImage iconProvider = MemoryImage(Uint8List.fromList(kTransparentImage));
531 final List<int> itemsTapped = <int>[];
532
533 await pumpWidgetWithBoilerplate(
534 tester,
535 MediaQuery(
536 data: const MediaQueryData(),
537 child: CupertinoTabBar(
538 items: <BottomNavigationBarItem>[
539 BottomNavigationBarItem(
540 icon: ImageIcon(
541 MemoryImage(Uint8List.fromList(kTransparentImage)),
542 ),
543 label: 'Tab 1',
544 ),
545 BottomNavigationBarItem(
546 icon: ImageIcon(
547 iconProvider,
548 ),
549 ),
550 ],
551 onTap: (int index) => itemsTapped.add(index),
552 ),
553 ),
554 );
555
556 expect(find.text('Tab 1'), findsOneWidget);
557
558 final Finder finder = find.byWidgetPredicate(
559 (Widget widget) => widget is Image && widget.image == iconProvider,
560 );
561
562 await tester.tap(finder);
563 expect(itemsTapped, <int>[1]);
564 });
565
566 testWidgets('Hide border hides the top border of the tabBar', (WidgetTester tester) async {
567 await pumpWidgetWithBoilerplate(
568 tester,
569 MediaQuery(
570 data: const MediaQueryData(),
571 child: CupertinoTabBar(
572 items: <BottomNavigationBarItem>[
573 BottomNavigationBarItem(
574 icon: ImageIcon(
575 MemoryImage(Uint8List.fromList(kTransparentImage)),
576 ),
577 label: 'Tab 1',
578 ),
579 BottomNavigationBarItem(
580 icon: ImageIcon(
581 MemoryImage(Uint8List.fromList(kTransparentImage)),
582 ),
583 label: 'Tab 2',
584 ),
585 ],
586 ),
587 ),
588 );
589
590 final DecoratedBox decoratedBox = tester.widget(find.byType(DecoratedBox));
591 final BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration;
592 expect(boxDecoration.border, isNotNull);
593
594 await pumpWidgetWithBoilerplate(
595 tester,
596 MediaQuery(
597 data: const MediaQueryData(),
598 child: CupertinoTabBar(
599 items: <BottomNavigationBarItem>[
600 BottomNavigationBarItem(
601 icon: ImageIcon(
602 MemoryImage(Uint8List.fromList(kTransparentImage)),
603 ),
604 label: 'Tab 1',
605 ),
606 BottomNavigationBarItem(
607 icon: ImageIcon(
608 MemoryImage(Uint8List.fromList(kTransparentImage)),
609 ),
610 label: 'Tab 2',
611 ),
612 ],
613 backgroundColor: const Color(0xFFFFFFFF), // Opaque white.
614 border: null,
615 ),
616 ),
617 );
618
619 final DecoratedBox decoratedBoxHiddenBorder =
620 tester.widget(find.byType(DecoratedBox));
621 final BoxDecoration boxDecorationHiddenBorder =
622 decoratedBoxHiddenBorder.decoration as BoxDecoration;
623 expect(boxDecorationHiddenBorder.border, isNull);
624 });
625
626 testWidgets('Hovering over tab bar item updates cursor to clickable on Web', (WidgetTester tester) async {
627 await pumpWidgetWithBoilerplate(
628 tester,
629 MediaQuery(
630 data: const MediaQueryData(),
631 child: Center(
632 child: CupertinoTabBar(
633 items: const <BottomNavigationBarItem>[
634 BottomNavigationBarItem(
635 icon: Icon(CupertinoIcons.alarm),
636 label: 'Tab 1',
637 ),
638 BottomNavigationBarItem(
639 icon: Icon(CupertinoIcons.app_badge),
640 label: 'Tab 2',
641 ),
642 ],
643 ),
644 ),
645 ),
646 );
647
648 final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
649 await gesture.addPointer(location: const Offset(10, 10));
650 await tester.pumpAndSettle();
651 expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
652
653 final Offset tabItem = tester.getCenter(find.text('Tab 1'));
654 await gesture.moveTo(tabItem);
655 await tester.pumpAndSettle();
656 expect(
657 RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
658 kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
659 );
660 });
661}
662

Provided by KDAB

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