material_test.dart 24.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// 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
  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),
          ),
228 229
          child: buildMaterial(color: surfaceColor, elevation: 8.0),
      ));
230 231 232 233
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColor));
    });

234 235 236 237
    testWidgets('applyElevationOverlayColor set to true applies a semi-transparent onSurface color to the surface color', (WidgetTester tester) async {
      const Color surfaceColor = Color(0xFF121212);
      const Color onSurfaceColor = Colors.greenAccent;

238
      // The colors we should get with a base surface color of 0xFF121212 for
239
      // and a given elevation
240 241
      const List<ElevationColor> elevationColors = <ElevationColor>[
        ElevationColor(0.0, Color(0xFF121212)),
242 243 244 245 246 247 248 249 250
        ElevationColor(1.0, Color(0xFF161D19)),
        ElevationColor(2.0, Color(0xFF18211D)),
        ElevationColor(3.0, Color(0xFF19241E)),
        ElevationColor(4.0, Color(0xFF1A2620)),
        ElevationColor(6.0, Color(0xFF1B2922)),
        ElevationColor(8.0, Color(0xFF1C2C24)),
        ElevationColor(12.0, Color(0xFF1D3027)),
        ElevationColor(16.0, Color(0xFF1E3329)),
        ElevationColor(24.0, Color(0xFF20362B)),
251 252 253 254 255 256 257
      ];

      for (ElevationColor test in elevationColors) {
        await tester.pumpWidget(
            Theme(
              data: ThemeData(
                applyElevationOverlayColor: true,
258 259 260 261
                colorScheme: const ColorScheme.dark().copyWith(
                  surface: surfaceColor,
                  onSurface: onSurfaceColor,
                ),
262 263 264 265 266
              ),
              child: buildMaterial(
                color: surfaceColor,
                elevation: test.elevation,
              ),
267
            ),
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
        );
        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,
284
                elevation: 8.0,
285
            ),
286
          ),
287 288 289 290 291 292 293
      );
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(Colors.cyan));
    });

  });

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

      expect(find.byKey(materialKey), hasNoImmediateClip);
306
    });
307

308 309 310 311 312 313 314 315 316
    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,
317
            ),
318 319 320 321 322 323
        );
      }

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

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

335
      expect(find.byKey(materialKey), clipsWithBoundingRect);
336 337
    });

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

      expect(
        find.byKey(materialKey),
        clipsWithBoundingRRect(
353
          borderRadius: const BorderRadius.all(Radius.circular(10.0))
354 355 356
        ),
      );
    });
357

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

      expect(
        find.byKey(materialKey),
        clipsWithShapeBorder(
          shape: const StadiumBorder(),
        ),
      );
    });
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 435 436 437 438 439

    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',
      ]);
    });
440 441 442 443
  });

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

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

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

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
474
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
475 476 477 478
          elevation: 1.0,
      ));
    });

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

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

497
    testWidgets('card', (WidgetTester tester) async {
498
      final GlobalKey materialKey = GlobalKey();
499
      await tester.pumpWidget(
500
        Material(
501 502 503
          key: materialKey,
          type: MaterialType.card,
          child: const SizedBox(width: 100.0, height: 100.0),
504
        ),
505 506 507 508
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
509
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
510 511 512 513 514
          elevation: 0.0,
      ));
    });

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

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
528
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
529 530 531 532
          elevation: 5.0,
      ));
    });

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

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

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

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

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

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
581
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
582 583 584 585 586
          elevation: 0.0,
      ));
    });

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

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
601
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
602 603 604
          elevation: 4.0,
      ));
    });
605 606

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

      expect(find.byKey(materialKey), rendersOnPhysicalShape(
          shape: const StadiumBorder(),
          elevation: 4.0,
      ));
    });
624
  });
625 626 627

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

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

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

      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 {
669
      final GlobalKey materialKey = GlobalKey();
670
      await tester.pumpWidget(
671
        Material(
672 673 674 675
          key: materialKey,
          type: MaterialType.transparency,
          child: const SizedBox(width: 100.0, height: 100.0),
          shape: const CircleBorder(),
676
        ),
677 678 679 680 681
      );

      final RenderBox box = tester.renderObject(find.byKey(materialKey));
      expect(box, isNot(paints..circle()));
    });
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705

    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,
706
                      ),
707 708 709 710
                    ],
                  ),
                ),
              ),
711 712
            ),
          ),
713 714 715 716 717
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
718
        matchesGoldenFile('material.border_paint_above.png'),
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 744 745

    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,
746
                      ),
747 748 749 750
                    ],
                  ),
                ),
              ),
751 752
            ),
          ),
753 754 755 756 757
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
758
        matchesGoldenFile('material.border_paint_below.png'),
759
      );
760
    });
761
  });
762
}