bottom_tab_bar_test.dart 16.9 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 33
  testWidgets('Need at least 2 tabs', (WidgetTester tester) async {
    try {
34
      await pumpWidgetWithBoilerplate(tester, CupertinoTabBar(
35
        items: <BottomNavigationBarItem>[
36
          BottomNavigationBarItem(
37 38
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 1'),
xster's avatar
xster committed
39 40 41 42
          ),
        ],
      ));
      fail('Should not be possible to create a tab bar with just one item');
43 44
    } on AssertionError catch (e) {
      expect(e.toString(), contains('items.length'));
xster's avatar
xster committed
45 46 47 48 49
      // Exception expected.
    }
  });

  testWidgets('Active and inactive colors', (WidgetTester tester) async {
50
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
51
      data: const MediaQueryData(),
52
      child: CupertinoTabBar(
53
        items: <BottomNavigationBarItem>[
54
          BottomNavigationBarItem(
55 56
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 1'),
57
          ),
58
          BottomNavigationBarItem(
59 60
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 2'),
61 62 63 64 65 66
          ),
        ],
        currentIndex: 1,
        activeColor: const Color(0xFF123456),
        inactiveColor: const Color(0xFF654321),
      ),
xster's avatar
xster committed
67 68 69 70 71 72
    ));

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

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

82 83 84 85 86

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

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

105 106 107 108 109 110 111 112 113 114 115 116 117 118
  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(
119
        items: <BottomNavigationBarItem>[
120
          BottomNavigationBarItem(
121 122
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 1'),
123 124
          ),
          BottomNavigationBarItem(
125 126
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 2'),
127 128 129 130 131 132 133 134 135 136 137 138
          ),
        ],
        currentIndex: 1,
        activeColor: dynamicActiveColor,
        inactiveColor: dynamicInactiveColor,
      ),
    ));

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

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

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

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

    // Switch to dark mode.
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
        data: const MediaQueryData(platformBrightness: Brightness.dark),
        child: CupertinoTabBar(
160
          items: <BottomNavigationBarItem>[
161
            BottomNavigationBarItem(
162 163
              icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
              title: const Text('Tab 1'),
164 165
            ),
            BottomNavigationBarItem(
166 167
              icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
              title: const Text('Tab 2'),
168 169 170 171 172 173 174 175 176 177 178 179
            ),
          ],
          currentIndex: 1,
          activeColor: dynamicActiveColor,
          inactiveColor: dynamicInactiveColor,
        ),
    ));

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

    actualActive = tester.widget(find.descendant(
        of: find.text('Tab 2'),
        matching: find.byType(RichText),
    ));
186
    expect(actualActive.text.style!.color!.value, 0xFF000001);
187 188

    // Border color is resolved correctly.
189
    final BoxDecoration decoration2 = renderDecoratedBox.decoration as BoxDecoration;
190
    expect(decoration2.border!.top.color.value, 0x29000000);
191 192
  });

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

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

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

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

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

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

254
    expect(actualActive.text.style!.color, isSameColorAs(CupertinoColors.activeBlue.darkColor));
xster's avatar
xster committed
255 256
  });

257
  testWidgets('Use active icon', (WidgetTester tester) async {
258
    final MemoryImage activeIcon = MemoryImage(Uint8List.fromList(kBlueSquarePng));
259
    final MemoryImage inactiveIcon = MemoryImage(Uint8List.fromList(kTransparentImage));
260 261 262 263

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

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

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

290
  testWidgets('Adjusts height to account for bottom padding', (WidgetTester tester) async {
291
    final CupertinoTabBar tabBar = CupertinoTabBar(
292
      items: <BottomNavigationBarItem>[
293
        BottomNavigationBarItem(
294 295
          icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
          title: const Text('Aka'),
xster's avatar
xster committed
296
        ),
297
        BottomNavigationBarItem(
298 299
          icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
          title: const Text('Shiro'),
xster's avatar
xster committed
300 301
        ),
      ],
302 303 304
    );

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

    // Verify height with bottom padding.
317
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
318
      data: const MediaQueryData(padding: EdgeInsets.only(bottom: 40.0)),
319
      child: CupertinoTabScaffold(
320 321 322 323 324 325 326 327 328 329
        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 {
330
    await pumpWidgetWithBoilerplate(tester, MediaQuery(
331
      data: const MediaQueryData(),
332
      child: CupertinoTabBar(
333
        items: <BottomNavigationBarItem>[
334
          BottomNavigationBarItem(
335 336
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 1'),
337
          ),
338
          BottomNavigationBarItem(
339 340
            icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
            title: const Text('Tab 2'),
341 342 343
          ),
        ],
      ),
xster's avatar
xster committed
344 345 346 347
    ));

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

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

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

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

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

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

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

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

    expect(semantics, includesNodeWith(
      label: 'Tab 1',
414
      hint: 'Tab 1 of 2',
415 416 417 418 419 420
      flags: <SemanticsFlag>[SemanticsFlag.isSelected],
      textDirection: TextDirection.ltr,
    ));

    expect(semantics, includesNodeWith(
      label: 'Tab 2',
421
      hint: 'Tab 2 of 2',
422 423 424 425 426
      textDirection: TextDirection.ltr,
    ));

    semantics.dispose();
  });
427 428

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

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

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

    final Finder finder = find.byWidgetPredicate(
        (Widget widget) => widget is Image && widget.image == iconProvider);

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

463
  testWidgets('Hide border hides the top border of the tabBar', (WidgetTester tester) async {
464 465 466 467 468
    await pumpWidgetWithBoilerplate(
        tester,
        MediaQuery(
          data: const MediaQueryData(),
          child: CupertinoTabBar(
469
            items: <BottomNavigationBarItem>[
470 471
              BottomNavigationBarItem(
                icon: ImageIcon(
472
                  MemoryImage(Uint8List.fromList(kTransparentImage)),
473
                ),
474
                title: const Text('Tab 1'),
475 476 477
              ),
              BottomNavigationBarItem(
                icon: ImageIcon(
478
                  MemoryImage(Uint8List.fromList(kTransparentImage)),
479
                ),
480
                title: const Text('Tab 2'),
481 482 483 484 485 486
              ),
            ],
          ),
        ));

    final DecoratedBox decoratedBox = tester.widget(find.byType(DecoratedBox));
487
    final BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration;
488 489 490 491 492 493 494
    expect(boxDecoration.border, isNotNull);

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

    final DecoratedBox decoratedBoxHiddenBorder =
        tester.widget(find.byType(DecoratedBox));
    final BoxDecoration boxDecorationHiddenBorder =
517
        decoratedBoxHiddenBorder.decoration as BoxDecoration;
518 519
    expect(boxDecorationHiddenBorder.border, isNull);
  });
520
}