1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:flutter/cupertino.dart'; |
6 | import 'package:flutter/foundation.dart'; |
7 | import 'package:flutter/gestures.dart'; |
8 | import 'package:flutter/rendering.dart'; |
9 | import 'package:flutter_test/flutter_test.dart'; |
10 | |
11 | import '../image_data.dart'; |
12 | import '../widgets/semantics_tester.dart'; |
13 | |
14 | Future<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 | |
30 | Future<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 | |