material_test.dart 27.3 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/rendering.dart';
7 8
import 'package:flutter_test/flutter_test.dart';

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

12
class NotifyMaterial extends StatelessWidget {
13
  const NotifyMaterial({ Key? key }) : super(key: key);
14 15
  @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
  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),
84
      shadowColor: Color(0xffff0000),
85 86 87 88 89 90 91 92 93 94 95 96
      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)',
97
      'shadowColor: Color(0xffff0000)',
98 99
      'textStyle.inherit: true',
      'textStyle.color: Color(0xff00ff00)',
100
      'borderRadius: BorderRadiusDirectional.circular(10.0)',
101 102 103
    ]);
  });

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

221 222 223 224 225 226 227 228
  testWidgets('Transparent material widget does not absorb hit test', (WidgetTester tester) async {
    // This is a regression test for https://github.com/flutter/flutter/issues/58665.
    bool pressed = false;
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Stack(
            children: <Widget>[
229
              ElevatedButton(
230 231 232
                onPressed: () {
                  pressed = true;
                },
233
                child: null,
234
              ),
235
              const Material(
236
                type: MaterialType.transparency,
237
                child: SizedBox(
238 239 240 241 242 243 244 245 246
                  width: 400.0,
                  height: 500.0,
                ),
              ),
            ],
          ),
        ),
      ),
    );
247
    await tester.tap(find.byType(ElevatedButton));
248 249 250
    expect(pressed, isTrue);
  });

251 252 253 254 255 256 257 258 259
  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),
          ),
260 261
          child: buildMaterial(color: surfaceColor, elevation: 8.0),
      ));
262 263 264 265
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColor));
    });

266 267 268 269
    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;

270
      // The colors we should get with a base surface color of 0xFF121212 for
271
      // and a given elevation
272 273
      const List<ElevationColor> elevationColors = <ElevationColor>[
        ElevationColor(0.0, Color(0xFF121212)),
274 275 276 277 278 279 280 281 282
        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)),
283 284
      ];

285
      for (final ElevationColor test in elevationColors) {
286 287 288 289
        await tester.pumpWidget(
            Theme(
              data: ThemeData(
                applyElevationOverlayColor: true,
290 291 292 293
                colorScheme: const ColorScheme.dark().copyWith(
                  surface: surfaceColor,
                  onSurface: onSurfaceColor,
                ),
294 295 296 297 298
              ),
              child: buildMaterial(
                color: surfaceColor,
                elevation: test.elevation,
              ),
299
            ),
300 301 302 303 304 305 306
        );
        await tester.pumpAndSettle(); // wait for the elevation animation to finish
        final RenderPhysicalShape model = getModel(tester);
        expect(model.color, equals(test.color));
      }
    });

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    testWidgets('overlay will not apply to materials using a non-surface color', (WidgetTester tester) async {
      await tester.pumpWidget(
        Theme(
          data: ThemeData(
            applyElevationOverlayColor: true,
            colorScheme: const ColorScheme.dark(),
          ),
          child: buildMaterial(
            color: Colors.cyan,
            elevation: 8.0,
          ),
        ),
      );
      final RenderPhysicalShape model = getModel(tester);
      // Shouldn't change, as it is not using a ColorScheme.surface color
      expect(model.color, equals(Colors.cyan));
    });

325
    testWidgets('overlay will not apply to materials using a light theme', (WidgetTester tester) async {
326 327 328 329
      await tester.pumpWidget(
          Theme(
            data: ThemeData(
              applyElevationOverlayColor: true,
330
              colorScheme: const ColorScheme.light(),
331 332
            ),
            child: buildMaterial(
333 334
              color: Colors.cyan,
              elevation: 8.0,
335
            ),
336
          ),
337 338
      );
      final RenderPhysicalShape model = getModel(tester);
339
      // Shouldn't change, as it was under a light color scheme.
340 341 342
      expect(model.color, equals(Colors.cyan));
    });

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
    testWidgets('overlay will apply to materials with a non-opaque surface color', (WidgetTester tester) async {
      const Color surfaceColor = Color(0xFF121212);
      const Color surfaceColorWithOverlay = Color(0xC6353535);

      await tester.pumpWidget(
        Theme(
          data: ThemeData(
            applyElevationOverlayColor: true,
            colorScheme: const ColorScheme.dark(surface: surfaceColor),
          ),
          child: buildMaterial(
            color: surfaceColor.withOpacity(.75),
            elevation: 8.0,
          ),
        ),
      );

      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColorWithOverlay));
      expect(model.color, isNot(equals(surfaceColor)));
    });
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392

    testWidgets('Expected overlay color can be computed using colorWithOverlay', (WidgetTester tester) async {
      const Color surfaceColor = Color(0xFF123456);
      const Color onSurfaceColor = Color(0xFF654321);
      const double elevation = 8.0;

      final Color surfaceColorWithOverlay =
        ElevationOverlay.colorWithOverlay(surfaceColor, onSurfaceColor, elevation);

      await tester.pumpWidget(
        Theme(
          data: ThemeData(
            applyElevationOverlayColor: true,
            colorScheme: const ColorScheme.dark(
              surface: surfaceColor,
              onSurface: onSurfaceColor,
            ),
          ),
          child: buildMaterial(
            color: surfaceColor,
            elevation: elevation,
          ),
        ),
      );

      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColorWithOverlay));
      expect(model.color, isNot(equals(surfaceColor)));
    });
393 394
  });

395
  group('Transparency clipping', () {
396
    testWidgets('No clip by default', (WidgetTester tester) async {
397
      final GlobalKey materialKey = GlobalKey();
398
      await tester.pumpWidget(
399
          Material(
400 401 402
            key: materialKey,
            type: MaterialType.transparency,
            child: const SizedBox(width: 100.0, height: 100.0),
403
          ),
404 405 406
      );

      expect(find.byKey(materialKey), hasNoImmediateClip);
407
    });
408 409

    testWidgets('clips to bounding rect by default given Clip.antiAlias', (WidgetTester tester) async {
410
      final GlobalKey materialKey = GlobalKey();
411
      await tester.pumpWidget(
412
        Material(
413 414
          key: materialKey,
          type: MaterialType.transparency,
415 416
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
417
        ),
418 419
      );

420
      expect(find.byKey(materialKey), clipsWithBoundingRect);
421 422
    });

423
    testWidgets('clips to rounded rect when borderRadius provided given Clip.antiAlias', (WidgetTester tester) async {
424
      final GlobalKey materialKey = GlobalKey();
425
      await tester.pumpWidget(
426
        Material(
427 428
          key: materialKey,
          type: MaterialType.transparency,
429
          borderRadius: const BorderRadius.all(Radius.circular(10.0)),
430 431
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
432
        ),
433 434 435 436 437
      );

      expect(
        find.byKey(materialKey),
        clipsWithBoundingRRect(
438
          borderRadius: const BorderRadius.all(Radius.circular(10.0))
439 440 441
        ),
      );
    });
442

443
    testWidgets('clips to shape when provided given Clip.antiAlias', (WidgetTester tester) async {
444
      final GlobalKey materialKey = GlobalKey();
445
      await tester.pumpWidget(
446
        Material(
447 448 449
          key: materialKey,
          type: MaterialType.transparency,
          shape: const StadiumBorder(),
450 451
          child: const SizedBox(width: 100.0, height: 100.0),
          clipBehavior: Clip.antiAlias,
452
        ),
453 454 455 456 457 458 459 460 461
      );

      expect(
        find.byKey(materialKey),
        clipsWithShapeBorder(
          shape: const StadiumBorder(),
        ),
      );
    });
462 463 464 465 466 467 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 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524

    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',
      ]);
    });
525 526 527 528
  });

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

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

    testWidgets('canvas with borderRadius and elevation', (WidgetTester tester) async {
546
      final GlobalKey materialKey = GlobalKey();
547
      await tester.pumpWidget(
548
        Material(
549 550
          key: materialKey,
          type: MaterialType.canvas,
551
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
552 553
          child: const SizedBox(width: 100.0, height: 100.0),
          elevation: 1.0,
554
        ),
555 556 557 558
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
559
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
560 561 562 563
          elevation: 1.0,
      ));
    });

564
    testWidgets('canvas with shape and elevation', (WidgetTester tester) async {
565
      final GlobalKey materialKey = GlobalKey();
566
      await tester.pumpWidget(
567
        Material(
568 569 570 571 572
          key: materialKey,
          type: MaterialType.canvas,
          shape: const StadiumBorder(),
          child: const SizedBox(width: 100.0, height: 100.0),
          elevation: 1.0,
573
        ),
574 575 576 577 578 579 580 581
      );

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

582
    testWidgets('card', (WidgetTester tester) async {
583
      final GlobalKey materialKey = GlobalKey();
584
      await tester.pumpWidget(
585
        Material(
586 587 588
          key: materialKey,
          type: MaterialType.card,
          child: const SizedBox(width: 100.0, height: 100.0),
589
        ),
590 591 592 593
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
594
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
595 596 597 598 599
          elevation: 0.0,
      ));
    });

    testWidgets('card with borderRadius and elevation', (WidgetTester tester) async {
600
      final GlobalKey materialKey = GlobalKey();
601
      await tester.pumpWidget(
602
        Material(
603 604
          key: materialKey,
          type: MaterialType.card,
605
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
606 607
          elevation: 5.0,
          child: const SizedBox(width: 100.0, height: 100.0),
608
        ),
609 610 611 612
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
613
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
614 615 616 617
          elevation: 5.0,
      ));
    });

618
    testWidgets('card with shape and elevation', (WidgetTester tester) async {
619
      final GlobalKey materialKey = GlobalKey();
620
      await tester.pumpWidget(
621
        Material(
622 623 624 625 626
          key: materialKey,
          type: MaterialType.card,
          shape: const StadiumBorder(),
          elevation: 5.0,
          child: const SizedBox(width: 100.0, height: 100.0),
627
        ),
628 629 630 631 632 633 634 635
      );

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

636
    testWidgets('circle', (WidgetTester tester) async {
637
      final GlobalKey materialKey = GlobalKey();
638
      await tester.pumpWidget(
639
        Material(
640 641 642 643
          key: materialKey,
          type: MaterialType.circle,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
644
        ),
645 646 647 648 649 650 651 652 653
      );

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

    testWidgets('button', (WidgetTester tester) async {
654
      final GlobalKey materialKey = GlobalKey();
655
      await tester.pumpWidget(
656
        Material(
657 658 659 660
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
661
        ),
662 663 664 665
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
666
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
667 668 669 670 671
          elevation: 0.0,
      ));
    });

    testWidgets('button with elevation and borderRadius', (WidgetTester tester) async {
672
      final GlobalKey materialKey = GlobalKey();
673
      await tester.pumpWidget(
674
        Material(
675 676 677 678
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
679
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
680
          elevation: 4.0,
681
        ),
682 683 684 685
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
686
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
687 688 689
          elevation: 4.0,
      ));
    });
690 691

    testWidgets('button with elevation and shape', (WidgetTester tester) async {
692
      final GlobalKey materialKey = GlobalKey();
693
      await tester.pumpWidget(
694
        Material(
695 696 697 698 699 700
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
          shape: const StadiumBorder(),
          elevation: 4.0,
701
        ),
702 703 704 705 706 707 708
      );

      expect(find.byKey(materialKey), rendersOnPhysicalShape(
          shape: const StadiumBorder(),
          elevation: 4.0,
      ));
    });
709
  });
710 711 712

  group('Border painting', () {
    testWidgets('border is painted on physical layers', (WidgetTester tester) async {
713
      final GlobalKey materialKey = GlobalKey();
714
      await tester.pumpWidget(
715
        Material(
716 717 718 719 720
          key: materialKey,
          type: MaterialType.button,
          child: const SizedBox(width: 100.0, height: 100.0),
          color: const Color(0xFF0000FF),
          shape: const CircleBorder(
721
            side: BorderSide(
722
              width: 2.0,
723
              color: Color(0xFF0000FF),
724
            ),
725
          ),
726
        ),
727 728 729 730 731 732 733
      );

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

    testWidgets('border is painted for transparent material', (WidgetTester tester) async {
734
      final GlobalKey materialKey = GlobalKey();
735
      await tester.pumpWidget(
736
        Material(
737 738 739 740
          key: materialKey,
          type: MaterialType.transparency,
          child: const SizedBox(width: 100.0, height: 100.0),
          shape: const CircleBorder(
741
            side: BorderSide(
742
              width: 2.0,
743
              color: Color(0xFF0000FF),
744
            ),
745
          ),
746
        ),
747 748 749 750 751 752 753
      );

      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 {
754
      final GlobalKey materialKey = GlobalKey();
755
      await tester.pumpWidget(
756
        Material(
757 758 759 760
          key: materialKey,
          type: MaterialType.transparency,
          child: const SizedBox(width: 100.0, height: 100.0),
          shape: const CircleBorder(),
761
        ),
762 763 764 765 766
      );

      final RenderBox box = tester.renderObject(find.byKey(materialKey));
      expect(box, isNot(paints..circle()));
    });
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790

    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,
791
                      ),
792 793 794 795
                    ],
                  ),
                ),
              ),
796 797
            ),
          ),
798 799 800 801 802
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
803
        matchesGoldenFile('material.border_paint_above.png'),
804
      );
805
    });
806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830

    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,
831
                      ),
832 833 834 835
                    ],
                  ),
                ),
              ),
836 837
            ),
          ),
838 839 840 841 842
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
843
        matchesGoldenFile('material.border_paint_below.png'),
844
      );
845
    });
846
  });
847
}