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

import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
7
import 'package:flutter/services.dart';
8
import 'package:flutter_test/flutter_test.dart';
9

10
import '../rendering/mock_canvas.dart';
11 12
import '../widgets/semantics_tester.dart';

13 14
int count = 0;

15 16 17
void main() {
  testWidgets('Middle still in center with asymmetrical actions', (WidgetTester tester) async {
    await tester.pumpWidget(
18 19
      const CupertinoApp(
        home: CupertinoNavigationBar(
20 21
          leading: CupertinoButton(child: Text('Something'), onPressed: null,),
          middle: Text('Title'),
xster's avatar
xster committed
22
        ),
23 24 25 26 27 28 29
      ),
    );

    // Expect the middle of the title to be exactly in the middle of the screen.
    expect(tester.getCenter(find.text('Title')).dx, 400.0);
  });

30 31
  testWidgets('Middle still in center with back button', (WidgetTester tester) async {
    await tester.pumpWidget(
32 33
      const CupertinoApp(
        home: CupertinoNavigationBar(
34
          middle: Text('Title'),
35 36 37 38
        ),
      ),
    );

39
    tester.state<NavigatorState>(find.byType(Navigator)).push(CupertinoPageRoute<void>(
40 41
      builder: (BuildContext context) {
        return const CupertinoNavigationBar(
42
          middle: Text('Page 2'),
43 44 45 46 47 48 49 50 51 52 53
        );
      },
    ));

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 500));

    // Expect the middle of the title to be exactly in the middle of the screen.
    expect(tester.getCenter(find.text('Page 2')).dx, 400.0);
  });

54 55 56 57 58 59
  testWidgets('Opaque background does not add blur effects, non-opaque background adds blur effects', (WidgetTester tester) async {
    const CupertinoDynamicColor background = CupertinoDynamicColor.withBrightness(
      color: Color(0xFFE5E5E5),
      darkColor: Color(0xF3E5E5E5),
    );

60
    await tester.pumpWidget(
61
      const CupertinoApp(
62
        theme: CupertinoThemeData(brightness: Brightness.light),
63
        home: CupertinoNavigationBar(
64
          middle: Text('Title'),
65
          backgroundColor: background,
xster's avatar
xster committed
66
        ),
67 68 69
      ),
    );
    expect(find.byType(BackdropFilter), findsNothing);
70 71 72 73 74 75 76 77 78 79 80 81 82
    expect(find.byType(CupertinoNavigationBar), paints..rect(color: background.color));

    await tester.pumpWidget(
      const CupertinoApp(
        theme: CupertinoThemeData(brightness: Brightness.dark),
        home: CupertinoNavigationBar(
          middle: Text('Title'),
          backgroundColor: background,
        ),
      ),
    );
    expect(find.byType(BackdropFilter), findsOneWidget);
    expect(find.byType(CupertinoNavigationBar), paints..rect(color: background.darkColor));
83 84 85 86
  });

  testWidgets('Non-opaque background adds blur effects', (WidgetTester tester) async {
    await tester.pumpWidget(
87 88
      const CupertinoApp(
        home: CupertinoNavigationBar(
89
          middle: Text('Title'),
xster's avatar
xster committed
90
        ),
91 92 93 94
      ),
    );
    expect(find.byType(BackdropFilter), findsOneWidget);
  });
95

96
  testWidgets('Can specify custom padding', (WidgetTester tester) async {
97
    final Key middleBox = GlobalKey();
98
    await tester.pumpWidget(
99 100
      CupertinoApp(
        home: Align(
101
          alignment: Alignment.topCenter,
102
          child: CupertinoNavigationBar(
103
            leading: const CupertinoButton(child: Text('Cheetah'), onPressed: null),
104 105
            // Let the box take all the vertical space to test vertical padding but let
            // the nav bar position it horizontally.
106
            middle: Align(
107 108 109
              key: middleBox,
              alignment: Alignment.center,
              widthFactor: 1.0,
110
              child: const Text('Title'),
111
            ),
112
            trailing: const CupertinoButton(child: Text('Puma'), onPressed: null),
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
            padding: const EdgeInsetsDirectional.only(
              start: 10.0,
              end: 20.0,
              top: 3.0,
              bottom: 4.0,
            ),
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byKey(middleBox)).top, 3.0);
    // 44 is the standard height of the nav bar.
    expect(
      tester.getRect(find.byKey(middleBox)).bottom,
      // 44 is the standard height of the nav bar.
      44.0 - 4.0,
    );

    expect(tester.getTopLeft(find.widgetWithText(CupertinoButton, 'Cheetah')).dx, 10.0);
    expect(tester.getTopRight(find.widgetWithText(CupertinoButton, 'Puma')).dx, 800.0 - 20.0);

    // Title is still exactly centered.
    expect(tester.getCenter(find.text('Title')).dx, 400.0);
  });

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
  testWidgets('Can specify custom brightness', (WidgetTester tester) async {
    await tester.pumpWidget(
      const CupertinoApp(
        home: CupertinoNavigationBar(
          backgroundColor: Color(0xF0F9F9F9),
          brightness: Brightness.dark,
        ),
      ),
    );

    final AnnotatedRegion<SystemUiOverlayStyle> region1 = tester.allWidgets
        .whereType<AnnotatedRegion<SystemUiOverlayStyle>>()
        .single;
    expect(region1.value, SystemUiOverlayStyle.light);

    await tester.pumpWidget(
      const CupertinoApp(
        home: CupertinoNavigationBar(
          backgroundColor: Color(0xF01D1D1D),
          brightness: Brightness.light,
        ),
      ),
    );

    final AnnotatedRegion<SystemUiOverlayStyle> region2 = tester.allWidgets
        .whereType<AnnotatedRegion<SystemUiOverlayStyle>>()
        .single;
    expect(region2.value, SystemUiOverlayStyle.dark);

    await tester.pumpWidget(
      const CupertinoApp(
        home: CustomScrollView(
          slivers: <Widget>[
            CupertinoSliverNavigationBar(
              largeTitle: Text('Title'),
              backgroundColor: Color(0xF0F9F9F9),
              brightness: Brightness.dark,
            )
          ],
        ),
      ),
    );

    final AnnotatedRegion<SystemUiOverlayStyle> region3 = tester.allWidgets
        .whereType<AnnotatedRegion<SystemUiOverlayStyle>>()
        .single;
    expect(region3.value, SystemUiOverlayStyle.light);

    await tester.pumpWidget(
      const CupertinoApp(
        home: CustomScrollView(
          slivers: <Widget>[
            CupertinoSliverNavigationBar(
              largeTitle: Text('Title'),
              backgroundColor: Color(0xF01D1D1D),
              brightness: Brightness.light,
            )
          ],
        ),
      ),
    );

    final AnnotatedRegion<SystemUiOverlayStyle> region4 = tester.allWidgets
        .whereType<AnnotatedRegion<SystemUiOverlayStyle>>()
        .single;
    expect(region4.value, SystemUiOverlayStyle.dark);
  });

207 208
  testWidgets('Padding works in RTL', (WidgetTester tester) async {
    await tester.pumpWidget(
209 210
      const CupertinoApp(
        home: Directionality(
211
          textDirection: TextDirection.rtl,
212
          child: Align(
213
            alignment: Alignment.topCenter,
214 215
            child: CupertinoNavigationBar(
              leading: CupertinoButton(child: Text('Cheetah'), onPressed: null),
216 217
              // Let the box take all the vertical space to test vertical padding but let
              // the nav bar position it horizontally.
218 219 220
              middle: Text('Title'),
              trailing: CupertinoButton(child: Text('Puma'), onPressed: null),
              padding: EdgeInsetsDirectional.only(
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
                start: 10.0,
                end: 20.0,
              ),
            ),
          ),
        ),
      ),
    );

    expect(tester.getTopRight(find.widgetWithText(CupertinoButton, 'Cheetah')).dx, 800.0 - 10.0);
    expect(tester.getTopLeft(find.widgetWithText(CupertinoButton, 'Puma')).dx, 20.0);

    // Title is still exactly centered.
    expect(tester.getCenter(find.text('Title')).dx, 400.0);
  });

xster's avatar
xster committed
237
  testWidgets('Nav bar uses theme defaults', (WidgetTester tester) async {
238 239
    count = 0x000000;
    await tester.pumpWidget(
xster's avatar
xster committed
240 241 242
      CupertinoApp(
        home: CupertinoNavigationBar(
          leading: CupertinoButton(
243
            onPressed: () { },
244
            child: _ExpectStyles(color: CupertinoColors.systemBlue.color, index: 0x000001),
xster's avatar
xster committed
245 246 247
          ),
          middle: const _ExpectStyles(color: CupertinoColors.black, index: 0x000100),
          trailing: CupertinoButton(
248
            onPressed: () { },
249
            child: _ExpectStyles(color: CupertinoColors.systemBlue.color, index: 0x010000),
xster's avatar
xster committed
250 251 252 253 254 255 256 257 258 259 260 261
          ),
        ),
      ),
    );
    expect(count, 0x010101);
  });

  testWidgets('Nav bar respects themes', (WidgetTester tester) async {
    count = 0x000000;
    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.dark),
262
        home: CupertinoNavigationBar(
xster's avatar
xster committed
263
          leading: CupertinoButton(
264
            onPressed: () { },
265
            child: _ExpectStyles(color: CupertinoColors.systemBlue.darkColor, index: 0x000001),
xster's avatar
xster committed
266 267 268
          ),
          middle: const _ExpectStyles(color: CupertinoColors.white, index: 0x000100),
          trailing: CupertinoButton(
269
            onPressed: () { },
270
            child: _ExpectStyles(color: CupertinoColors.systemBlue.darkColor, index: 0x010000),
xster's avatar
xster committed
271 272 273 274 275 276 277
          ),
        ),
      ),
    );
    expect(count, 0x010101);
  });

278
  testWidgets('Theme active color can be overridden', (WidgetTester tester) async {
xster's avatar
xster committed
279 280 281 282 283
    count = 0x000000;
    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoNavigationBar(
          leading: CupertinoButton(
284
            onPressed: () { },
xster's avatar
xster committed
285 286 287 288
            child: const _ExpectStyles(color: Color(0xFF001122), index: 0x000001),
          ),
          middle: const _ExpectStyles(color: Color(0xFF000000), index: 0x000100),
          trailing: CupertinoButton(
289
            onPressed: () { },
xster's avatar
xster committed
290 291 292
            child: const _ExpectStyles(color: Color(0xFF001122), index: 0x010000),
          ),
          actionsForegroundColor: const Color(0xFF001122),
xster's avatar
xster committed
293
        ),
294 295 296 297
      ),
    );
    expect(count, 0x010101);
  });
298 299 300

  testWidgets('No slivers with no large titles', (WidgetTester tester) async {
    await tester.pumpWidget(
301 302
      const CupertinoApp(
        home: CupertinoPageScaffold(
303 304
          navigationBar: CupertinoNavigationBar(
            middle: Text('Title'),
xster's avatar
xster committed
305
          ),
306
          child: Center(),
xster's avatar
xster committed
307
        ),
308 309 310 311 312 313
      ),
    );

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

314
  testWidgets('Media padding is applied to CupertinoSliverNavigationBar', (WidgetTester tester) async {
315 316 317 318 319
    final ScrollController scrollController = ScrollController();
    final Key leadingKey = GlobalKey();
    final Key middleKey = GlobalKey();
    final Key trailingKey = GlobalKey();
    final Key titleKey = GlobalKey();
320
    await tester.pumpWidget(
321 322
      CupertinoApp(
        home: MediaQuery(
xster's avatar
xster committed
323
          data: const MediaQueryData(
324
            padding: EdgeInsets.only(
xster's avatar
xster committed
325 326 327 328 329 330
              top: 10.0,
              left: 20.0,
              bottom: 30.0,
              right: 40.0,
            ),
          ),
331 332
          child: CupertinoPageScaffold(
            child: CustomScrollView(
xster's avatar
xster committed
333 334
              controller: scrollController,
              slivers: <Widget>[
335 336 337 338 339
                CupertinoSliverNavigationBar(
                  leading: Placeholder(key: leadingKey),
                  middle: Placeholder(key: middleKey),
                  largeTitle: Text('Large Title', key: titleKey),
                  trailing: Placeholder(key: trailingKey),
340
                ),
341 342
                SliverToBoxAdapter(
                  child: Container(
xster's avatar
xster committed
343
                    height: 1200.0,
344 345
                  ),
                ),
xster's avatar
xster committed
346 347 348 349
              ],
            ),
          ),
        ),
350 351 352
      ),
    );

353 354 355 356
    // Media padding applied to leading (T,L), middle (T), trailing (T, R).
    expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(16.0 + 20.0, 10.0));
    expect(tester.getRect(find.byKey(middleKey)).top, 10.0);
    expect(tester.getTopRight(find.byKey(trailingKey)), const Offset(800.0 - 16.0 - 40.0, 10.0));
357 358

    // Top and left padding is applied to large title.
359
    expect(tester.getTopLeft(find.byKey(titleKey)), const Offset(16.0 + 20.0, 54.0 + 10.0));
360 361
  });

362
  testWidgets('Large title nav bar scrolls', (WidgetTester tester) async {
363
    final ScrollController scrollController = ScrollController();
364
    await tester.pumpWidget(
365 366 367
      CupertinoApp(
        home: CupertinoPageScaffold(
          child: CustomScrollView(
xster's avatar
xster committed
368 369 370
            controller: scrollController,
            slivers: <Widget>[
              const CupertinoSliverNavigationBar(
371
                largeTitle: Text('Title'),
xster's avatar
xster committed
372
              ),
373 374
              SliverToBoxAdapter(
                child: Container(
xster's avatar
xster committed
375
                  height: 1200.0,
376
                ),
xster's avatar
xster committed
377 378 379 380
              ),
            ],
          ),
        ),
381 382 383 384 385 386 387 388 389 390 391 392
      ),
    );

    expect(scrollController.offset, 0.0);
    expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
    expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);

    expect(find.text('Title'), findsNWidgets(2)); // Though only one is visible.

    List<Element> titles = tester.elementList(find.text('Title'))
        .toList()
        ..sort((Element a, Element b) {
393 394
          final RenderParagraph aParagraph = a.renderObject! as RenderParagraph;
          final RenderParagraph bParagraph = b.renderObject! as RenderParagraph;
395
          return aParagraph.text.style!.fontSize!.compareTo(bParagraph.text.style!.fontSize!);
396 397
        });

398
    Iterable<double> opacities = titles.map<double>((Element element) {
399
      final RenderAnimatedOpacity renderOpacity = element.findAncestorRenderObjectOfType<RenderAnimatedOpacity>()!;
400
      return renderOpacity.opacity.value;
401 402 403
    });

    expect(opacities, <double> [
404 405
      0.0, // Initially the smaller font title is invisible.
      1.0, // The larger font title is visible.
406 407 408
    ]);

    expect(tester.getTopLeft(find.widgetWithText(OverflowBox, 'Title')).dy, 44.0);
409
    expect(tester.getSize(find.widgetWithText(OverflowBox, 'Title')).height, 52.0);
410 411 412 413 414 415 416 417

    scrollController.jumpTo(600.0);
    await tester.pump(); // Once to trigger the opacity animation.
    await tester.pump(const Duration(milliseconds: 300));

    titles = tester.elementList(find.text('Title'))
        .toList()
        ..sort((Element a, Element b) {
418 419
          final RenderParagraph aParagraph = a.renderObject! as RenderParagraph;
          final RenderParagraph bParagraph = b.renderObject! as RenderParagraph;
420
          return aParagraph.text.style!.fontSize!.compareTo(bParagraph.text.style!.fontSize!);
421 422
        });

423
    opacities = titles.map<double>((Element element) {
424
      final RenderAnimatedOpacity renderOpacity = element.findAncestorRenderObjectOfType<RenderAnimatedOpacity>()!;
425
      return renderOpacity.opacity.value;
426 427 428
    });

    expect(opacities, <double> [
429 430
      1.0, // Smaller font title now visible
      0.0, // Larger font title invisible.
431 432 433 434 435 436 437 438 439 440
    ]);

    // The persistent toolbar doesn't move or change size.
    expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
    expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);

    expect(tester.getTopLeft(find.widgetWithText(OverflowBox, 'Title')).dy, 44.0);
    // The OverflowBox is squished with the text in it.
    expect(tester.getSize(find.widgetWithText(OverflowBox, 'Title')).height, 0.0);
  });
441

442
  testWidgets('User specified middle is always visible in sliver', (WidgetTester tester) async {
443 444
    final ScrollController scrollController = ScrollController();
    final Key segmentedControlsKey = UniqueKey();
445
    await tester.pumpWidget(
446 447 448
      CupertinoApp(
        home: CupertinoPageScaffold(
          child: CustomScrollView(
449 450
            controller: scrollController,
            slivers: <Widget>[
451 452
              CupertinoSliverNavigationBar(
                middle: ConstrainedBox(
453
                  constraints: const BoxConstraints(maxWidth: 200.0),
454
                  child: CupertinoSegmentedControl<int>(
455 456 457 458 459 460 461 462 463 464 465
                    key: segmentedControlsKey,
                    children: const <int, Widget>{
                      0: Text('Option A'),
                      1: Text('Option B'),
                    },
                    onValueChanged: (int selected) { },
                    groupValue: 0,
                  ),
                ),
                largeTitle: const Text('Title'),
              ),
466 467
              SliverToBoxAdapter(
                child: Container(
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
                  height: 1200.0,
                ),
              ),
            ],
          ),
        ),
      ),
    );

    expect(scrollController.offset, 0.0);
    expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
    expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);

    expect(find.text('Title'), findsOneWidget);
    expect(tester.getCenter(find.byKey(segmentedControlsKey)).dx, 400.0);

    expect(tester.getTopLeft(find.widgetWithText(OverflowBox, 'Title')).dy, 44.0);
    expect(tester.getSize(find.widgetWithText(OverflowBox, 'Title')).height, 52.0);

    scrollController.jumpTo(600.0);
    await tester.pump(); // Once to trigger the opacity animation.
    await tester.pump(const Duration(milliseconds: 300));

    expect(tester.getCenter(find.byKey(segmentedControlsKey)).dx, 400.0);
    // The large title is invisible now.
    expect(
      tester.renderObject<RenderAnimatedOpacity>(
        find.widgetWithText(AnimatedOpacity, 'Title')
      ).opacity.value,
      0.0,
    );
  });

Josh Soref's avatar
Josh Soref committed
501
  testWidgets('Small title can be overridden', (WidgetTester tester) async {
502
    final ScrollController scrollController = ScrollController();
503
    await tester.pumpWidget(
504 505 506
      CupertinoApp(
        home: CupertinoPageScaffold(
          child: CustomScrollView(
xster's avatar
xster committed
507 508 509
            controller: scrollController,
            slivers: <Widget>[
              const CupertinoSliverNavigationBar(
510 511
                middle: Text('Different title'),
                largeTitle: Text('Title'),
xster's avatar
xster committed
512
              ),
513 514
              SliverToBoxAdapter(
                child: Container(
xster's avatar
xster committed
515
                  height: 1200.0,
516
                ),
xster's avatar
xster committed
517 518 519 520
              ),
            ],
          ),
        ),
521 522 523 524 525 526 527 528 529 530
      ),
    );

    expect(scrollController.offset, 0.0);
    expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
    expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);

    expect(find.text('Title'), findsOneWidget);
    expect(find.text('Different title'), findsOneWidget);

531
    RenderAnimatedOpacity largeTitleOpacity =
532
        tester.element(find.text('Title')).findAncestorRenderObjectOfType<RenderAnimatedOpacity>()!;
533 534
    // Large title initially visible.
    expect(
535
      largeTitleOpacity.opacity.value,
536
      1.0,
537 538 539
    );
    // Middle widget not even wrapped with RenderOpacity, i.e. is always visible.
    expect(
540
      tester.element(find.text('Different title')).findAncestorRenderObjectOfType<RenderOpacity>(),
541 542 543
      isNull,
    );

544
    expect(tester.getBottomLeft(find.text('Title')).dy, 44.0 + 52.0 - 8.0); // Static part + extension - padding.
545 546 547 548 549 550

    scrollController.jumpTo(600.0);
    await tester.pump(); // Once to trigger the opacity animation.
    await tester.pump(const Duration(milliseconds: 300));

    largeTitleOpacity =
551
        tester.element(find.text('Title')).findAncestorRenderObjectOfType<RenderAnimatedOpacity>()!;
552 553
    // Large title no longer visible.
    expect(
554
      largeTitleOpacity.opacity.value,
555
      0.0,
556 557 558 559 560 561 562 563
    );

    // The persistent toolbar doesn't move or change size.
    expect(tester.getTopLeft(find.byType(NavigationToolbar)).dy, 0.0);
    expect(tester.getSize(find.byType(NavigationToolbar)).height, 44.0);

    expect(tester.getBottomLeft(find.text('Title')).dy, 44.0 - 8.0); // Extension gone, (static part - padding) left.
  });
564 565 566

  testWidgets('Auto back/close button', (WidgetTester tester) async {
    await tester.pumpWidget(
567 568
      const CupertinoApp(
        home: CupertinoNavigationBar(
569
          middle: Text('Home page'),
xster's avatar
xster committed
570
        ),
571 572 573 574 575
      ),
    );

    expect(find.byType(CupertinoButton), findsNothing);

576
    tester.state<NavigatorState>(find.byType(Navigator)).push(CupertinoPageRoute<void>(
577 578
      builder: (BuildContext context) {
        return const CupertinoNavigationBar(
579
          middle: Text('Page 2'),
580 581 582 583 584
        );
      },
    ));

    await tester.pump();
585
    await tester.pump(const Duration(milliseconds: 500));
586 587

    expect(find.byType(CupertinoButton), findsOneWidget);
588
    expect(find.text(String.fromCharCode(CupertinoIcons.back.codePoint)), findsOneWidget);
589

590
    tester.state<NavigatorState>(find.byType(Navigator)).push(CupertinoPageRoute<void>(
591 592 593
      fullscreenDialog: true,
      builder: (BuildContext context) {
        return const CupertinoNavigationBar(
594
          middle: Text('Dialog page'),
595 596 597 598 599
        );
      },
    ));

    await tester.pump();
600
    await tester.pump(const Duration(milliseconds: 500));
601

602
    expect(find.widgetWithText(CupertinoButton, 'Close'), findsOneWidget);
603 604 605 606 607

    // Test popping goes back correctly.
    await tester.tap(find.text('Close'));

    await tester.pump();
608
    await tester.pump(const Duration(milliseconds: 500));
609 610 611

    expect(find.text('Page 2'), findsOneWidget);

612
    await tester.tap(find.text(String.fromCharCode(CupertinoIcons.back.codePoint)));
613 614

    await tester.pump();
615
    await tester.pump(const Duration(milliseconds: 500));
616 617 618

    expect(find.text('Home page'), findsOneWidget);
  });
619

620 621
  testWidgets('Long back label turns into "back"', (WidgetTester tester) async {
    await tester.pumpWidget(
622 623
      const CupertinoApp(
        home: Placeholder(),
624 625 626 627
      ),
    );

    tester.state<NavigatorState>(find.byType(Navigator)).push(
628
      CupertinoPageRoute<void>(
629 630 631
        builder: (BuildContext context) {
          return const CupertinoPageScaffold(
            navigationBar: CupertinoNavigationBar(
632
              previousPageTitle: '012345678901',
633 634 635 636 637 638 639 640 641 642
            ),
            child: Placeholder(),
          );
        }
      )
    );

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 500));

643
    expect(find.widgetWithText(CupertinoButton, '012345678901'), findsOneWidget);
644 645

    tester.state<NavigatorState>(find.byType(Navigator)).push(
646
      CupertinoPageRoute<void>(
647 648 649
        builder: (BuildContext context) {
          return const CupertinoPageScaffold(
            navigationBar: CupertinoNavigationBar(
650
              previousPageTitle: '0123456789012',
651 652 653 654 655 656 657 658 659 660 661 662
            ),
            child: Placeholder(),
          );
        }
      )
    );

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 500));
    expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget);
  });

663 664
  testWidgets('Border should be displayed by default', (WidgetTester tester) async {
    await tester.pumpWidget(
665 666
      const CupertinoApp(
        home: CupertinoNavigationBar(
667
          middle: Text('Title'),
xster's avatar
xster committed
668
        ),
669 670 671
      ),
    );

672 673 674
    final DecoratedBox decoratedBox = tester.widgetList(find.descendant(
      of: find.byType(CupertinoNavigationBar),
      matching: find.byType(DecoratedBox),
675
    )).first as DecoratedBox;
676 677
    expect(decoratedBox.decoration.runtimeType, BoxDecoration);

678
    final BoxDecoration decoration = decoratedBox.decoration as BoxDecoration;
679 680
    expect(decoration.border, isNotNull);

681
    final BorderSide side = decoration.border!.bottom;
682 683 684 685 686
    expect(side, isNotNull);
  });

  testWidgets('Overrides border color', (WidgetTester tester) async {
    await tester.pumpWidget(
687 688
      const CupertinoApp(
        home: CupertinoNavigationBar(
689 690 691 692
          middle: Text('Title'),
          border: Border(
            bottom: BorderSide(
              color: Color(0xFFAABBCC),
xster's avatar
xster committed
693 694 695 696
              width: 0.0,
            ),
          ),
        ),
697 698 699
      ),
    );

700 701 702
    final DecoratedBox decoratedBox = tester.widgetList(find.descendant(
      of: find.byType(CupertinoNavigationBar),
      matching: find.byType(DecoratedBox),
703
    )).first as DecoratedBox;
704 705
    expect(decoratedBox.decoration.runtimeType, BoxDecoration);

706
    final BoxDecoration decoration = decoratedBox.decoration as BoxDecoration;
707 708
    expect(decoration.border, isNotNull);

709
    final BorderSide side = decoration.border!.bottom;
710 711 712 713 714 715
    expect(side, isNotNull);
    expect(side.color, const Color(0xFFAABBCC));
  });

  testWidgets('Border should not be displayed when null', (WidgetTester tester) async {
    await tester.pumpWidget(
716 717
      const CupertinoApp(
        home: CupertinoNavigationBar(
718
          middle: Text('Title'),
xster's avatar
xster committed
719 720
          border: null,
        ),
721 722 723
      ),
    );

724 725 726
    final DecoratedBox decoratedBox = tester.widgetList(find.descendant(
      of: find.byType(CupertinoNavigationBar),
      matching: find.byType(DecoratedBox),
727
    )).first as DecoratedBox;
728 729
    expect(decoratedBox.decoration.runtimeType, BoxDecoration);

730
    final BoxDecoration decoration = decoratedBox.decoration as BoxDecoration;
731 732
    expect(decoration.border, isNull);
  });
733

734
  testWidgets('Border is displayed by default in sliver nav bar', (WidgetTester tester) async {
735
    await tester.pumpWidget(
736
      const CupertinoApp(
737 738
        home: CupertinoPageScaffold(
          child: CustomScrollView(
739
            slivers: <Widget>[
740 741
              CupertinoSliverNavigationBar(
                largeTitle: Text('Large Title'),
xster's avatar
xster committed
742 743 744 745
              ),
            ],
          ),
        ),
746 747 748 749 750 751
      ),
    );

    final DecoratedBox decoratedBox = tester.widgetList(find.descendant(
      of: find.byType(CupertinoSliverNavigationBar),
      matching: find.byType(DecoratedBox),
752
    )).first as DecoratedBox;
753 754
    expect(decoratedBox.decoration.runtimeType, BoxDecoration);

755
    final BoxDecoration decoration = decoratedBox.decoration as BoxDecoration;
756 757
    expect(decoration.border, isNotNull);

758
    final BorderSide bottom = decoration.border!.bottom;
759 760 761
    expect(bottom, isNotNull);
  });

762
  testWidgets('Border is not displayed when null in sliver nav bar', (WidgetTester tester) async {
763
    await tester.pumpWidget(
764
      const CupertinoApp(
765 766
        home: CupertinoPageScaffold(
          child: CustomScrollView(
767
            slivers: <Widget>[
768 769
              CupertinoSliverNavigationBar(
                largeTitle: Text('Large Title'),
xster's avatar
xster committed
770 771 772 773 774
                border: null,
              ),
            ],
          ),
        ),
775 776 777 778 779 780
      ),
    );

    final DecoratedBox decoratedBox = tester.widgetList(find.descendant(
      of: find.byType(CupertinoSliverNavigationBar),
      matching: find.byType(DecoratedBox),
781
    )).first as DecoratedBox;
782 783
    expect(decoratedBox.decoration.runtimeType, BoxDecoration);

784
    final BoxDecoration decoration = decoratedBox.decoration as BoxDecoration;
785 786 787
    expect(decoration.border, isNull);
  });

788
  testWidgets('CupertinoSliverNavigationBar has semantics', (WidgetTester tester) async {
789
    final SemanticsTester semantics = SemanticsTester(tester);
790

791
    await tester.pumpWidget(const CupertinoApp(
792 793
      home: CupertinoPageScaffold(
        child: CustomScrollView(
794
          slivers: <Widget>[
795 796
            CupertinoSliverNavigationBar(
              largeTitle: Text('Large Title'),
xster's avatar
xster committed
797 798 799 800 801
              border: null,
            ),
          ],
        ),
      ),
802 803 804 805 806 807 808 809 810 811 812 813
    ));

    expect(semantics.nodesWith(
      label: 'Large Title',
      flags: <SemanticsFlag>[SemanticsFlag.isHeader],
      textDirection: TextDirection.ltr,
    ), hasLength(1));

    semantics.dispose();
  });

  testWidgets('CupertinoNavigationBar has semantics', (WidgetTester tester) async {
814
    final SemanticsTester semantics = SemanticsTester(tester);
815

816 817
    await tester.pumpWidget(CupertinoApp(
      home: CupertinoPageScaffold(
xster's avatar
xster committed
818
        navigationBar: const CupertinoNavigationBar(
819
          middle: Text('Fixed Title'),
xster's avatar
xster committed
820
        ),
821
        child: Container(),
xster's avatar
xster committed
822
      ),
823 824 825 826 827 828 829 830 831 832 833
    ));

    expect(semantics.nodesWith(
      label: 'Fixed Title',
      flags: <SemanticsFlag>[SemanticsFlag.isHeader],
      textDirection: TextDirection.ltr,
    ), hasLength(1));

    semantics.dispose();
  });

834
  testWidgets('Border can be overridden in sliver nav bar', (WidgetTester tester) async {
835
    await tester.pumpWidget(
836
      const CupertinoApp(
837 838
        home: CupertinoPageScaffold(
          child: CustomScrollView(
839
            slivers: <Widget>[
840 841 842 843 844
              CupertinoSliverNavigationBar(
                largeTitle: Text('Large Title'),
                border: Border(
                  bottom: BorderSide(
                    color: Color(0xFFAABBCC),
xster's avatar
xster committed
845 846
                    width: 0.0,
                  ),
847
                ),
xster's avatar
xster committed
848 849 850 851
              ),
            ],
          ),
        ),
852 853 854 855 856 857
      ),
    );

    final DecoratedBox decoratedBox = tester.widgetList(find.descendant(
      of: find.byType(CupertinoSliverNavigationBar),
      matching: find.byType(DecoratedBox),
858
    )).first as DecoratedBox;
859 860
    expect(decoratedBox.decoration.runtimeType, BoxDecoration);

861
    final BoxDecoration decoration = decoratedBox.decoration as BoxDecoration;
862 863
    expect(decoration.border, isNotNull);

864
    final BorderSide top = decoration.border!.top;
865 866
    expect(top, isNotNull);
    expect(top, BorderSide.none);
867
    final BorderSide bottom = decoration.border!.bottom;
868 869 870 871
    expect(bottom, isNotNull);
    expect(bottom.color, const Color(0xFFAABBCC));
  });

872 873 874 875
  testWidgets(
    'Standard title golden',
    (WidgetTester tester) async {
      await tester.pumpWidget(
876 877
        const CupertinoApp(
          home: RepaintBoundary(
878 879 880
            child: CupertinoPageScaffold(
              navigationBar: CupertinoNavigationBar(
                middle: Text('Bling bling'),
xster's avatar
xster committed
881
              ),
882
              child: Center(),
xster's avatar
xster committed
883 884
            ),
          ),
885 886 887 888 889
        ),
      );

      await expectLater(
        find.byType(RepaintBoundary).last,
890
        matchesGoldenFile('nav_bar_test.standard_title.png'),
891 892 893 894 895 896 897 898
      );
    },
  );

  testWidgets(
    'Large title golden',
    (WidgetTester tester) async {
      await tester.pumpWidget(
899 900 901 902
        CupertinoApp(
          home: RepaintBoundary(
            child: CupertinoPageScaffold(
              child: CustomScrollView(
xster's avatar
xster committed
903 904
                slivers: <Widget>[
                  const CupertinoSliverNavigationBar(
905
                    largeTitle: Text('Bling bling'),
xster's avatar
xster committed
906
                  ),
907 908
                  SliverToBoxAdapter(
                    child: Container(
xster's avatar
xster committed
909 910
                      height: 1200.0,
                    ),
911
                  ),
xster's avatar
xster committed
912 913 914 915
                ],
              ),
            ),
          ),
916 917 918 919 920
        ),
      );

      await expectLater(
        find.byType(RepaintBoundary).last,
921
        matchesGoldenFile('nav_bar_test.large_title.png'),
922 923
      );
    },
924
  );
925 926 927 928


  testWidgets('NavBar draws a light system bar for a dark background', (WidgetTester tester) async {
    await tester.pumpWidget(
929
      WidgetsApp(
930 931
        color: const Color(0xFFFFFFFF),
        onGenerateRoute: (RouteSettings settings) {
932
          return CupertinoPageRoute<void>(
933 934 935
            settings: settings,
            builder: (BuildContext context) {
              return const CupertinoNavigationBar(
936 937
                middle: Text('Test'),
                backgroundColor: Color(0xFF000000),
938 939 940 941 942 943 944 945 946 947 948
              );
            },
          );
        },
      ),
    );
    expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
  });

  testWidgets('NavBar draws a dark system bar for a light background', (WidgetTester tester) async {
    await tester.pumpWidget(
949
      WidgetsApp(
950 951
        color: const Color(0xFFFFFFFF),
        onGenerateRoute: (RouteSettings settings) {
952
          return CupertinoPageRoute<void>(
953 954 955
            settings: settings,
            builder: (BuildContext context) {
              return const CupertinoNavigationBar(
956 957
                middle: Text('Test'),
                backgroundColor: Color(0xFFFFFFFF),
958 959 960 961 962 963 964 965
              );
            },
          );
        },
      ),
    );
    expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
  });
966 967 968 969 970 971 972 973 974 975 976 977

  testWidgets('CupertinoNavigationBarBackButton shows an error when manually added outside a route', (WidgetTester tester) async {
    await tester.pumpWidget(
      const CupertinoNavigationBarBackButton()
    );

    final dynamic exception = tester.takeException();
    expect(exception, isAssertionError);
    expect(exception.toString(), contains('CupertinoNavigationBarBackButton should only be used in routes that can be popped'));
  });

  testWidgets('CupertinoNavigationBarBackButton shows an error when placed in a route that cannot be popped', (WidgetTester tester) async {
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
    await tester.pumpWidget(
      const CupertinoApp(
        home: CupertinoNavigationBarBackButton(),
      ),
    );

    final dynamic exception = tester.takeException();
    expect(exception, isAssertionError);
    expect(exception.toString(), contains('CupertinoNavigationBarBackButton should only be used in routes that can be popped'));
  });

  testWidgets('CupertinoNavigationBarBackButton with a custom onPressed callback can be placed anywhere', (WidgetTester tester) async {
    bool backPressed = false;

    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoNavigationBarBackButton(
          onPressed: () => backPressed = true,
        ),
      ),
    );

    expect(tester.takeException(), isNull);
    expect(find.text(String.fromCharCode(CupertinoIcons.back.codePoint)), findsOneWidget);

    await tester.tap(find.byType(CupertinoNavigationBarBackButton));

    expect(backPressed, true);
  });

  testWidgets(
    'Manually inserted CupertinoNavigationBarBackButton still automatically '
        'show previous page title when possible',
    (WidgetTester tester) async {
1012 1013
      await tester.pumpWidget(
        const CupertinoApp(
1014
          home: Placeholder(),
1015 1016 1017
        ),
      );

1018 1019 1020 1021 1022 1023 1024 1025 1026
      tester.state<NavigatorState>(find.byType(Navigator)).push(
        CupertinoPageRoute<void>(
          title: 'An iPod',
          builder: (BuildContext context) {
            return const CupertinoPageScaffold(
              navigationBar: CupertinoNavigationBar(),
              child: Placeholder(),
            );
          },
1027
        ),
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
      );

      await tester.pump();
      await tester.pump(const Duration(milliseconds: 500));

      tester.state<NavigatorState>(find.byType(Navigator)).push(
        CupertinoPageRoute<void>(
          title: 'A Phone',
          builder: (BuildContext context) {
            return const CupertinoNavigationBarBackButton();
          },
1039
        ),
1040 1041 1042 1043 1044 1045
      );

      await tester.pump();
      await tester.pump(const Duration(milliseconds: 500));

      expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
1046
    },
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
  );

  testWidgets(
    'CupertinoNavigationBarBackButton onPressed overrides default pop behavior',
    (WidgetTester tester) async {
      bool backPressed = false;
      await tester.pumpWidget(
        const CupertinoApp(
          home: Placeholder(),
        ),
      );

      tester.state<NavigatorState>(find.byType(Navigator)).push(
        CupertinoPageRoute<void>(
          title: 'An iPod',
          builder: (BuildContext context) {
            return const CupertinoPageScaffold(
              navigationBar: CupertinoNavigationBar(),
              child: Placeholder(),
            );
          },
1068
        ),
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
      );

      await tester.pump();
      await tester.pump(const Duration(milliseconds: 500));

      tester.state<NavigatorState>(find.byType(Navigator)).push(
        CupertinoPageRoute<void>(
          title: 'A Phone',
          builder: (BuildContext context) {
            return CupertinoPageScaffold(
              navigationBar: CupertinoNavigationBar(
                leading: CupertinoNavigationBarBackButton(
                  onPressed: () => backPressed = true,
                ),
              ),
              child: const Placeholder(),
            );
          },
1087
        ),
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
      );

      await tester.pump();
      await tester.pump(const Duration(milliseconds: 500));

      await tester.tap(find.byType(CupertinoNavigationBarBackButton));
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 500));

      // The second page is still on top and didn't pop.
      expect(find.text('A Phone'), findsOneWidget);
      // Custom onPressed called.
      expect(backPressed, true);
1101
    },
1102
  );
1103 1104 1105 1106 1107 1108

  testWidgets('textScaleFactor is set to 1.0', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        home: Builder(builder: (BuildContext context) {
          return MediaQuery(
1109
            data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
            child: CupertinoPageScaffold(
              child: CustomScrollView(
                slivers: <Widget>[
                  const CupertinoSliverNavigationBar(
                    leading: Text('leading'),
                    middle: Text('middle'),
                    largeTitle: Text('Large Title'),
                    trailing: Text('trailing'),
                  ),
                  SliverToBoxAdapter(
                    child: Container(
                      child: const Text('content'),
                    ),
                  ),
                ],
              ),
            ),
          );
        }),
      ),
    );

    final Iterable<RichText> barItems = tester.widgetList<RichText>(
      find.descendant(
        of: find.byType(CupertinoSliverNavigationBar),
        matching: find.byType(RichText),
      ),
    );

    final Iterable<RichText> contents = tester.widgetList<RichText>(
      find.descendant(
        of: find.text('content'),
        matching: find.byType(RichText),
      ),
    );

    expect(barItems.length, greaterThan(0));
    expect(barItems.any((RichText t) => t.textScaleFactor != 1), isFalse);

    expect(contents.length, greaterThan(0));
    expect(contents.any((RichText t) => t.textScaleFactor != 99), isFalse);

    // Also works with implicitly added widgets.
    tester.state<NavigatorState>(find.byType(Navigator)).push(CupertinoPageRoute<void>(
      title: 'title',
      builder: (BuildContext context) {
        return MediaQuery(
1157
          data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
          child: Container(
            child: const CupertinoPageScaffold(
              child: CustomScrollView(
                slivers: <Widget>[
                  CupertinoSliverNavigationBar(
                    automaticallyImplyLeading: true,
                    automaticallyImplyTitle: true,
                    previousPageTitle: 'previous title',
                  ),
                ],
              ),
            ),
          ),
        );
      },
    ));

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 500));

    final Iterable<RichText> barItems2 = tester.widgetList<RichText>(
      find.descendant(
        of: find.byType(CupertinoSliverNavigationBar),
        matching: find.byType(RichText),
      ),
    );

    expect(barItems2.length, greaterThan(0));
    expect(barItems2.any((RichText t) => t.textScaleFactor != 1), isFalse);
  });
1188 1189 1190
}

class _ExpectStyles extends StatelessWidget {
1191 1192 1193 1194
  const _ExpectStyles({
    required this.color,
    required this.index
  });
1195 1196 1197 1198 1199 1200

  final Color color;
  final int index;

  @override
  Widget build(BuildContext context) {
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
1201
    final TextStyle style = DefaultTextStyle.of(context).style;
1202
    expect(style.color, isSameColorAs(color));
xster's avatar
xster committed
1203
    expect(style.fontFamily, '.SF Pro Text');
1204
    expect(style.fontSize, 17.0);
xster's avatar
xster committed
1205
    expect(style.letterSpacing, -0.41);
1206
    count += index;
1207
    return Container();
1208
  }
1209
}