material_test.dart 24.4 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
6
import 'package:flutter/painting.dart';
7
import 'package:flutter/rendering.dart';
8 9
import 'package:flutter_test/flutter_test.dart';

10
import '../rendering/mock_canvas.dart';
11
import '../widgets/test_border.dart' show TestBorder;
12

13 14 15
class NotifyMaterial extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
16 17
    LayoutChangedNotification().dispatch(context);
    return Container();
18 19 20
  }
}

21 22 23
Widget buildMaterial({
  double elevation = 0.0,
  Color shadowColor = const Color(0xFF00FF00),
24
  Color color = const Color(0xFF0000FF),
25
}) {
26 27
  return Center(
    child: SizedBox(
28 29
      height: 100.0,
      width: 100.0,
30
      child: Material(
31
        color: color,
32
        shadowColor: shadowColor,
33
        elevation: elevation,
34
        shape: const CircleBorder(),
35 36 37 38 39
      ),
    ),
  );
}

40
RenderPhysicalShape getModel(WidgetTester tester) {
41
  return tester.renderObject(find.byType(PhysicalShape));
42 43
}

44 45 46
class PaintRecorder extends CustomPainter {
  PaintRecorder(this.log);

47
  final List<Size> log;
48 49 50 51

  @override
  void paint(Canvas canvas, Size size) {
    log.add(size);
52
    final Paint paint = Paint()..color = const Color(0xFF0000FF);
53
    canvas.drawRect(Offset.zero & size, paint);
54 55 56 57 58 59
  }

  @override
  bool shouldRepaint(PaintRecorder oldDelegate) => false;
}

60 61 62 63 64 65
class ElevationColor {
  const ElevationColor(this.elevation, this.color);
  final double elevation;
  final Color color;
}

66
void main() {
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
  testWidgets('default Material debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const Material().debugFillProperties(builder);

    final List<String> description = builder.properties
      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
      .map((DiagnosticsNode node) => node.toString())
      .toList();

    expect(description, <String>['type: canvas']);
  });

  testWidgets('Material implements debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const Material(
      type: MaterialType.canvas,
      color: Color(0xFFFFFFFF),
      textStyle: TextStyle(color: Color(0xff00ff00)),
      borderRadius: BorderRadiusDirectional.all(Radius.circular(10)),
    ).debugFillProperties(builder);

    final List<String> description = builder.properties
      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
      .map((DiagnosticsNode node) => node.toString())
      .toList();

    expect(description, <String>[
      'type: canvas',
      'color: Color(0xffffffff)',
      'textStyle.inherit: true',
      'textStyle.color: Color(0xff00ff00)',
98
      'borderRadius: BorderRadiusDirectional.circular(10.0)',
99 100 101
    ]);
  });

102
  testWidgets('LayoutChangedNotification test', (WidgetTester tester) async {
103
    await tester.pumpWidget(
104 105
      Material(
        child: NotifyMaterial(),
106
      ),
107 108
    );
  });
109 110

  testWidgets('ListView scroll does not repaint', (WidgetTester tester) async {
111
    final List<Size> log = <Size>[];
112 113

    await tester.pumpWidget(
114
      Directionality(
115
        textDirection: TextDirection.ltr,
116
        child: Column(
117
          children: <Widget>[
118
            SizedBox(
119 120
              width: 150.0,
              height: 150.0,
121 122
              child: CustomPaint(
                painter: PaintRecorder(log),
123
              ),
124
            ),
125 126 127
            Expanded(
              child: Material(
                child: Column(
128
                  children: <Widget>[
129 130
                    Expanded(
                      child: ListView(
131
                        children: <Widget>[
132
                          Container(
133 134 135 136 137
                            height: 2000.0,
                            color: const Color(0xFF00FF00),
                          ),
                        ],
                      ),
138
                    ),
139
                    SizedBox(
140 141
                      width: 100.0,
                      height: 100.0,
142 143
                      child: CustomPaint(
                        painter: PaintRecorder(log),
144
                      ),
145
                    ),
146 147
                  ],
                ),
148 149
              ),
            ),
150 151
          ],
        ),
152 153 154 155 156 157 158 159 160 161 162
      ),
    );

    // We paint twice because we have two CustomPaint widgets in the tree above
    // to test repainting both inside and outside the Material widget.
    expect(log, equals(<Size>[
      const Size(150.0, 150.0),
      const Size(100.0, 100.0),
    ]));
    log.clear();

163
    await tester.drag(find.byType(ListView), const Offset(0.0, -300.0));
164 165 166 167
    await tester.pump();

    expect(log, isEmpty);
  });
168 169

  testWidgets('Shadows animate smoothly', (WidgetTester tester) async {
170 171
    // This code verifies that the PhysicalModel's elevation animates over
    // a kThemeChangeDuration time interval.
172

173
    await tester.pumpWidget(buildMaterial(elevation: 0.0));
174
    final RenderPhysicalShape modelA = getModel(tester);
175 176
    expect(modelA.elevation, equals(0.0));

177
    await tester.pumpWidget(buildMaterial(elevation: 9.0));
178
    final RenderPhysicalShape modelB = getModel(tester);
179
    expect(modelB.elevation, equals(0.0));
180 181

    await tester.pump(const Duration(milliseconds: 1));
182
    final RenderPhysicalShape modelC = getModel(tester);
183
    expect(modelC.elevation, closeTo(0.0, 0.001));
184 185

    await tester.pump(kThemeChangeDuration ~/ 2);
186
    final RenderPhysicalShape modelD = getModel(tester);
187
    expect(modelD.elevation, isNot(closeTo(0.0, 0.001)));
188 189

    await tester.pump(kThemeChangeDuration);
190
    final RenderPhysicalShape modelE = getModel(tester);
191
    expect(modelE.elevation, equals(9.0));
192
  });
193 194

  testWidgets('Shadow colors animate smoothly', (WidgetTester tester) async {
195
    // This code verifies that the PhysicalModel's shadowColor animates over
196 197 198
    // a kThemeChangeDuration time interval.

    await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFF00FF00)));
199
    final RenderPhysicalShape modelA = getModel(tester);
200 201 202
    expect(modelA.shadowColor, equals(const Color(0xFF00FF00)));

    await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFFFF0000)));
203
    final RenderPhysicalShape modelB = getModel(tester);
204 205 206
    expect(modelB.shadowColor, equals(const Color(0xFF00FF00)));

    await tester.pump(const Duration(milliseconds: 1));
207
    final RenderPhysicalShape modelC = getModel(tester);
208
    expect(modelC.shadowColor, within<Color>(distance: 1, from: const Color(0xFF00FF00)));
209 210

    await tester.pump(kThemeChangeDuration ~/ 2);
211
    final RenderPhysicalShape modelD = getModel(tester);
212
    expect(modelD.shadowColor, isNot(within<Color>(distance: 1, from: const Color(0xFF00FF00))));
213 214

    await tester.pump(kThemeChangeDuration);
215
    final RenderPhysicalShape modelE = getModel(tester);
216 217
    expect(modelE.shadowColor, equals(const Color(0xFFFF0000)));
  });
218

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
  group('Elevation Overlay', () {

    testWidgets('applyElevationOverlayColor set to false does not change surface color', (WidgetTester tester) async {
      const Color surfaceColor = Color(0xFF121212);
      await tester.pumpWidget(Theme(
          data: ThemeData(
            applyElevationOverlayColor: false,
            colorScheme: const ColorScheme.dark().copyWith(surface: surfaceColor),
          ),
          child: buildMaterial(color: surfaceColor, elevation: 8.0))
      );
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColor));
    });

    testWidgets('applyElevationOverlayColor set to true overlays a transparent white on surface color', (WidgetTester tester) async {
      // The colors we should get with a base surface color of 0xFF121212 for
      // a given elevation
      const List<ElevationColor> elevationColors = <ElevationColor>[
        ElevationColor(0.0, Color(0xFF121212)),
        ElevationColor(1.0, Color(0xFF1E1E1E)),
        ElevationColor(2.0, Color(0xFF222222)),
        ElevationColor(3.0, Color(0xFF252525)),
        ElevationColor(4.0, Color(0xFF282828)),
        ElevationColor(6.0, Color(0xFF2B2B2B)),
        ElevationColor(8.0, Color(0xFF2D2D2D)),
        ElevationColor(12.0, Color(0xFF323232)),
        ElevationColor(16.0, Color(0xFF353535)),
        ElevationColor(24.0, Color(0xFF393939)),
      ];
      const Color surfaceColor = Color(0xFF121212);

      for (ElevationColor test in elevationColors) {
        await tester.pumpWidget(
            Theme(
              data: ThemeData(
                applyElevationOverlayColor: true,
                colorScheme: const ColorScheme.dark().copyWith(surface: surfaceColor),
              ),
              child: buildMaterial(
                color: surfaceColor,
                elevation: test.elevation,
              ),
            )
        );
        await tester.pumpAndSettle(); // wait for the elevation animation to finish
        final RenderPhysicalShape model = getModel(tester);
        expect(model.color, equals(test.color));
      }
    });

    testWidgets('overlay will only apply to materials using colorScheme.surface', (WidgetTester tester) async {
      await tester.pumpWidget(
          Theme(
            data: ThemeData(
              applyElevationOverlayColor: true,
              colorScheme: const ColorScheme.dark().copyWith(surface: const Color(0xFF121212)),
            ),
            child: buildMaterial(
                color: Colors.cyan,
                elevation: 8.0
            ),
          )
      );
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(Colors.cyan));
    });

  });

289
  group('Transparency clipping', () {
290
    testWidgets('No clip by default', (WidgetTester tester) async {
291
      final GlobalKey materialKey = GlobalKey();
292
      await tester.pumpWidget(
293
          Material(
294 295 296 297 298 299 300
            key: materialKey,
            type: MaterialType.transparency,
            child: const SizedBox(width: 100.0, height: 100.0),
          )
      );

      expect(find.byKey(materialKey), hasNoImmediateClip);
301
    });
302

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    testWidgets('Null clipBehavior asserts', (WidgetTester tester) async {
      final GlobalKey materialKey = GlobalKey();
      Future<void> doPump() async {
        await tester.pumpWidget(
            Material(
              key: materialKey,
              type: MaterialType.transparency,
              child: const SizedBox(width: 100.0, height: 100.0),
              clipBehavior: null,
            )
        );
      }

      expect(() async => doPump(), throwsAssertionError);
    });

319
    testWidgets('clips to bounding rect by default given Clip.antiAlias', (WidgetTester tester) async {
320
      final GlobalKey materialKey = GlobalKey();
321
      await tester.pumpWidget(
322
        Material(
323 324
          key: materialKey,
          type: MaterialType.transparency,
325 326
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
327 328 329
        )
      );

330
      expect(find.byKey(materialKey), clipsWithBoundingRect);
331 332
    });

333
    testWidgets('clips to rounded rect when borderRadius provided given Clip.antiAlias', (WidgetTester tester) async {
334
      final GlobalKey materialKey = GlobalKey();
335
      await tester.pumpWidget(
336
        Material(
337 338
          key: materialKey,
          type: MaterialType.transparency,
339
          borderRadius: const BorderRadius.all(Radius.circular(10.0)),
340 341
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
342 343 344 345 346 347
        )
      );

      expect(
        find.byKey(materialKey),
        clipsWithBoundingRRect(
348
          borderRadius: const BorderRadius.all(Radius.circular(10.0))
349 350 351
        ),
      );
    });
352

353
    testWidgets('clips to shape when provided given Clip.antiAlias', (WidgetTester tester) async {
354
      final GlobalKey materialKey = GlobalKey();
355
      await tester.pumpWidget(
356
        Material(
357 358 359
          key: materialKey,
          type: MaterialType.transparency,
          shape: const StadiumBorder(),
360 361
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
362 363 364 365 366 367 368 369 370 371
        )
      );

      expect(
        find.byKey(materialKey),
        clipsWithShapeBorder(
          shape: const StadiumBorder(),
        ),
      );
    });
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434

    testWidgets('supports directional clips', (WidgetTester tester) async {
      final List<String> logs = <String>[];
      final ShapeBorder shape = TestBorder((String message) { logs.add(message); });
      Widget buildMaterial() {
        return Material(
          type: MaterialType.transparency,
          shape: shape,
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
        );
      }
      final Widget material = buildMaterial();
      // verify that a regular clip works as one would expect
      logs.add('--0');
      await tester.pumpWidget(material);
      // verify that pumping again doesn't recompute the clip
      // even though the widget itself is new (the shape doesn't change identity)
      logs.add('--1');
      await tester.pumpWidget(buildMaterial());
      // verify that Material passes the TextDirection on to its shape when it's transparent
      logs.add('--2');
      await tester.pumpWidget(Directionality(
        textDirection: TextDirection.ltr,
        child: material,
      ));
      // verify that changing the text direction from LTR to RTL has an effect
      // even though the widget itself is identical
      logs.add('--3');
      await tester.pumpWidget(Directionality(
        textDirection: TextDirection.rtl,
        child: material,
      ));
      // verify that pumping again with a text direction has no effect
      logs.add('--4');
      await tester.pumpWidget(Directionality(
        textDirection: TextDirection.rtl,
        child: buildMaterial(),
      ));
      logs.add('--5');
      // verify that changing the text direction and the widget at the same time
      // works as expected
      await tester.pumpWidget(Directionality(
        textDirection: TextDirection.ltr,
        child: material,
      ));
      expect(logs, <String>[
        '--0',
        'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
        'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
        '--1',
        '--2',
        'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
        'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
        '--3',
        'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
        'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
        '--4',
        '--5',
        'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
        'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
      ]);
    });
435 436 437 438
  });

  group('PhysicalModels', () {
    testWidgets('canvas', (WidgetTester tester) async {
439
      final GlobalKey materialKey = GlobalKey();
440
      await tester.pumpWidget(
441
        Material(
442 443
          key: materialKey,
          type: MaterialType.canvas,
444
          child: const SizedBox(width: 100.0, height: 100.0),
445 446 447 448 449 450 451 452 453 454 455
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
          borderRadius: BorderRadius.zero,
          elevation: 0.0,
      ));
    });

    testWidgets('canvas with borderRadius and elevation', (WidgetTester tester) async {
456
      final GlobalKey materialKey = GlobalKey();
457
      await tester.pumpWidget(
458
        Material(
459 460
          key: materialKey,
          type: MaterialType.canvas,
461
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
462 463 464 465 466 467 468
          child: const SizedBox(width: 100.0, height: 100.0),
          elevation: 1.0,
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
469
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
470 471 472 473
          elevation: 1.0,
      ));
    });

474
    testWidgets('canvas with shape and elevation', (WidgetTester tester) async {
475
      final GlobalKey materialKey = GlobalKey();
476
      await tester.pumpWidget(
477
        Material(
478 479 480 481 482 483 484 485 486 487 488 489 490 491
          key: materialKey,
          type: MaterialType.canvas,
          shape: const StadiumBorder(),
          child: const SizedBox(width: 100.0, height: 100.0),
          elevation: 1.0,
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalShape(
          shape: const StadiumBorder(),
          elevation: 1.0,
      ));
    });

492
    testWidgets('card', (WidgetTester tester) async {
493
      final GlobalKey materialKey = GlobalKey();
494
      await tester.pumpWidget(
495
        Material(
496 497 498 499 500 501 502 503
          key: materialKey,
          type: MaterialType.card,
          child: const SizedBox(width: 100.0, height: 100.0),
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
504
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
505 506 507 508 509
          elevation: 0.0,
      ));
    });

    testWidgets('card with borderRadius and elevation', (WidgetTester tester) async {
510
      final GlobalKey materialKey = GlobalKey();
511
      await tester.pumpWidget(
512
        Material(
513 514
          key: materialKey,
          type: MaterialType.card,
515
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
516 517 518 519 520 521 522
          elevation: 5.0,
          child: const SizedBox(width: 100.0, height: 100.0),
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
523
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
524 525 526 527
          elevation: 5.0,
      ));
    });

528
    testWidgets('card with shape and elevation', (WidgetTester tester) async {
529
      final GlobalKey materialKey = GlobalKey();
530
      await tester.pumpWidget(
531
        Material(
532 533 534 535 536 537 538 539 540 541 542 543 544 545
          key: materialKey,
          type: MaterialType.card,
          shape: const StadiumBorder(),
          elevation: 5.0,
          child: const SizedBox(width: 100.0, height: 100.0),
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalShape(
          shape: const StadiumBorder(),
          elevation: 5.0,
      ));
    });

546
    testWidgets('circle', (WidgetTester tester) async {
547
      final GlobalKey materialKey = GlobalKey();
548
      await tester.pumpWidget(
549
        Material(
550 551 552 553 554 555 556 557 558 559 560 561 562 563
          key: materialKey,
          type: MaterialType.circle,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.circle,
          elevation: 0.0,
      ));
    });

    testWidgets('button', (WidgetTester tester) async {
564
      final GlobalKey materialKey = GlobalKey();
565
      await tester.pumpWidget(
566
        Material(
567 568 569 570 571 572 573 574 575
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
576
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
577 578 579 580 581
          elevation: 0.0,
      ));
    });

    testWidgets('button with elevation and borderRadius', (WidgetTester tester) async {
582
      final GlobalKey materialKey = GlobalKey();
583
      await tester.pumpWidget(
584
        Material(
585 586 587 588
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
589
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
590 591 592 593 594 595
          elevation: 4.0,
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
596
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
597 598 599
          elevation: 4.0,
      ));
    });
600 601

    testWidgets('button with elevation and shape', (WidgetTester tester) async {
602
      final GlobalKey materialKey = GlobalKey();
603
      await tester.pumpWidget(
604
        Material(
605 606 607 608 609 610 611 612 613 614 615 616 617 618
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
          shape: const StadiumBorder(),
          elevation: 4.0,
        )
      );

      expect(find.byKey(materialKey), rendersOnPhysicalShape(
          shape: const StadiumBorder(),
          elevation: 4.0,
      ));
    });
619
  });
620 621 622

  group('Border painting', () {
    testWidgets('border is painted on physical layers', (WidgetTester tester) async {
623
      final GlobalKey materialKey = GlobalKey();
624
      await tester.pumpWidget(
625
        Material(
626 627 628 629 630
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
          shape: const CircleBorder(
631
            side: BorderSide(
632
              width: 2.0,
633
              color: Color(0xFF0000FF),
634
            ),
635 636 637 638 639 640 641 642 643
          ),
        )
      );

      final RenderBox box = tester.renderObject(find.byKey(materialKey));
      expect(box, paints..circle());
    });

    testWidgets('border is painted for transparent material', (WidgetTester tester) async {
644
      final GlobalKey materialKey = GlobalKey();
645
      await tester.pumpWidget(
646
        Material(
647 648 649 650
          key: materialKey,
          type: MaterialType.transparency,
          child: const SizedBox(width: 100.0, height: 100.0),
          shape: const CircleBorder(
651
            side: BorderSide(
652
              width: 2.0,
653
              color: Color(0xFF0000FF),
654
            ),
655 656 657 658 659 660 661 662 663
          ),
        )
      );

      final RenderBox box = tester.renderObject(find.byKey(materialKey));
      expect(box, paints..circle());
    });

    testWidgets('border is not painted for when border side is none', (WidgetTester tester) async {
664
      final GlobalKey materialKey = GlobalKey();
665
      await tester.pumpWidget(
666
        Material(
667 668 669 670 671 672 673 674 675 676
          key: materialKey,
          type: MaterialType.transparency,
          child: const SizedBox(width: 100.0, height: 100.0),
          shape: const CircleBorder(),
        )
      );

      final RenderBox box = tester.renderObject(find.byKey(materialKey));
      expect(box, isNot(paints..circle()));
    });
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700

    testWidgets('border is painted above child by default', (WidgetTester tester) async {
      final Key painterKey = UniqueKey();

      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: RepaintBoundary(
            key: painterKey,
            child: Card(
              child: SizedBox(
                width: 200,
                height: 300,
                child: Material(
                  clipBehavior: Clip.hardEdge,
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                    side: const BorderSide(color: Colors.grey, width: 6),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Column(
                    children: <Widget>[
                      Container(
                        color: Colors.green,
                        height: 150,
701
                      ),
702 703 704 705
                    ],
                  ),
                ),
              ),
706 707
            ),
          ),
708 709 710 711 712
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
713 714 715 716
        matchesGoldenFile(
          'material.border_paint_above.png',
          version: null,
        ),
717
      );
718
    }, skip: isBrowser);
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743

    testWidgets('border is painted below child when specified', (WidgetTester tester) async {
      final Key painterKey = UniqueKey();

      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: RepaintBoundary(
            key: painterKey,
            child: Card(
              child: SizedBox(
                width: 200,
                height: 300,
                child: Material(
                  clipBehavior: Clip.hardEdge,
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                    side: const BorderSide(color: Colors.grey, width: 6),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  borderOnForeground: false,
                  child: Column(
                    children: <Widget>[
                      Container(
                        color: Colors.green,
                        height: 150,
744
                      ),
745 746 747 748
                    ],
                  ),
                ),
              ),
749 750
            ),
          ),
751 752 753 754 755
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
756 757 758 759
        matchesGoldenFile(
          'material.border_paint_below.png',
          version: null,
        ),
760
      );
761
    }, skip: isBrowser);
762
  });
763
}