bottom_tab_bar_test.dart 16.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
xster's avatar
xster committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:typed_data';

xster's avatar
xster committed
7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/rendering.dart';
xster's avatar
xster committed
9 10
import 'package:flutter_test/flutter_test.dart';

11
import '../image_data.dart';
12
import '../widgets/semantics_tester.dart';
xster's avatar
xster committed
13

14
Future<void> pumpWidgetWithBoilerplate(WidgetTester tester, Widget widget) async {
15
  await tester.pumpWidget(
16 17
    Localizations(
      locale: const Locale('en', 'US'),
18
      delegates: const <LocalizationsDelegate<dynamic>>[
19 20 21 22 23 24 25
        DefaultWidgetsLocalizations.delegate,
        DefaultCupertinoLocalizations.delegate,
      ],
      child: Directionality(
        textDirection: TextDirection.ltr,
        child: widget,
      ),
26 27 28 29
    ),
  );
}

30 31
Future<void> main() async {

xster's avatar
xster committed
32
  testWidgets('Need at least 2 tabs', (WidgetTester tester) async {
33 34
    await expectLater(
      () => pumpWidgetWithBoilerplate(tester, CupertinoTabBar(
35
        items: <BottomNavigationBarItem>[
36
          BottomNavigationBarItem(
37
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
38
            label: 'Tab 1',
xster's avatar
xster committed
39 40
          ),
        ],
41 42 43 44 45 46 47
      )),
      throwsA(isA<AssertionError>().having(
        (AssertionError error) => error.toString(),
        '.toString()',
        contains('items.length'),
      )),
    );
xster's avatar
xster committed
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 65 66 67
          ),
        ],
        currentIndex: 1,
        activeColor: const Color(0xFF123456),
        inactiveColor: const Color(0xFF654321),
      ),
xster's avatar
xster committed
68 69 70 71 72 73
    ));

    final RichText actualInactive = tester.widget(find.descendant(
      of: find.text('Tab 1'),
      matching: find.byType(RichText),
    ));
74
    expect(actualInactive.text.style!.color, const Color(0xFF654321));
xster's avatar
xster committed
75 76 77 78 79

    final RichText actualActive = tester.widget(find.descendant(
      of: find.text('Tab 2'),
      matching: find.byType(RichText),
    ));
80
    expect(actualActive.text.style!.color, const Color(0xFF123456));
xster's avatar
xster committed
81 82
  });

83 84 85 86 87

  testWidgets('BottomNavigationBar.label will create a text widget', (WidgetTester tester) async {
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
      data: const MediaQueryData(),
      child: CupertinoTabBar(
88
        items: <BottomNavigationBarItem>[
89
          BottomNavigationBarItem(
90
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
91 92 93
            label: 'Tab 1',
          ),
          BottomNavigationBarItem(
94
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
95 96 97 98 99 100 101 102 103 104 105
            label: 'Tab 2',
          ),
        ],
        currentIndex: 1,
      ),
    ));

    expect(find.text('Tab 1'), findsOneWidget);
    expect(find.text('Tab 2'), findsOneWidget);
  });

106 107 108 109 110 111 112 113 114 115 116 117 118 119
  testWidgets('Active and inactive colors dark mode', (WidgetTester tester) async {
    const CupertinoDynamicColor dynamicActiveColor = CupertinoDynamicColor.withBrightness(
      color: Color(0xFF000000),
      darkColor: Color(0xFF000001),
    );

    const CupertinoDynamicColor dynamicInactiveColor = CupertinoDynamicColor.withBrightness(
      color: Color(0xFF000002),
      darkColor: Color(0xFF000003),
    );

    await pumpWidgetWithBoilerplate(tester, MediaQuery(
      data: const MediaQueryData(),
      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 131 132 133 134 135 136 137 138 139
          ),
        ],
        currentIndex: 1,
        activeColor: dynamicActiveColor,
        inactiveColor: dynamicInactiveColor,
      ),
    ));

    RichText actualInactive = tester.widget(find.descendant(
      of: find.text('Tab 1'),
      matching: find.byType(RichText),
    ));
140
    expect(actualInactive.text.style!.color!.value, 0xFF000002);
141 142 143 144 145

    RichText actualActive = tester.widget(find.descendant(
      of: find.text('Tab 2'),
      matching: find.byType(RichText),
    ));
146
    expect(actualActive.text.style!.color!.value, 0xFF000000);
147 148 149 150 151 152 153

    final RenderDecoratedBox renderDecoratedBox = tester.renderObject(find.descendant(
      of: find.byType(BackdropFilter),
      matching: find.byType(DecoratedBox),
    ));

    // Border color is resolved correctly.
154
    final BoxDecoration decoration1 = renderDecoratedBox.decoration as BoxDecoration;
155
    expect(decoration1.border!.top.color.value, 0x4C000000);
156 157 158 159 160

    // Switch to dark mode.
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
        data: const MediaQueryData(platformBrightness: Brightness.dark),
        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 172 173 174 175 176 177 178 179 180
            ),
          ],
          currentIndex: 1,
          activeColor: dynamicActiveColor,
          inactiveColor: dynamicInactiveColor,
        ),
    ));

    actualInactive = tester.widget(find.descendant(
        of: find.text('Tab 1'),
        matching: find.byType(RichText),
    ));
181
    expect(actualInactive.text.style!.color!.value, 0xFF000003);
182 183 184 185 186

    actualActive = tester.widget(find.descendant(
        of: find.text('Tab 2'),
        matching: find.byType(RichText),
    ));
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
  });

xster's avatar
xster committed
194 195 196 197
  testWidgets('Tabs respects themes', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoTabBar(
198
          items: <BottomNavigationBarItem>[
xster's avatar
xster committed
199
            BottomNavigationBarItem(
200
              icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
201
              label: 'Tab 1',
xster's avatar
xster committed
202 203
            ),
            BottomNavigationBarItem(
204
              icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
205
              label: 'Tab 2',
xster's avatar
xster committed
206 207 208 209 210 211 212 213 214 215 216
            ),
          ],
          currentIndex: 1,
        ),
      ),
    );

    RichText actualInactive = tester.widget(find.descendant(
      of: find.text('Tab 1'),
      matching: find.byType(RichText),
    ));
217
    expect(actualInactive.text.style!.color!.value, 0xFF999999);
xster's avatar
xster committed
218 219 220 221 222

    RichText actualActive = tester.widget(find.descendant(
      of: find.text('Tab 2'),
      matching: find.byType(RichText),
    ));
223
    expect(actualActive.text.style!.color, CupertinoColors.activeBlue);
xster's avatar
xster committed
224 225 226 227 228

    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.dark),
        home: CupertinoTabBar(
229
          items: <BottomNavigationBarItem>[
xster's avatar
xster committed
230
            BottomNavigationBarItem(
231
              icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
232
              label: 'Tab 1',
xster's avatar
xster committed
233 234
            ),
            BottomNavigationBarItem(
235
              icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
236
              label: 'Tab 2',
xster's avatar
xster committed
237 238 239 240 241 242 243 244 245 246 247
            ),
          ],
          currentIndex: 1,
        ),
      ),
    );

    actualInactive = tester.widget(find.descendant(
      of: find.text('Tab 1'),
      matching: find.byType(RichText),
    ));
248
    expect(actualInactive.text.style!.color!.value, 0xFF757575);
xster's avatar
xster committed
249 250 251 252 253 254

    actualActive = tester.widget(find.descendant(
      of: find.text('Tab 2'),
      matching: find.byType(RichText),
    ));

255
    expect(actualActive.text.style!.color, isSameColorAs(CupertinoColors.activeBlue.darkColor));
xster's avatar
xster committed
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 263 264

    await pumpWidgetWithBoilerplate(tester, MediaQuery(
      data: const MediaQueryData(),
      child: CupertinoTabBar(
265
        items: <BottomNavigationBarItem>[
266
          BottomNavigationBarItem(
267
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
268
            label: 'Tab 1',
269 270 271 272
          ),
          BottomNavigationBarItem(
            icon: ImageIcon(inactiveIcon),
            activeIcon: ImageIcon(activeIcon),
273
            label: 'Tab 2',
274 275 276 277 278 279 280 281 282 283
          ),
        ],
        currentIndex: 1,
        activeColor: const Color(0xFF123456),
        inactiveColor: const Color(0xFF654321),
      ),
    ));

    final Image image = tester.widget(find.descendant(
      of: find.widgetWithText(GestureDetector, 'Tab 2'),
284
      matching: find.byType(Image),
285 286 287 288 289 290
    ));

    expect(image.color, const Color(0xFF123456));
    expect(image.image, activeIcon);
  });

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',
xster's avatar
xster committed
297
        ),
298
        BottomNavigationBarItem(
299
          icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
300
          label: 'Shiro',
xster's avatar
xster committed
301 302
        ),
      ],
303 304 305
    );

    // Verify height with no bottom padding.
306
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
307
      data: const MediaQueryData(),
308
      child: CupertinoTabScaffold(
309 310 311 312 313 314 315 316 317
        tabBar: tabBar,
        tabBuilder: (BuildContext context, int index) {
          return const Placeholder();
        },
      ),
    ));
    expect(tester.getSize(find.byType(CupertinoTabBar)).height, 50.0);

    // Verify height with bottom padding.
318
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
319
      data: const MediaQueryData(padding: EdgeInsets.only(bottom: 40.0)),
320
      child: CupertinoTabScaffold(
321 322 323 324 325 326 327 328 329 330
        tabBar: tabBar,
        tabBuilder: (BuildContext context, int index) {
          return const Placeholder();
        },
      ),
    ));
    expect(tester.getSize(find.byType(CupertinoTabBar)).height, 90.0);
  });

  testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async {
331
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
332
      data: const MediaQueryData(),
333
      child: CupertinoTabBar(
334
        items: <BottomNavigationBarItem>[
335
          BottomNavigationBarItem(
336
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
337
            label: 'Tab 1',
338
          ),
339
          BottomNavigationBarItem(
340
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
341
            label: 'Tab 2',
342 343 344
          ),
        ],
      ),
xster's avatar
xster committed
345 346 347 348
    ));

    expect(find.byType(BackdropFilter), findsOneWidget);

349
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
350
      data: const MediaQueryData(),
351
      child: CupertinoTabBar(
352
        items: <BottomNavigationBarItem>[
353
          BottomNavigationBarItem(
354
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
355
            label: 'Tab 1',
356
          ),
357
          BottomNavigationBarItem(
358
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
359
            label: 'Tab 2',
360 361 362 363
          ),
        ],
        backgroundColor: const Color(0xFFFFFFFF), // Opaque white.
      ),
xster's avatar
xster committed
364 365 366 367 368 369
    ));

    expect(find.byType(BackdropFilter), findsNothing);
  });

  testWidgets('Tap callback', (WidgetTester tester) async {
370
    late int callbackTab;
xster's avatar
xster committed
371

372 373 374
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
      data: const MediaQueryData(),
      child: CupertinoTabBar(
375
        items: <BottomNavigationBarItem>[
376
          BottomNavigationBarItem(
377
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
378
            label: 'Tab 1',
379 380
          ),
          BottomNavigationBarItem(
381
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
382
            label: 'Tab 2',
383 384 385 386 387
          ),
        ],
        currentIndex: 1,
        onTap: (int tab) { callbackTab = tab; },
      ),
xster's avatar
xster committed
388 389 390 391
    ));

    await tester.tap(find.text('Tab 1'));
    expect(callbackTab, 0);
392 393 394

    await tester.tap(find.text('Tab 2'));
    expect(callbackTab, 1);
xster's avatar
xster committed
395
  });
396 397

  testWidgets('tabs announce semantics', (WidgetTester tester) async {
398
    final SemanticsTester semantics = SemanticsTester(tester);
399

400
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
401
      data: const MediaQueryData(),
402
      child: CupertinoTabBar(
403
        items: <BottomNavigationBarItem>[
404
          BottomNavigationBarItem(
405
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
406
            label: 'Tab 1',
407
          ),
408
          BottomNavigationBarItem(
409
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
410
            label: 'Tab 2',
411 412 413 414 415 416 417
          ),
        ],
      ),
    ));

    expect(semantics, includesNodeWith(
      label: 'Tab 1',
418
      hint: 'Tab 1 of 2',
419 420 421 422 423 424
      flags: <SemanticsFlag>[SemanticsFlag.isSelected],
      textDirection: TextDirection.ltr,
    ));

    expect(semantics, includesNodeWith(
      label: 'Tab 2',
425
      hint: 'Tab 2 of 2',
426 427 428 429 430
      textDirection: TextDirection.ltr,
    ));

    semantics.dispose();
  });
431 432

  testWidgets('Title of items should be nullable', (WidgetTester tester) async {
433
    final MemoryImage iconProvider = MemoryImage(Uint8List.fromList(kTransparentImage));
434 435 436
    final List<int> itemsTapped = <int>[];

    await pumpWidgetWithBoilerplate(
437 438 439 440 441 442 443 444
      tester,
      MediaQuery(
        data: const MediaQueryData(),
        child: CupertinoTabBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: ImageIcon(
                MemoryImage(Uint8List.fromList(kTransparentImage)),
445
              ),
446 447 448 449 450
              label: 'Tab 1',
            ),
            BottomNavigationBarItem(
              icon: ImageIcon(
                iconProvider,
451
              ),
452 453 454 455 456 457
            ),
          ],
          onTap: (int index) => itemsTapped.add(index),
        ),
      ),
    );
458 459 460 461

    expect(find.text('Tab 1'), findsOneWidget);

    final Finder finder = find.byWidgetPredicate(
462 463
      (Widget widget) => widget is Image && widget.image == iconProvider,
    );
464 465 466 467 468

    await tester.tap(finder);
    expect(itemsTapped, <int>[1]);
  });

469
  testWidgets('Hide border hides the top border of the tabBar', (WidgetTester tester) async {
470
    await pumpWidgetWithBoilerplate(
471 472 473 474 475 476 477 478
      tester,
      MediaQuery(
        data: const MediaQueryData(),
        child: CupertinoTabBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: ImageIcon(
                MemoryImage(Uint8List.fromList(kTransparentImage)),
479
              ),
480 481 482 483 484
              label: 'Tab 1',
            ),
            BottomNavigationBarItem(
              icon: ImageIcon(
                MemoryImage(Uint8List.fromList(kTransparentImage)),
485
              ),
486 487 488 489 490 491
              label: 'Tab 2',
            ),
          ],
        ),
      ),
    );
492 493

    final DecoratedBox decoratedBox = tester.widget(find.byType(DecoratedBox));
494
    final BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration;
495 496 497
    expect(boxDecoration.border, isNotNull);

    await pumpWidgetWithBoilerplate(
498 499 500 501 502 503 504 505
      tester,
      MediaQuery(
        data: const MediaQueryData(),
        child: CupertinoTabBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: ImageIcon(
                MemoryImage(Uint8List.fromList(kTransparentImage)),
506
              ),
507 508 509 510 511
              label: 'Tab 1',
            ),
            BottomNavigationBarItem(
              icon: ImageIcon(
                MemoryImage(Uint8List.fromList(kTransparentImage)),
512
              ),
513 514 515 516 517 518 519 520
              label: 'Tab 2',
            ),
          ],
          backgroundColor: const Color(0xFFFFFFFF), // Opaque white.
          border: null,
        ),
      ),
    );
521 522 523 524

    final DecoratedBox decoratedBoxHiddenBorder =
        tester.widget(find.byType(DecoratedBox));
    final BoxDecoration boxDecorationHiddenBorder =
525
        decoratedBoxHiddenBorder.decoration as BoxDecoration;
526 527
    expect(boxDecorationHiddenBorder.border, isNull);
  });
528
}