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
class NotifyMaterial extends StatelessWidget {
14
  const NotifyMaterial({ Key key }) : super(key: key);
15 16
  @override
  Widget build(BuildContext context) {
17 18
    LayoutChangedNotification().dispatch(context);
    return Container();
19 20 21
  }
}

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

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

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

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

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

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

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

67
void main() {
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 98
  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)',
99
      'borderRadius: BorderRadiusDirectional.circular(10.0)',
100 101 102
    ]);
  });

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

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

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

    // 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();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

220 221 222 223 224 225 226 227 228
  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),
          ),
229 230
          child: buildMaterial(color: surfaceColor, elevation: 8.0),
      ));
231 232 233 234
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColor));
    });

235 236 237 238
    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;

239
      // The colors we should get with a base surface color of 0xFF121212 for
240
      // and a given elevation
241 242
      const List<ElevationColor> elevationColors = <ElevationColor>[
        ElevationColor(0.0, Color(0xFF121212)),
243 244 245 246 247 248 249 250 251
        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)),
252 253
      ];

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

  });

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

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

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

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

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

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

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

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

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

      expect(
        find.byKey(materialKey),
        clipsWithShapeBorder(
          shape: const StadiumBorder(),
        ),
      );
    });
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 440

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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