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 | // This file is run as part of a reduced test set in CI on Mac and Windows |
6 | // machines. |
7 | @Tags(<String>['reduced-test-set']) |
8 | library; |
9 | |
10 | import 'dart:math'; |
11 | |
12 | import 'package:flutter/foundation.dart'; |
13 | import 'package:flutter/gestures.dart'; |
14 | import 'package:flutter/material.dart'; |
15 | import 'package:flutter/services.dart'; |
16 | import 'package:flutter_test/flutter_test.dart'; |
17 | |
18 | void main() { |
19 | testWidgets('Navigation bar updates destinations when tapped', (WidgetTester tester) async { |
20 | int mutatedIndex = -1; |
21 | final Widget widget = _buildWidget( |
22 | NavigationBar( |
23 | destinations: const <Widget>[ |
24 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
25 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
26 | ], |
27 | onDestinationSelected: (int i) { |
28 | mutatedIndex = i; |
29 | }, |
30 | ), |
31 | ); |
32 | |
33 | await tester.pumpWidget(widget); |
34 | |
35 | expect(find.text('AC'), findsOneWidget); |
36 | expect(find.text('Alarm'), findsOneWidget); |
37 | |
38 | await tester.tap(find.text('Alarm')); |
39 | expect(mutatedIndex, 1); |
40 | |
41 | await tester.tap(find.text('AC')); |
42 | expect(mutatedIndex, 0); |
43 | }); |
44 | |
45 | testWidgets('NavigationBar can update background color', (WidgetTester tester) async { |
46 | const Color color = Colors.yellow; |
47 | |
48 | await tester.pumpWidget( |
49 | _buildWidget( |
50 | NavigationBar( |
51 | backgroundColor: color, |
52 | destinations: const <Widget>[ |
53 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
54 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
55 | ], |
56 | onDestinationSelected: (int i) {}, |
57 | ), |
58 | ), |
59 | ); |
60 | |
61 | expect(_getMaterial(tester).color, equals(color)); |
62 | }); |
63 | |
64 | testWidgets('NavigationBar can update elevation', (WidgetTester tester) async { |
65 | const double elevation = 42.0; |
66 | |
67 | await tester.pumpWidget( |
68 | _buildWidget( |
69 | NavigationBar( |
70 | elevation: elevation, |
71 | destinations: const <Widget>[ |
72 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
73 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
74 | ], |
75 | onDestinationSelected: (int i) {}, |
76 | ), |
77 | ), |
78 | ); |
79 | |
80 | expect(_getMaterial(tester).elevation, equals(elevation)); |
81 | }); |
82 | |
83 | testWidgets('NavigationBar adds bottom padding to height', (WidgetTester tester) async { |
84 | const double bottomPadding = 40.0; |
85 | |
86 | await tester.pumpWidget( |
87 | _buildWidget( |
88 | NavigationBar( |
89 | destinations: const <Widget>[ |
90 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
91 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
92 | ], |
93 | onDestinationSelected: (int i) {}, |
94 | ), |
95 | ), |
96 | ); |
97 | |
98 | final double defaultSize = tester.getSize(find.byType(NavigationBar)).height; |
99 | expect(defaultSize, 80); |
100 | |
101 | await tester.pumpWidget( |
102 | _buildWidget( |
103 | MediaQuery( |
104 | data: const MediaQueryData(padding: EdgeInsets.only(bottom: bottomPadding)), |
105 | child: NavigationBar( |
106 | destinations: const <Widget>[ |
107 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
108 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
109 | ], |
110 | onDestinationSelected: (int i) {}, |
111 | ), |
112 | ), |
113 | ), |
114 | ); |
115 | |
116 | final double expectedHeight = defaultSize + bottomPadding; |
117 | expect(tester.getSize(find.byType(NavigationBar)).height, expectedHeight); |
118 | }); |
119 | |
120 | testWidgets('NavigationBar respects the notch/system navigation bar in landscape mode', ( |
121 | WidgetTester tester, |
122 | ) async { |
123 | const double safeAreaPadding = 40.0; |
124 | Widget navigationBar() { |
125 | return NavigationBar( |
126 | destinations: const <Widget>[ |
127 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
128 | NavigationDestination( |
129 | key: Key('Center'), |
130 | icon: Icon(Icons.center_focus_strong), |
131 | label: 'Center', |
132 | ), |
133 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
134 | ], |
135 | onDestinationSelected: (int i) {}, |
136 | ); |
137 | } |
138 | |
139 | await tester.pumpWidget(_buildWidget(navigationBar())); |
140 | final double defaultWidth = tester.getSize(find.byType(NavigationBar)).width; |
141 | final Finder defaultCenterItem = find.byKey(const Key('Center')); |
142 | final Offset center = tester.getCenter(defaultCenterItem); |
143 | expect(center.dx, defaultWidth / 2); |
144 | |
145 | await tester.pumpWidget( |
146 | _buildWidget( |
147 | MediaQuery( |
148 | data: const MediaQueryData(padding: EdgeInsets.only(left: safeAreaPadding)), |
149 | child: navigationBar(), |
150 | ), |
151 | ), |
152 | ); |
153 | |
154 | // The position of center item of navigation bar should indicate whether |
155 | // the safe area is sufficiently respected, when safe area is on the left side. |
156 | // e.g. Android device with system navigation bar in landscape mode. |
157 | final Finder leftPaddedCenterItem = find.byKey(const Key('Center')); |
158 | final Offset leftPaddedCenter = tester.getCenter(leftPaddedCenterItem); |
159 | expect( |
160 | leftPaddedCenter.dx, |
161 | closeTo((defaultWidth + safeAreaPadding) / 2.0, precisionErrorTolerance), |
162 | ); |
163 | |
164 | await tester.pumpWidget( |
165 | _buildWidget( |
166 | MediaQuery( |
167 | data: const MediaQueryData(padding: EdgeInsets.only(right: safeAreaPadding)), |
168 | child: navigationBar(), |
169 | ), |
170 | ), |
171 | ); |
172 | |
173 | // The position of center item of navigation bar should indicate whether |
174 | // the safe area is sufficiently respected, when safe area is on the right side. |
175 | // e.g. Android device with system navigation bar in landscape mode. |
176 | final Finder rightPaddedCenterItem = find.byKey(const Key('Center')); |
177 | final Offset rightPaddedCenter = tester.getCenter(rightPaddedCenterItem); |
178 | expect( |
179 | rightPaddedCenter.dx, |
180 | closeTo((defaultWidth - safeAreaPadding) / 2, precisionErrorTolerance), |
181 | ); |
182 | |
183 | await tester.pumpWidget( |
184 | _buildWidget( |
185 | MediaQuery( |
186 | data: const MediaQueryData( |
187 | padding: EdgeInsets.fromLTRB(safeAreaPadding, 0, safeAreaPadding, safeAreaPadding), |
188 | ), |
189 | child: navigationBar(), |
190 | ), |
191 | ), |
192 | ); |
193 | |
194 | // The position of center item of navigation bar should indicate whether |
195 | // the safe area is sufficiently respected, when safe areas are on both sides. |
196 | // e.g. iOS device with both sides of round corner. |
197 | final Finder paddedCenterItem = find.byKey(const Key('Center')); |
198 | final Offset paddedCenter = tester.getCenter(paddedCenterItem); |
199 | expect(paddedCenter.dx, closeTo(defaultWidth / 2, precisionErrorTolerance)); |
200 | }); |
201 | |
202 | testWidgets('Material2 - NavigationBar uses proper defaults when no parameters are given', ( |
203 | WidgetTester tester, |
204 | ) async { |
205 | // M2 settings that were hand coded. |
206 | await tester.pumpWidget( |
207 | _buildWidget( |
208 | NavigationBar( |
209 | destinations: const <Widget>[ |
210 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
211 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
212 | ], |
213 | onDestinationSelected: (int i) {}, |
214 | ), |
215 | useMaterial3: false, |
216 | ), |
217 | ); |
218 | |
219 | expect(_getMaterial(tester).color, const Color(0xffeaeaea)); |
220 | expect(_getMaterial(tester).surfaceTintColor, null); |
221 | expect(_getMaterial(tester).elevation, 0); |
222 | expect(tester.getSize(find.byType(NavigationBar)).height, 80); |
223 | expect(_getIndicatorDecoration(tester)?.color, const Color(0x3d2196f3)); |
224 | expect( |
225 | _getIndicatorDecoration(tester)?.shape, |
226 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), |
227 | ); |
228 | }); |
229 | |
230 | testWidgets('Material3 - NavigationBar uses proper defaults when no parameters are given', ( |
231 | WidgetTester tester, |
232 | ) async { |
233 | // M3 settings from the token database. |
234 | final ThemeData theme = ThemeData(); |
235 | await tester.pumpWidget( |
236 | _buildWidget( |
237 | NavigationBar( |
238 | destinations: const <Widget>[ |
239 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
240 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
241 | ], |
242 | onDestinationSelected: (int i) {}, |
243 | ), |
244 | useMaterial3: theme.useMaterial3, |
245 | ), |
246 | ); |
247 | |
248 | expect(_getMaterial(tester).color, theme.colorScheme.surfaceContainer); |
249 | expect(_getMaterial(tester).surfaceTintColor, Colors.transparent); |
250 | expect(_getMaterial(tester).elevation, 3); |
251 | expect(tester.getSize(find.byType(NavigationBar)).height, 80); |
252 | expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer); |
253 | expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder()); |
254 | }); |
255 | |
256 | testWidgets('Material2 - NavigationBar shows tooltips with text scaling', ( |
257 | WidgetTester tester, |
258 | ) async { |
259 | const String label = 'A'; |
260 | |
261 | Widget buildApp({required TextScaler textScaler}) { |
262 | return MediaQuery( |
263 | data: MediaQueryData(textScaler: textScaler), |
264 | child: Localizations( |
265 | locale: const Locale('en', 'US'), |
266 | delegates: const <LocalizationsDelegate<dynamic>>[ |
267 | DefaultMaterialLocalizations.delegate, |
268 | DefaultWidgetsLocalizations.delegate, |
269 | ], |
270 | child: MaterialApp( |
271 | theme: ThemeData(useMaterial3: false), |
272 | home: Navigator( |
273 | onGenerateRoute: (RouteSettings settings) { |
274 | return MaterialPageRoute<void>( |
275 | builder: (BuildContext context) { |
276 | return Scaffold( |
277 | bottomNavigationBar: NavigationBar( |
278 | destinations: const <NavigationDestination>[ |
279 | NavigationDestination( |
280 | label: label, |
281 | icon: Icon(Icons.ac_unit), |
282 | tooltip: label, |
283 | ), |
284 | NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)), |
285 | ], |
286 | ), |
287 | ); |
288 | }, |
289 | ); |
290 | }, |
291 | ), |
292 | ), |
293 | ), |
294 | ); |
295 | } |
296 | |
297 | await tester.pumpWidget(buildApp(textScaler: TextScaler.noScaling)); |
298 | expect(find.text(label), findsOneWidget); |
299 | await tester.longPress(find.text(label)); |
300 | expect(find.text(label), findsNWidgets(2)); |
301 | |
302 | // The default size of a tooltip with the text A. |
303 | const Size defaultTooltipSize = Size(14.0, 14.0); |
304 | expect(tester.getSize(find.text(label).last), defaultTooltipSize); |
305 | // The duration is needed to ensure the tooltip disappears. |
306 | await tester.pumpAndSettle(const Duration(seconds: 2)); |
307 | |
308 | await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(4.0))); |
309 | expect(find.text(label), findsOneWidget); |
310 | await tester.longPress(find.text(label)); |
311 | expect( |
312 | tester.getSize(find.text(label).last), |
313 | Size(defaultTooltipSize.width * 4, defaultTooltipSize.height * 4), |
314 | ); |
315 | }); |
316 | |
317 | testWidgets('Material3 - NavigationBar shows tooltips with text scaling', ( |
318 | WidgetTester tester, |
319 | ) async { |
320 | const String label = 'A'; |
321 | |
322 | Widget buildApp({required TextScaler textScaler}) { |
323 | return MediaQuery( |
324 | data: MediaQueryData(textScaler: textScaler), |
325 | child: Localizations( |
326 | locale: const Locale('en', 'US'), |
327 | delegates: const <LocalizationsDelegate<dynamic>>[ |
328 | DefaultMaterialLocalizations.delegate, |
329 | DefaultWidgetsLocalizations.delegate, |
330 | ], |
331 | child: MaterialApp( |
332 | home: Navigator( |
333 | onGenerateRoute: (RouteSettings settings) { |
334 | return MaterialPageRoute<void>( |
335 | builder: (BuildContext context) { |
336 | return Scaffold( |
337 | bottomNavigationBar: NavigationBar( |
338 | destinations: const <NavigationDestination>[ |
339 | NavigationDestination( |
340 | label: label, |
341 | icon: Icon(Icons.ac_unit), |
342 | tooltip: label, |
343 | ), |
344 | NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)), |
345 | ], |
346 | ), |
347 | ); |
348 | }, |
349 | ); |
350 | }, |
351 | ), |
352 | ), |
353 | ), |
354 | ); |
355 | } |
356 | |
357 | await tester.pumpWidget(buildApp(textScaler: TextScaler.noScaling)); |
358 | expect(find.text(label), findsOneWidget); |
359 | await tester.longPress(find.text(label)); |
360 | expect(find.text(label), findsNWidgets(2)); |
361 | |
362 | expect(tester.getSize(find.text(label).last), const Size(14.25, 20.0)); |
363 | // The duration is needed to ensure the tooltip disappears. |
364 | await tester.pumpAndSettle(const Duration(seconds: 2)); |
365 | |
366 | await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(4.0))); |
367 | expect(find.text(label), findsOneWidget); |
368 | await tester.longPress(find.text(label)); |
369 | |
370 | expect(tester.getSize(find.text(label).last), const Size(56.25, 80.0)); |
371 | }); |
372 | |
373 | testWidgets('Material3 - NavigationBar label can scale and has maxScaleFactor', ( |
374 | WidgetTester tester, |
375 | ) async { |
376 | const String label = 'A'; |
377 | |
378 | Widget buildApp({required TextScaler textScaler}) { |
379 | return MediaQuery( |
380 | data: MediaQueryData(textScaler: textScaler), |
381 | child: Localizations( |
382 | locale: const Locale('en', 'US'), |
383 | delegates: const <LocalizationsDelegate<dynamic>>[ |
384 | DefaultMaterialLocalizations.delegate, |
385 | DefaultWidgetsLocalizations.delegate, |
386 | ], |
387 | child: MaterialApp( |
388 | home: Navigator( |
389 | onGenerateRoute: (RouteSettings settings) { |
390 | return MaterialPageRoute<void>( |
391 | builder: (BuildContext context) { |
392 | return Scaffold( |
393 | bottomNavigationBar: NavigationBar( |
394 | destinations: const <NavigationDestination>[ |
395 | NavigationDestination(label: label, icon: Icon(Icons.ac_unit)), |
396 | NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)), |
397 | ], |
398 | ), |
399 | ); |
400 | }, |
401 | ); |
402 | }, |
403 | ), |
404 | ), |
405 | ), |
406 | ); |
407 | } |
408 | |
409 | await tester.pumpWidget(buildApp(textScaler: TextScaler.noScaling)); |
410 | expect(find.text(label), findsOneWidget); |
411 | expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(12.5, 16.0)), true); |
412 | |
413 | await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(1.1))); |
414 | await tester.pumpAndSettle(); |
415 | |
416 | expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(13.7, 18.0)), true); |
417 | |
418 | await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(1.3))); |
419 | |
420 | expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(16.1, 21.0)), true); |
421 | |
422 | await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(4))); |
423 | expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(16.1, 21.0)), true); |
424 | }); |
425 | |
426 | testWidgets('Custom tooltips in NavigationBarDestination', (WidgetTester tester) async { |
427 | await tester.pumpWidget( |
428 | MaterialApp( |
429 | home: Scaffold( |
430 | bottomNavigationBar: NavigationBar( |
431 | destinations: const <NavigationDestination>[ |
432 | NavigationDestination(label: 'A', tooltip: 'A tooltip', icon: Icon(Icons.ac_unit)), |
433 | NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)), |
434 | NavigationDestination(label: 'C', icon: Icon(Icons.cake), tooltip: ''), |
435 | ], |
436 | ), |
437 | ), |
438 | ), |
439 | ); |
440 | |
441 | expect(find.text('A'), findsOneWidget); |
442 | await tester.longPress(find.text('A')); |
443 | expect(find.byTooltip('A tooltip'), findsOneWidget); |
444 | |
445 | expect(find.text('B'), findsOneWidget); |
446 | await tester.longPress(find.text('B')); |
447 | expect(find.byTooltip('B'), findsOneWidget); |
448 | |
449 | expect(find.text('C'), findsOneWidget); |
450 | await tester.longPress(find.text('C')); |
451 | expect(find.byTooltip('C'), findsNothing); |
452 | }); |
453 | |
454 | testWidgets('Navigation bar semantics', (WidgetTester tester) async { |
455 | Widget widget({int selectedIndex = 0}) { |
456 | return _buildWidget( |
457 | NavigationBar( |
458 | selectedIndex: selectedIndex, |
459 | destinations: const <Widget>[ |
460 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
461 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
462 | NavigationDestination(icon: Icon(Icons.abc), label: 'ABC'), |
463 | ], |
464 | ), |
465 | ); |
466 | } |
467 | |
468 | await tester.pumpWidget(widget()); |
469 | |
470 | expect( |
471 | tester.getSemantics(find.text('AC')), |
472 | matchesSemantics( |
473 | label: 'AC${kIsWeb ? '': '\nTab 1 of 3'} ', |
474 | textDirection: TextDirection.ltr, |
475 | isFocusable: true, |
476 | isSelected: true, |
477 | isButton: true, |
478 | hasSelectedState: true, |
479 | hasEnabledState: true, |
480 | isEnabled: true, |
481 | hasTapAction: true, |
482 | hasFocusAction: true, |
483 | ), |
484 | ); |
485 | expect( |
486 | tester.getSemantics(find.text('Alarm')), |
487 | matchesSemantics( |
488 | label: 'Alarm${kIsWeb ? '': '\nTab 2 of 3'} ', |
489 | textDirection: TextDirection.ltr, |
490 | isFocusable: true, |
491 | isButton: true, |
492 | hasSelectedState: true, |
493 | hasEnabledState: true, |
494 | isEnabled: true, |
495 | hasTapAction: true, |
496 | hasFocusAction: true, |
497 | ), |
498 | ); |
499 | expect( |
500 | tester.getSemantics(find.text('ABC')), |
501 | matchesSemantics( |
502 | label: 'ABC${kIsWeb ? '': '\nTab 3 of 3'} ', |
503 | textDirection: TextDirection.ltr, |
504 | isFocusable: true, |
505 | isButton: true, |
506 | hasSelectedState: true, |
507 | hasEnabledState: true, |
508 | isEnabled: true, |
509 | hasTapAction: true, |
510 | hasFocusAction: true, |
511 | ), |
512 | ); |
513 | |
514 | await tester.pumpWidget(widget(selectedIndex: 1)); |
515 | |
516 | expect( |
517 | tester.getSemantics(find.text('AC')), |
518 | matchesSemantics( |
519 | label: 'AC${kIsWeb ? '': '\nTab 1 of 3'} ', |
520 | textDirection: TextDirection.ltr, |
521 | isFocusable: true, |
522 | isButton: true, |
523 | hasEnabledState: true, |
524 | hasSelectedState: true, |
525 | isEnabled: true, |
526 | hasTapAction: true, |
527 | hasFocusAction: true, |
528 | ), |
529 | ); |
530 | expect( |
531 | tester.getSemantics(find.text('Alarm')), |
532 | matchesSemantics( |
533 | label: 'Alarm${kIsWeb ? '': '\nTab 2 of 3'} ', |
534 | textDirection: TextDirection.ltr, |
535 | isFocusable: true, |
536 | isSelected: true, |
537 | isButton: true, |
538 | hasEnabledState: true, |
539 | hasSelectedState: true, |
540 | isEnabled: true, |
541 | hasTapAction: true, |
542 | hasFocusAction: true, |
543 | ), |
544 | ); |
545 | expect( |
546 | tester.getSemantics(find.text('ABC')), |
547 | matchesSemantics( |
548 | label: 'ABC${kIsWeb ? '': '\nTab 3 of 3'} ', |
549 | textDirection: TextDirection.ltr, |
550 | isFocusable: true, |
551 | isButton: true, |
552 | hasEnabledState: true, |
553 | hasSelectedState: true, |
554 | isEnabled: true, |
555 | hasTapAction: true, |
556 | hasFocusAction: true, |
557 | ), |
558 | ); |
559 | }); |
560 | testWidgets('Navigation bar disabled semantics', (WidgetTester tester) async { |
561 | Widget widget({int selectedIndex = 0}) { |
562 | return _buildWidget( |
563 | NavigationBar( |
564 | selectedIndex: selectedIndex, |
565 | destinations: const <Widget>[ |
566 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC', enabled: false), |
567 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'Another'), |
568 | ], |
569 | ), |
570 | ); |
571 | } |
572 | |
573 | await tester.pumpWidget(widget()); |
574 | |
575 | expect( |
576 | tester.getSemantics(find.text('AC')), |
577 | matchesSemantics( |
578 | label: 'AC${kIsWeb ? '': '\nTab 1 of 2'} ', |
579 | textDirection: TextDirection.ltr, |
580 | isSelected: true, |
581 | hasSelectedState: true, |
582 | hasEnabledState: true, |
583 | isButton: true, |
584 | ), |
585 | ); |
586 | }); |
587 | |
588 | testWidgets('Navigation bar semantics with some labels hidden', (WidgetTester tester) async { |
589 | Widget widget({int selectedIndex = 0}) { |
590 | return _buildWidget( |
591 | NavigationBar( |
592 | labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, |
593 | selectedIndex: selectedIndex, |
594 | destinations: const <Widget>[ |
595 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
596 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
597 | ], |
598 | ), |
599 | ); |
600 | } |
601 | |
602 | await tester.pumpWidget(widget()); |
603 | |
604 | expect( |
605 | tester.getSemantics(find.text('AC')), |
606 | matchesSemantics( |
607 | label: 'AC${kIsWeb ? '': '\nTab 1 of 2'} ', |
608 | textDirection: TextDirection.ltr, |
609 | isFocusable: true, |
610 | isSelected: true, |
611 | isButton: true, |
612 | hasEnabledState: true, |
613 | hasSelectedState: true, |
614 | isEnabled: true, |
615 | hasTapAction: true, |
616 | hasFocusAction: true, |
617 | ), |
618 | ); |
619 | expect( |
620 | tester.getSemantics(find.text('Alarm')), |
621 | matchesSemantics( |
622 | label: 'Alarm${kIsWeb ? '': '\nTab 2 of 2'} ', |
623 | textDirection: TextDirection.ltr, |
624 | isFocusable: true, |
625 | isButton: true, |
626 | hasEnabledState: true, |
627 | hasSelectedState: true, |
628 | isEnabled: true, |
629 | hasTapAction: true, |
630 | hasFocusAction: true, |
631 | ), |
632 | ); |
633 | |
634 | await tester.pumpWidget(widget(selectedIndex: 1)); |
635 | |
636 | expect( |
637 | tester.getSemantics(find.text('AC')), |
638 | matchesSemantics( |
639 | label: 'AC${kIsWeb ? '': '\nTab 1 of 2'} ', |
640 | textDirection: TextDirection.ltr, |
641 | isFocusable: true, |
642 | isButton: true, |
643 | hasEnabledState: true, |
644 | hasSelectedState: true, |
645 | isEnabled: true, |
646 | hasTapAction: true, |
647 | hasFocusAction: true, |
648 | ), |
649 | ); |
650 | expect( |
651 | tester.getSemantics(find.text('Alarm')), |
652 | matchesSemantics( |
653 | label: 'Alarm${kIsWeb ? '': '\nTab 2 of 2'} ', |
654 | textDirection: TextDirection.ltr, |
655 | isFocusable: true, |
656 | hasEnabledState: true, |
657 | hasSelectedState: true, |
658 | isEnabled: true, |
659 | isSelected: true, |
660 | isButton: true, |
661 | hasTapAction: true, |
662 | hasFocusAction: true, |
663 | ), |
664 | ); |
665 | }); |
666 | |
667 | testWidgets('Navigation bar does not grow with text scale factor', (WidgetTester tester) async { |
668 | const int animationMilliseconds = 800; |
669 | |
670 | Widget widget({TextScaler textScaler = TextScaler.noScaling}) { |
671 | return _buildWidget( |
672 | MediaQuery( |
673 | data: MediaQueryData(textScaler: textScaler), |
674 | child: NavigationBar( |
675 | animationDuration: const Duration(milliseconds: animationMilliseconds), |
676 | destinations: const <NavigationDestination>[ |
677 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
678 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
679 | ], |
680 | ), |
681 | ), |
682 | ); |
683 | } |
684 | |
685 | await tester.pumpWidget(widget()); |
686 | final double initialHeight = tester.getSize(find.byType(NavigationBar)).height; |
687 | |
688 | await tester.pumpWidget(widget(textScaler: const TextScaler.linear(2))); |
689 | final double newHeight = tester.getSize(find.byType(NavigationBar)).height; |
690 | |
691 | expect(newHeight, equals(initialHeight)); |
692 | }); |
693 | |
694 | testWidgets('Material3 - Navigation indicator renders ripple', (WidgetTester tester) async { |
695 | // This is a regression test for https://github.com/flutter/flutter/issues/116751. |
696 | int selectedIndex = 0; |
697 | |
698 | Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) { |
699 | return MaterialApp( |
700 | home: Scaffold( |
701 | bottomNavigationBar: Center( |
702 | child: NavigationBar( |
703 | selectedIndex: selectedIndex, |
704 | labelBehavior: labelBehavior, |
705 | destinations: const <Widget>[ |
706 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
707 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
708 | ], |
709 | onDestinationSelected: (int i) {}, |
710 | ), |
711 | ), |
712 | ), |
713 | ); |
714 | } |
715 | |
716 | await tester.pumpWidget(buildWidget()); |
717 | |
718 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
719 | await gesture.addPointer(); |
720 | await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm))); |
721 | await tester.pumpAndSettle(); |
722 | |
723 | final RenderObject inkFeatures = tester.allRenderObjects.firstWhere( |
724 | (RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures', |
725 | ); |
726 | Offset indicatorCenter = const Offset(600, 30); |
727 | const Size includedIndicatorSize = Size(64, 32); |
728 | const Size excludedIndicatorSize = Size(74, 40); |
729 | |
730 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default). |
731 | expect( |
732 | inkFeatures, |
733 | paints |
734 | ..clipPath( |
735 | pathMatcher: isPathThat( |
736 | includes: <Offset>[ |
737 | // Left center. |
738 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
739 | // Top center. |
740 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
741 | // Right center. |
742 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
743 | // Bottom center. |
744 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
745 | ], |
746 | excludes: <Offset>[ |
747 | // Left center. |
748 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
749 | // Top center. |
750 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
751 | // Right center. |
752 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
753 | // Bottom center. |
754 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
755 | ], |
756 | ), |
757 | ) |
758 | ..circle( |
759 | x: indicatorCenter.dx, |
760 | y: indicatorCenter.dy, |
761 | radius: 35.0, |
762 | color: const Color(0x0a000000), |
763 | ), |
764 | ); |
765 | |
766 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`. |
767 | await tester.pumpWidget( |
768 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide), |
769 | ); |
770 | await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm))); |
771 | await tester.pumpAndSettle(); |
772 | |
773 | indicatorCenter = const Offset(600, 40); |
774 | |
775 | expect( |
776 | inkFeatures, |
777 | paints |
778 | ..clipPath( |
779 | pathMatcher: isPathThat( |
780 | includes: <Offset>[ |
781 | // Left center. |
782 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
783 | // Top center. |
784 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
785 | // Right center. |
786 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
787 | // Bottom center. |
788 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
789 | ], |
790 | excludes: <Offset>[ |
791 | // Left center. |
792 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
793 | // Top center. |
794 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
795 | // Right center. |
796 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
797 | // Bottom center. |
798 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
799 | ], |
800 | ), |
801 | ) |
802 | ..circle( |
803 | x: indicatorCenter.dx, |
804 | y: indicatorCenter.dy, |
805 | radius: 35.0, |
806 | color: const Color(0x0a000000), |
807 | ), |
808 | ); |
809 | |
810 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`. |
811 | await tester.pumpWidget( |
812 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected), |
813 | ); |
814 | await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm))); |
815 | await tester.pumpAndSettle(); |
816 | |
817 | expect( |
818 | inkFeatures, |
819 | paints |
820 | ..clipPath( |
821 | pathMatcher: isPathThat( |
822 | includes: <Offset>[ |
823 | // Left center. |
824 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
825 | // Top center. |
826 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
827 | // Right center. |
828 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
829 | // Bottom center. |
830 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
831 | ], |
832 | excludes: <Offset>[ |
833 | // Left center. |
834 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
835 | // Top center. |
836 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
837 | // Right center. |
838 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
839 | // Bottom center. |
840 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
841 | ], |
842 | ), |
843 | ) |
844 | ..circle( |
845 | x: indicatorCenter.dx, |
846 | y: indicatorCenter.dy, |
847 | radius: 35.0, |
848 | color: const Color(0x0a000000), |
849 | ), |
850 | ); |
851 | |
852 | // Make sure ripple is shifted when selectedIndex changes. |
853 | selectedIndex = 1; |
854 | await tester.pumpWidget( |
855 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected), |
856 | ); |
857 | await tester.pumpAndSettle(); |
858 | indicatorCenter = const Offset(600, 30); |
859 | |
860 | expect( |
861 | inkFeatures, |
862 | paints |
863 | ..clipPath( |
864 | pathMatcher: isPathThat( |
865 | includes: <Offset>[ |
866 | // Left center. |
867 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
868 | // Top center. |
869 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
870 | // Right center. |
871 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
872 | // Bottom center. |
873 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
874 | ], |
875 | excludes: <Offset>[ |
876 | // Left center. |
877 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
878 | // Top center. |
879 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
880 | // Right center. |
881 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
882 | // Bottom center. |
883 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
884 | ], |
885 | ), |
886 | ) |
887 | ..circle( |
888 | x: indicatorCenter.dx, |
889 | y: indicatorCenter.dy, |
890 | radius: 35.0, |
891 | color: const Color(0x0a000000), |
892 | ), |
893 | ); |
894 | }); |
895 | |
896 | testWidgets('Material3 - Navigation indicator ripple golden test', (WidgetTester tester) async { |
897 | // This is a regression test for https://github.com/flutter/flutter/issues/117420. |
898 | |
899 | Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) { |
900 | return MaterialApp( |
901 | home: Scaffold( |
902 | bottomNavigationBar: Center( |
903 | child: NavigationBar( |
904 | labelBehavior: labelBehavior, |
905 | destinations: const <Widget>[ |
906 | NavigationDestination(icon: SizedBox(), label: 'AC'), |
907 | NavigationDestination(icon: SizedBox(), label: 'Alarm'), |
908 | ], |
909 | onDestinationSelected: (int i) {}, |
910 | ), |
911 | ), |
912 | ), |
913 | ); |
914 | } |
915 | |
916 | await tester.pumpWidget(buildWidget()); |
917 | |
918 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
919 | await gesture.addPointer(); |
920 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last)); |
921 | await tester.pumpAndSettle(); |
922 | |
923 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default). |
924 | await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysShow_m3.png')); |
925 | |
926 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`. |
927 | await tester.pumpWidget( |
928 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide), |
929 | ); |
930 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last)); |
931 | await tester.pumpAndSettle(); |
932 | |
933 | await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysHide_m3.png')); |
934 | |
935 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`. |
936 | await tester.pumpWidget( |
937 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected), |
938 | ); |
939 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).first)); |
940 | await tester.pumpAndSettle(); |
941 | |
942 | await expectLater( |
943 | find.byType(NavigationBar), |
944 | matchesGoldenFile('indicator_onlyShowSelected_selected_m3.png'), |
945 | ); |
946 | |
947 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last)); |
948 | await tester.pumpAndSettle(); |
949 | |
950 | await expectLater( |
951 | find.byType(NavigationBar), |
952 | matchesGoldenFile('indicator_onlyShowSelected_unselected_m3.png'), |
953 | ); |
954 | }); |
955 | |
956 | testWidgets('Navigation indicator scale transform', (WidgetTester tester) async { |
957 | int selectedIndex = 0; |
958 | |
959 | Widget buildNavigationBar() { |
960 | return MaterialApp( |
961 | home: Scaffold( |
962 | bottomNavigationBar: Center( |
963 | child: NavigationBar( |
964 | selectedIndex: selectedIndex, |
965 | destinations: const <Widget>[ |
966 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
967 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
968 | ], |
969 | onDestinationSelected: (int i) {}, |
970 | ), |
971 | ), |
972 | ), |
973 | ); |
974 | } |
975 | |
976 | await tester.pumpWidget(buildNavigationBar()); |
977 | await tester.pumpAndSettle(); |
978 | final Finder transformFinder = |
979 | find |
980 | .descendant(of: find.byType(NavigationIndicator), matching: find.byType(Transform)) |
981 | .last; |
982 | Matrix4 transform = tester.widget<Transform>(transformFinder).transform; |
983 | expect(transform.getColumn(0)[0], 0.0); |
984 | |
985 | selectedIndex = 1; |
986 | await tester.pumpWidget(buildNavigationBar()); |
987 | await tester.pump(const Duration(milliseconds: 100)); |
988 | transform = tester.widget<Transform>(transformFinder).transform; |
989 | expect(transform.getColumn(0)[0], closeTo(0.7805849514007568, precisionErrorTolerance)); |
990 | |
991 | await tester.pump(const Duration(milliseconds: 100)); |
992 | transform = tester.widget<Transform>(transformFinder).transform; |
993 | expect(transform.getColumn(0)[0], closeTo(0.9473570239543915, precisionErrorTolerance)); |
994 | |
995 | await tester.pumpAndSettle(); |
996 | transform = tester.widget<Transform>(transformFinder).transform; |
997 | expect(transform.getColumn(0)[0], 1.0); |
998 | }); |
999 | |
1000 | testWidgets('Material3 - Navigation destination updates indicator color and shape', ( |
1001 | WidgetTester tester, |
1002 | ) async { |
1003 | final ThemeData theme = ThemeData(); |
1004 | const Color color = Color(0xff0000ff); |
1005 | const ShapeBorder shape = RoundedRectangleBorder(); |
1006 | |
1007 | Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) { |
1008 | return MaterialApp( |
1009 | theme: theme, |
1010 | home: Scaffold( |
1011 | bottomNavigationBar: RepaintBoundary( |
1012 | child: NavigationBar( |
1013 | indicatorColor: indicatorColor, |
1014 | indicatorShape: indicatorShape, |
1015 | destinations: const <Widget>[ |
1016 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
1017 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
1018 | ], |
1019 | onDestinationSelected: (int i) {}, |
1020 | ), |
1021 | ), |
1022 | ), |
1023 | ); |
1024 | } |
1025 | |
1026 | await tester.pumpWidget(buildNavigationBar()); |
1027 | |
1028 | // Test default indicator color and shape. |
1029 | expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer); |
1030 | expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder()); |
1031 | |
1032 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
1033 | await gesture.addPointer(); |
1034 | await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last)); |
1035 | await tester.pumpAndSettle(); |
1036 | |
1037 | // Test default indicator color and shape with ripple. |
1038 | await expectLater( |
1039 | find.byType(NavigationBar), |
1040 | matchesGoldenFile('m3.navigation_bar.default.indicator.inkwell.shape.png'), |
1041 | ); |
1042 | |
1043 | await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape)); |
1044 | |
1045 | // Test custom indicator color and shape. |
1046 | expect(_getIndicatorDecoration(tester)?.color, color); |
1047 | expect(_getIndicatorDecoration(tester)?.shape, shape); |
1048 | |
1049 | // Test custom indicator color and shape with ripple. |
1050 | await expectLater( |
1051 | find.byType(NavigationBar), |
1052 | matchesGoldenFile('m3.navigation_bar.custom.indicator.inkwell.shape.png'), |
1053 | ); |
1054 | }); |
1055 | |
1056 | testWidgets('Destinations respect their disabled state', (WidgetTester tester) async { |
1057 | int selectedIndex = 0; |
1058 | |
1059 | await tester.pumpWidget( |
1060 | _buildWidget( |
1061 | NavigationBar( |
1062 | destinations: const <Widget>[ |
1063 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
1064 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
1065 | NavigationDestination(icon: Icon(Icons.bookmark), label: 'Bookmark', enabled: false), |
1066 | ], |
1067 | onDestinationSelected: (int i) => selectedIndex = i, |
1068 | selectedIndex: selectedIndex, |
1069 | ), |
1070 | ), |
1071 | ); |
1072 | |
1073 | await tester.tap(find.text('AC')); |
1074 | expect(selectedIndex, 0); |
1075 | |
1076 | await tester.tap(find.text('Alarm')); |
1077 | expect(selectedIndex, 1); |
1078 | |
1079 | await tester.tap(find.text('Bookmark')); |
1080 | expect(selectedIndex, 1); |
1081 | }); |
1082 | |
1083 | testWidgets('NavigationBar respects overlayColor in active/pressed/hovered states', ( |
1084 | WidgetTester tester, |
1085 | ) async { |
1086 | tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; |
1087 | const Color hoverColor = Color(0xff0000ff); |
1088 | const Color focusColor = Color(0xff00ffff); |
1089 | const Color pressedColor = Color(0xffff00ff); |
1090 | final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color>(( |
1091 | Set<MaterialState> states, |
1092 | ) { |
1093 | if (states.contains(MaterialState.hovered)) { |
1094 | return hoverColor; |
1095 | } |
1096 | if (states.contains(MaterialState.focused)) { |
1097 | return focusColor; |
1098 | } |
1099 | if (states.contains(MaterialState.pressed)) { |
1100 | return pressedColor; |
1101 | } |
1102 | return Colors.transparent; |
1103 | }); |
1104 | |
1105 | await tester.pumpWidget( |
1106 | MaterialApp( |
1107 | home: Scaffold( |
1108 | bottomNavigationBar: RepaintBoundary( |
1109 | child: NavigationBar( |
1110 | overlayColor: overlayColor, |
1111 | destinations: const <Widget>[ |
1112 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
1113 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
1114 | ], |
1115 | onDestinationSelected: (int i) {}, |
1116 | ), |
1117 | ), |
1118 | ), |
1119 | ), |
1120 | ); |
1121 | |
1122 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
1123 | await gesture.addPointer(); |
1124 | await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last)); |
1125 | await tester.pumpAndSettle(); |
1126 | |
1127 | final RenderObject inkFeatures = tester.allRenderObjects.firstWhere( |
1128 | (RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures', |
1129 | ); |
1130 | |
1131 | // Test hovered state. |
1132 | expect( |
1133 | inkFeatures, |
1134 | kIsWeb |
1135 | ? (paints |
1136 | ..rrect() |
1137 | ..rrect() |
1138 | ..circle(color: hoverColor)) |
1139 | : (paints..circle(color: hoverColor)), |
1140 | ); |
1141 | |
1142 | await gesture.down(tester.getCenter(find.byType(NavigationIndicator).last)); |
1143 | await tester.pumpAndSettle(); |
1144 | |
1145 | // Test pressed state. |
1146 | expect( |
1147 | inkFeatures, |
1148 | kIsWeb |
1149 | ? (paints |
1150 | ..circle() |
1151 | ..circle() |
1152 | ..circle(color: pressedColor)) |
1153 | : (paints |
1154 | ..circle() |
1155 | ..circle(color: pressedColor)), |
1156 | ); |
1157 | |
1158 | await gesture.up(); |
1159 | await tester.pumpAndSettle(); |
1160 | |
1161 | // Press tab to focus the navigation bar. |
1162 | await tester.sendKeyEvent(LogicalKeyboardKey.tab); |
1163 | await tester.pumpAndSettle(); |
1164 | |
1165 | // Test focused state. |
1166 | expect( |
1167 | inkFeatures, |
1168 | kIsWeb |
1169 | ? (paints |
1170 | ..circle() |
1171 | ..circle(color: focusColor)) |
1172 | : (paints |
1173 | ..circle() |
1174 | ..circle(color: focusColor)), |
1175 | ); |
1176 | }); |
1177 | |
1178 | testWidgets('NavigationBar.labelPadding overrides NavigationDestination.label padding', ( |
1179 | WidgetTester tester, |
1180 | ) async { |
1181 | const EdgeInsetsGeometry labelPadding = EdgeInsets.all(8); |
1182 | Widget buildNavigationBar({EdgeInsetsGeometry? labelPadding}) { |
1183 | return MaterialApp( |
1184 | home: Scaffold( |
1185 | bottomNavigationBar: NavigationBar( |
1186 | labelPadding: labelPadding, |
1187 | destinations: const <Widget>[ |
1188 | NavigationDestination(icon: Icon(Icons.home), label: 'Home'), |
1189 | NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'), |
1190 | ], |
1191 | onDestinationSelected: (int i) {}, |
1192 | ), |
1193 | ), |
1194 | ); |
1195 | } |
1196 | |
1197 | await tester.pumpWidget(buildNavigationBar()); |
1198 | expect(_getLabelPadding(tester, 'Home'), const EdgeInsets.only(top: 4)); |
1199 | expect(_getLabelPadding(tester, 'Settings'), const EdgeInsets.only(top: 4)); |
1200 | |
1201 | await tester.pumpWidget(buildNavigationBar(labelPadding: labelPadding)); |
1202 | expect(_getLabelPadding(tester, 'Home'), labelPadding); |
1203 | expect(_getLabelPadding(tester, 'Settings'), labelPadding); |
1204 | }); |
1205 | |
1206 | group('Material 2', () { |
1207 | // These tests are only relevant for Material 2. Once Material 2 |
1208 | // support is deprecated and the APIs are removed, these tests |
1209 | // can be deleted. |
1210 | |
1211 | testWidgets('Material2 - Navigation destination updates indicator color and shape', ( |
1212 | WidgetTester tester, |
1213 | ) async { |
1214 | final ThemeData theme = ThemeData(useMaterial3: false); |
1215 | const Color color = Color(0xff0000ff); |
1216 | const ShapeBorder shape = RoundedRectangleBorder(); |
1217 | |
1218 | Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) { |
1219 | return MaterialApp( |
1220 | theme: theme, |
1221 | home: Scaffold( |
1222 | bottomNavigationBar: NavigationBar( |
1223 | indicatorColor: indicatorColor, |
1224 | indicatorShape: indicatorShape, |
1225 | destinations: const <Widget>[ |
1226 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
1227 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
1228 | ], |
1229 | onDestinationSelected: (int i) {}, |
1230 | ), |
1231 | ), |
1232 | ); |
1233 | } |
1234 | |
1235 | await tester.pumpWidget(buildNavigationBar()); |
1236 | |
1237 | // Test default indicator color and shape. |
1238 | expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondary.withOpacity(0.24)); |
1239 | expect( |
1240 | _getIndicatorDecoration(tester)?.shape, |
1241 | const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), |
1242 | ); |
1243 | |
1244 | await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape)); |
1245 | |
1246 | // Test custom indicator color and shape. |
1247 | expect(_getIndicatorDecoration(tester)?.color, color); |
1248 | expect(_getIndicatorDecoration(tester)?.shape, shape); |
1249 | }); |
1250 | |
1251 | testWidgets('Material2 - Navigation indicator renders ripple', (WidgetTester tester) async { |
1252 | // This is a regression test for https://github.com/flutter/flutter/issues/116751. |
1253 | int selectedIndex = 0; |
1254 | |
1255 | Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) { |
1256 | return MaterialApp( |
1257 | theme: ThemeData(useMaterial3: false), |
1258 | home: Scaffold( |
1259 | bottomNavigationBar: Center( |
1260 | child: NavigationBar( |
1261 | selectedIndex: selectedIndex, |
1262 | labelBehavior: labelBehavior, |
1263 | destinations: const <Widget>[ |
1264 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
1265 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
1266 | ], |
1267 | onDestinationSelected: (int i) {}, |
1268 | ), |
1269 | ), |
1270 | ), |
1271 | ); |
1272 | } |
1273 | |
1274 | await tester.pumpWidget(buildWidget()); |
1275 | |
1276 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
1277 | await gesture.addPointer(); |
1278 | await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm))); |
1279 | await tester.pumpAndSettle(); |
1280 | |
1281 | final RenderObject inkFeatures = tester.allRenderObjects.firstWhere( |
1282 | (RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures', |
1283 | ); |
1284 | Offset indicatorCenter = const Offset(600, 33); |
1285 | const Size includedIndicatorSize = Size(64, 32); |
1286 | const Size excludedIndicatorSize = Size(74, 40); |
1287 | |
1288 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default). |
1289 | expect( |
1290 | inkFeatures, |
1291 | paints |
1292 | ..clipPath( |
1293 | pathMatcher: isPathThat( |
1294 | includes: <Offset>[ |
1295 | // Left center. |
1296 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1297 | // Top center. |
1298 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
1299 | // Right center. |
1300 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1301 | // Bottom center. |
1302 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
1303 | ], |
1304 | excludes: <Offset>[ |
1305 | // Left center. |
1306 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1307 | // Top center. |
1308 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
1309 | // Right center. |
1310 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1311 | // Bottom center. |
1312 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
1313 | ], |
1314 | ), |
1315 | ) |
1316 | ..circle( |
1317 | x: indicatorCenter.dx, |
1318 | y: indicatorCenter.dy, |
1319 | radius: 35.0, |
1320 | color: const Color(0x0a000000), |
1321 | ), |
1322 | ); |
1323 | |
1324 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`. |
1325 | await tester.pumpWidget( |
1326 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide), |
1327 | ); |
1328 | await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm))); |
1329 | await tester.pumpAndSettle(); |
1330 | |
1331 | indicatorCenter = const Offset(600, 40); |
1332 | |
1333 | expect( |
1334 | inkFeatures, |
1335 | paints |
1336 | ..clipPath( |
1337 | pathMatcher: isPathThat( |
1338 | includes: <Offset>[ |
1339 | // Left center. |
1340 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1341 | // Top center. |
1342 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
1343 | // Right center. |
1344 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1345 | // Bottom center. |
1346 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
1347 | ], |
1348 | excludes: <Offset>[ |
1349 | // Left center. |
1350 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1351 | // Top center. |
1352 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
1353 | // Right center. |
1354 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1355 | // Bottom center. |
1356 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
1357 | ], |
1358 | ), |
1359 | ) |
1360 | ..circle( |
1361 | x: indicatorCenter.dx, |
1362 | y: indicatorCenter.dy, |
1363 | radius: 35.0, |
1364 | color: const Color(0x0a000000), |
1365 | ), |
1366 | ); |
1367 | |
1368 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`. |
1369 | await tester.pumpWidget( |
1370 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected), |
1371 | ); |
1372 | await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm))); |
1373 | await tester.pumpAndSettle(); |
1374 | |
1375 | expect( |
1376 | inkFeatures, |
1377 | paints |
1378 | ..clipPath( |
1379 | pathMatcher: isPathThat( |
1380 | includes: <Offset>[ |
1381 | // Left center. |
1382 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1383 | // Top center. |
1384 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
1385 | // Right center. |
1386 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1387 | // Bottom center. |
1388 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
1389 | ], |
1390 | excludes: <Offset>[ |
1391 | // Left center. |
1392 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1393 | // Top center. |
1394 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
1395 | // Right center. |
1396 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1397 | // Bottom center. |
1398 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
1399 | ], |
1400 | ), |
1401 | ) |
1402 | ..circle( |
1403 | x: indicatorCenter.dx, |
1404 | y: indicatorCenter.dy, |
1405 | radius: 35.0, |
1406 | color: const Color(0x0a000000), |
1407 | ), |
1408 | ); |
1409 | |
1410 | // Make sure ripple is shifted when selectedIndex changes. |
1411 | selectedIndex = 1; |
1412 | await tester.pumpWidget( |
1413 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected), |
1414 | ); |
1415 | await tester.pumpAndSettle(); |
1416 | indicatorCenter = const Offset(600, 33); |
1417 | |
1418 | expect( |
1419 | inkFeatures, |
1420 | paints |
1421 | ..clipPath( |
1422 | pathMatcher: isPathThat( |
1423 | includes: <Offset>[ |
1424 | // Left center. |
1425 | Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1426 | // Top center. |
1427 | Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)), |
1428 | // Right center. |
1429 | Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy), |
1430 | // Bottom center. |
1431 | Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)), |
1432 | ], |
1433 | excludes: <Offset>[ |
1434 | // Left center. |
1435 | Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1436 | // Top center. |
1437 | Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)), |
1438 | // Right center. |
1439 | Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy), |
1440 | // Bottom center. |
1441 | Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)), |
1442 | ], |
1443 | ), |
1444 | ) |
1445 | ..circle( |
1446 | x: indicatorCenter.dx, |
1447 | y: indicatorCenter.dy, |
1448 | radius: 35.0, |
1449 | color: const Color(0x0a000000), |
1450 | ), |
1451 | ); |
1452 | }); |
1453 | |
1454 | testWidgets('Material2 - Navigation indicator ripple golden test', (WidgetTester tester) async { |
1455 | // This is a regression test for https://github.com/flutter/flutter/issues/117420. |
1456 | |
1457 | Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) { |
1458 | return MaterialApp( |
1459 | theme: ThemeData(useMaterial3: false), |
1460 | home: Scaffold( |
1461 | bottomNavigationBar: Center( |
1462 | child: NavigationBar( |
1463 | labelBehavior: labelBehavior, |
1464 | destinations: const <Widget>[ |
1465 | NavigationDestination(icon: SizedBox(), label: 'AC'), |
1466 | NavigationDestination(icon: SizedBox(), label: 'Alarm'), |
1467 | ], |
1468 | onDestinationSelected: (int i) {}, |
1469 | ), |
1470 | ), |
1471 | ), |
1472 | ); |
1473 | } |
1474 | |
1475 | await tester.pumpWidget(buildWidget()); |
1476 | |
1477 | final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
1478 | await gesture.addPointer(); |
1479 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last)); |
1480 | await tester.pumpAndSettle(); |
1481 | |
1482 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default). |
1483 | await expectLater( |
1484 | find.byType(NavigationBar), |
1485 | matchesGoldenFile('indicator_alwaysShow_m2.png'), |
1486 | ); |
1487 | |
1488 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`. |
1489 | await tester.pumpWidget( |
1490 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide), |
1491 | ); |
1492 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last)); |
1493 | await tester.pumpAndSettle(); |
1494 | |
1495 | await expectLater( |
1496 | find.byType(NavigationBar), |
1497 | matchesGoldenFile('indicator_alwaysHide_m2.png'), |
1498 | ); |
1499 | |
1500 | // Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`. |
1501 | await tester.pumpWidget( |
1502 | buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected), |
1503 | ); |
1504 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).first)); |
1505 | await tester.pumpAndSettle(); |
1506 | |
1507 | await expectLater( |
1508 | find.byType(NavigationBar), |
1509 | matchesGoldenFile('indicator_onlyShowSelected_selected_m2.png'), |
1510 | ); |
1511 | |
1512 | await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last)); |
1513 | await tester.pumpAndSettle(); |
1514 | |
1515 | await expectLater( |
1516 | find.byType(NavigationBar), |
1517 | matchesGoldenFile('indicator_onlyShowSelected_unselected_m2.png'), |
1518 | ); |
1519 | }); |
1520 | |
1521 | testWidgets('Destination icon does not rebuild when tapped', (WidgetTester tester) async { |
1522 | // This is a regression test for https://github.com/flutter/flutter/issues/122811. |
1523 | |
1524 | Widget buildNavigationBar() { |
1525 | return MaterialApp( |
1526 | home: Scaffold( |
1527 | bottomNavigationBar: StatefulBuilder( |
1528 | builder: (BuildContext context, StateSetter setState) { |
1529 | int selectedIndex = 0; |
1530 | return NavigationBar( |
1531 | selectedIndex: selectedIndex, |
1532 | destinations: const <Widget>[ |
1533 | NavigationDestination( |
1534 | icon: IconWithRandomColor(icon: Icons.ac_unit), |
1535 | label: 'AC', |
1536 | ), |
1537 | NavigationDestination( |
1538 | icon: IconWithRandomColor(icon: Icons.access_alarm), |
1539 | label: 'Alarm', |
1540 | ), |
1541 | ], |
1542 | onDestinationSelected: (int i) { |
1543 | setState(() { |
1544 | selectedIndex = i; |
1545 | }); |
1546 | }, |
1547 | ); |
1548 | }, |
1549 | ), |
1550 | ), |
1551 | ); |
1552 | } |
1553 | |
1554 | await tester.pumpWidget(buildNavigationBar()); |
1555 | Icon icon = tester.widget<Icon>(find.byType(Icon).last); |
1556 | final Color initialColor = icon.color!; |
1557 | |
1558 | // Trigger a rebuild. |
1559 | await tester.tap(find.text('Alarm')); |
1560 | await tester.pumpAndSettle(); |
1561 | |
1562 | // Icon color should be the same as before the rebuild. |
1563 | icon = tester.widget<Icon>(find.byType(Icon).last); |
1564 | expect(icon.color, initialColor); |
1565 | }); |
1566 | }); |
1567 | |
1568 | testWidgets('NavigationBar.labelPadding overrides NavigationDestination.label padding', ( |
1569 | WidgetTester tester, |
1570 | ) async { |
1571 | const String selectedText = 'Home'; |
1572 | const String unselectedText = 'Settings'; |
1573 | const EdgeInsetsGeometry labelPadding = EdgeInsets.all(8); |
1574 | Widget buildNavigationBar({EdgeInsetsGeometry? labelPadding}) { |
1575 | return MaterialApp( |
1576 | home: Scaffold( |
1577 | bottomNavigationBar: NavigationBar( |
1578 | labelPadding: labelPadding, |
1579 | destinations: const <Widget>[ |
1580 | NavigationDestination(icon: Icon(Icons.home), label: selectedText), |
1581 | NavigationDestination(icon: Icon(Icons.settings), label: unselectedText), |
1582 | ], |
1583 | onDestinationSelected: (int i) {}, |
1584 | ), |
1585 | ), |
1586 | ); |
1587 | } |
1588 | |
1589 | await tester.pumpWidget(buildNavigationBar()); |
1590 | expect(_getLabelPadding(tester, selectedText), const EdgeInsets.only(top: 4)); |
1591 | expect(_getLabelPadding(tester, unselectedText), const EdgeInsets.only(top: 4)); |
1592 | |
1593 | await tester.pumpWidget(buildNavigationBar(labelPadding: labelPadding)); |
1594 | expect(_getLabelPadding(tester, selectedText), labelPadding); |
1595 | expect(_getLabelPadding(tester, unselectedText), labelPadding); |
1596 | }); |
1597 | |
1598 | testWidgets('NavigationBar.labelTextStyle overrides NavigationDestination.label text style', ( |
1599 | WidgetTester tester, |
1600 | ) async { |
1601 | const String selectedText = 'Home'; |
1602 | const String unselectedText = 'Settings'; |
1603 | const String disabledText = 'Bookmark'; |
1604 | final ThemeData theme = ThemeData(); |
1605 | Widget buildNavigationBar({WidgetStateProperty<TextStyle?>? labelTextStyle}) { |
1606 | return MaterialApp( |
1607 | theme: theme, |
1608 | home: Scaffold( |
1609 | bottomNavigationBar: NavigationBar( |
1610 | labelTextStyle: labelTextStyle, |
1611 | destinations: const <Widget>[ |
1612 | NavigationDestination(icon: Icon(Icons.home), label: selectedText), |
1613 | NavigationDestination(icon: Icon(Icons.settings), label: unselectedText), |
1614 | NavigationDestination( |
1615 | enabled: false, |
1616 | icon: Icon(Icons.bookmark), |
1617 | label: disabledText, |
1618 | ), |
1619 | ], |
1620 | ), |
1621 | ), |
1622 | ); |
1623 | } |
1624 | |
1625 | await tester.pumpWidget(buildNavigationBar()); |
1626 | |
1627 | // Test selected label text style. |
1628 | expect(_getLabelStyle(tester, selectedText).fontSize, equals(12.0)); |
1629 | expect(_getLabelStyle(tester, selectedText).color, equals(theme.colorScheme.onSurface)); |
1630 | |
1631 | // Test unselected label text style. |
1632 | expect(_getLabelStyle(tester, unselectedText).fontSize, equals(12.0)); |
1633 | expect( |
1634 | _getLabelStyle(tester, unselectedText).color, |
1635 | equals(theme.colorScheme.onSurfaceVariant), |
1636 | ); |
1637 | |
1638 | // Test disabled label text style. |
1639 | expect(_getLabelStyle(tester, disabledText).fontSize, equals(12.0)); |
1640 | expect( |
1641 | _getLabelStyle(tester, disabledText).color, |
1642 | equals(theme.colorScheme.onSurfaceVariant.withOpacity(0.38)), |
1643 | ); |
1644 | |
1645 | const TextStyle selectedTextStyle = TextStyle(fontSize: 15, color: Color(0xFF00FF00)); |
1646 | const TextStyle unselectedTextStyle = TextStyle(fontSize: 15, color: Color(0xFF0000FF)); |
1647 | const TextStyle disabledTextStyle = TextStyle(fontSize: 16, color: Color(0xFFFF0000)); |
1648 | await tester.pumpWidget( |
1649 | buildNavigationBar( |
1650 | labelTextStyle: |
1651 | const WidgetStateProperty<TextStyle?>.fromMap(<WidgetStatesConstraint, TextStyle?>{ |
1652 | WidgetState.disabled: disabledTextStyle, |
1653 | WidgetState.selected: selectedTextStyle, |
1654 | WidgetState.any: unselectedTextStyle, |
1655 | }), |
1656 | ), |
1657 | ); |
1658 | |
1659 | // Test selected label text style. |
1660 | expect(_getLabelStyle(tester, selectedText).fontSize, equals(selectedTextStyle.fontSize)); |
1661 | expect(_getLabelStyle(tester, selectedText).color, equals(selectedTextStyle.color)); |
1662 | |
1663 | // Test unselected label text style. |
1664 | expect(_getLabelStyle(tester, unselectedText).fontSize, equals(unselectedTextStyle.fontSize)); |
1665 | expect(_getLabelStyle(tester, unselectedText).color, equals(unselectedTextStyle.color)); |
1666 | |
1667 | // Test disabled label text style. |
1668 | expect(_getLabelStyle(tester, disabledText).fontSize, equals(disabledTextStyle.fontSize)); |
1669 | expect(_getLabelStyle(tester, disabledText).color, equals(disabledTextStyle.color)); |
1670 | }); |
1671 | |
1672 | testWidgets('NavigationBar.maintainBottomViewPadding can consume bottom MediaQuery.padding', ( |
1673 | WidgetTester tester, |
1674 | ) async { |
1675 | const double bottomPadding = 40; |
1676 | const TextDirection textDirection = TextDirection.ltr; |
1677 | |
1678 | await tester.pumpWidget( |
1679 | MaterialApp( |
1680 | home: Directionality( |
1681 | textDirection: textDirection, |
1682 | child: MediaQuery( |
1683 | data: const MediaQueryData(padding: EdgeInsets.only(bottom: bottomPadding)), |
1684 | child: Scaffold( |
1685 | bottomNavigationBar: NavigationBar( |
1686 | maintainBottomViewPadding: true, |
1687 | destinations: const <Widget>[ |
1688 | NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), |
1689 | NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), |
1690 | ], |
1691 | ), |
1692 | ), |
1693 | ), |
1694 | ), |
1695 | ), |
1696 | ); |
1697 | |
1698 | final double safeAreaBottomPadding = |
1699 | tester.widget<Padding>(find.byType(Padding).first).padding.resolve(textDirection).bottom; |
1700 | expect(safeAreaBottomPadding, equals(0)); |
1701 | }); |
1702 | } |
1703 | |
1704 | Widget _buildWidget(Widget child, {bool? useMaterial3}) { |
1705 | return MaterialApp( |
1706 | theme: ThemeData(useMaterial3: useMaterial3), |
1707 | home: Scaffold(bottomNavigationBar: Center(child: child)), |
1708 | ); |
1709 | } |
1710 | |
1711 | Material _getMaterial(WidgetTester tester) { |
1712 | return tester.firstWidget<Material>( |
1713 | find.descendant(of: find.byType(NavigationBar), matching: find.byType(Material)), |
1714 | ); |
1715 | } |
1716 | |
1717 | ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { |
1718 | return tester |
1719 | .firstWidget<Ink>( |
1720 | find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), |
1721 | ) |
1722 | .decoration |
1723 | as ShapeDecoration?; |
1724 | } |
1725 | |
1726 | class IconWithRandomColor extends StatelessWidget { |
1727 | const IconWithRandomColor({super.key, required this.icon}); |
1728 | |
1729 | final IconData icon; |
1730 | |
1731 | @override |
1732 | Widget build(BuildContext context) { |
1733 | final Color randomColor = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0); |
1734 | return Icon(icon, color: randomColor); |
1735 | } |
1736 | } |
1737 | |
1738 | bool _sizeAlmostEqual(Size a, Size b, {double maxDiff = 0.05}) { |
1739 | return (a.width - b.width).abs() <= maxDiff && (a.height - b.height).abs() <= maxDiff; |
1740 | } |
1741 | |
1742 | EdgeInsetsGeometry _getLabelPadding(WidgetTester tester, String text) { |
1743 | return tester |
1744 | .widget<Padding>(find.ancestor(of: find.text(text), matching: find.byType(Padding)).first) |
1745 | .padding; |
1746 | } |
1747 | |
1748 | TextStyle _getLabelStyle(WidgetTester tester, String text) { |
1749 | return tester |
1750 | .widget<RichText>(find.descendant(of: find.text(text), matching: find.byType(RichText))) |
1751 | .text |
1752 | .style!; |
1753 | } |
1754 |
Definitions
- main
- navigationBar
- buildApp
- buildApp
- buildApp
- widget
- widget
- widget
- widget
- buildWidget
- buildWidget
- buildNavigationBar
- buildNavigationBar
- buildNavigationBar
- buildNavigationBar
- buildWidget
- buildWidget
- buildNavigationBar
- buildNavigationBar
- buildNavigationBar
- _buildWidget
- _getMaterial
- _getIndicatorDecoration
- IconWithRandomColor
- IconWithRandomColor
- build
- _sizeAlmostEqual
- _getLabelPadding
Learn more about Flutter for embedded and desktop on industrialflutter.com