material_test.dart 27.8 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
  // Regression test for https://github.com/flutter/flutter/issues/81504
  testWidgets('MaterialApp.home nullable and update test', (WidgetTester tester) async {
    // _WidgetsAppState._usesNavigator == true
    await tester.pumpWidget(const MaterialApp(home: SizedBox.shrink()));

    // _WidgetsAppState._usesNavigator == false
    await tester.pumpWidget(const MaterialApp()); // Do not crash!

    // _WidgetsAppState._usesNavigator == true
    await tester.pumpWidget(const MaterialApp(home: SizedBox.shrink())); // Do not crash!

    expect(tester.takeException(), null);
  });

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),
98
      shadowColor: Color(0xffff0000),
99 100 101 102 103 104 105 106 107 108 109 110
      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)',
111
      'shadowColor: Color(0xffff0000)',
112 113
      'textStyle.inherit: true',
      'textStyle.color: Color(0xff00ff00)',
114
      'borderRadius: BorderRadiusDirectional.circular(10.0)',
115 116 117
    ]);
  });

118
  testWidgets('LayoutChangedNotification test', (WidgetTester tester) async {
119
    await tester.pumpWidget(
120
      const Material(
121
        child: NotifyMaterial(),
122
      ),
123 124
    );
  });
125 126

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

    await tester.pumpWidget(
130
      Directionality(
131
        textDirection: TextDirection.ltr,
132
        child: Column(
133
          children: <Widget>[
134
            SizedBox(
135 136
              width: 150.0,
              height: 150.0,
137 138
              child: CustomPaint(
                painter: PaintRecorder(log),
139
              ),
140
            ),
141 142 143
            Expanded(
              child: Material(
                child: Column(
144
                  children: <Widget>[
145 146
                    Expanded(
                      child: ListView(
147
                        children: <Widget>[
148
                          Container(
149 150 151 152 153
                            height: 2000.0,
                            color: const Color(0xFF00FF00),
                          ),
                        ],
                      ),
154
                    ),
155
                    SizedBox(
156 157
                      width: 100.0,
                      height: 100.0,
158 159
                      child: CustomPaint(
                        painter: PaintRecorder(log),
160
                      ),
161
                    ),
162 163
                  ],
                ),
164 165
              ),
            ),
166 167
          ],
        ),
168 169 170 171 172 173 174 175 176 177 178
      ),
    );

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

179
    await tester.drag(find.byType(ListView), const Offset(0.0, -300.0));
180 181 182 183
    await tester.pump();

    expect(log, isEmpty);
  });
184 185

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

189
    await tester.pumpWidget(buildMaterial(elevation: 0.0));
190
    final RenderPhysicalShape modelA = getModel(tester);
191 192
    expect(modelA.elevation, equals(0.0));

193
    await tester.pumpWidget(buildMaterial(elevation: 9.0));
194
    final RenderPhysicalShape modelB = getModel(tester);
195
    expect(modelB.elevation, equals(0.0));
196 197

    await tester.pump(const Duration(milliseconds: 1));
198
    final RenderPhysicalShape modelC = getModel(tester);
199
    expect(modelC.elevation, moreOrLessEquals(0.0, epsilon: 0.001));
200 201

    await tester.pump(kThemeChangeDuration ~/ 2);
202
    final RenderPhysicalShape modelD = getModel(tester);
203
    expect(modelD.elevation, isNot(moreOrLessEquals(0.0, epsilon: 0.001)));
204 205

    await tester.pump(kThemeChangeDuration);
206
    final RenderPhysicalShape modelE = getModel(tester);
207
    expect(modelE.elevation, equals(9.0));
208
  });
209 210

  testWidgets('Shadow colors animate smoothly', (WidgetTester tester) async {
211
    // This code verifies that the PhysicalModel's shadowColor animates over
212 213 214
    // a kThemeChangeDuration time interval.

    await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFF00FF00)));
215
    final RenderPhysicalShape modelA = getModel(tester);
216 217 218
    expect(modelA.shadowColor, equals(const Color(0xFF00FF00)));

    await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFFFF0000)));
219
    final RenderPhysicalShape modelB = getModel(tester);
220 221 222
    expect(modelB.shadowColor, equals(const Color(0xFF00FF00)));

    await tester.pump(const Duration(milliseconds: 1));
223
    final RenderPhysicalShape modelC = getModel(tester);
224
    expect(modelC.shadowColor, within<Color>(distance: 1, from: const Color(0xFF00FF00)));
225 226

    await tester.pump(kThemeChangeDuration ~/ 2);
227
    final RenderPhysicalShape modelD = getModel(tester);
228
    expect(modelD.shadowColor, isNot(within<Color>(distance: 1, from: const Color(0xFF00FF00))));
229 230

    await tester.pump(kThemeChangeDuration);
231
    final RenderPhysicalShape modelE = getModel(tester);
232 233
    expect(modelE.shadowColor, equals(const Color(0xFFFF0000)));
  });
234

235 236 237 238 239 240 241 242
  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>[
243
              ElevatedButton(
244 245 246
                onPressed: () {
                  pressed = true;
                },
247
                child: null,
248
              ),
249
              const Material(
250
                type: MaterialType.transparency,
251
                child: SizedBox(
252 253 254 255 256 257 258 259 260
                  width: 400.0,
                  height: 500.0,
                ),
              ),
            ],
          ),
        ),
      ),
    );
261
    await tester.tap(find.byType(ElevatedButton));
262 263 264
    expect(pressed, isTrue);
  });

265 266 267 268 269 270 271 272 273
  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),
          ),
274 275
          child: buildMaterial(color: surfaceColor, elevation: 8.0),
      ));
276 277 278 279
      final RenderPhysicalShape model = getModel(tester);
      expect(model.color, equals(surfaceColor));
    });

280 281 282 283
    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;

284
      // The colors we should get with a base surface color of 0xFF121212 for
285
      // and a given elevation
286 287
      const List<ElevationColor> elevationColors = <ElevationColor>[
        ElevationColor(0.0, Color(0xFF121212)),
288 289 290 291 292 293 294 295 296
        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)),
297 298
      ];

299
      for (final ElevationColor test in elevationColors) {
300 301 302 303
        await tester.pumpWidget(
            Theme(
              data: ThemeData(
                applyElevationOverlayColor: true,
304 305 306 307
                colorScheme: const ColorScheme.dark().copyWith(
                  surface: surfaceColor,
                  onSurface: onSurfaceColor,
                ),
308 309 310 311 312
              ),
              child: buildMaterial(
                color: surfaceColor,
                elevation: test.elevation,
              ),
313
            ),
314 315 316 317 318 319 320
        );
        await tester.pumpAndSettle(); // wait for the elevation animation to finish
        final RenderPhysicalShape model = getModel(tester);
        expect(model.color, equals(test.color));
      }
    });

321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
    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));
    });

339
    testWidgets('overlay will not apply to materials using a light theme', (WidgetTester tester) async {
340 341 342 343
      await tester.pumpWidget(
          Theme(
            data: ThemeData(
              applyElevationOverlayColor: true,
344
              colorScheme: const ColorScheme.light(),
345 346
            ),
            child: buildMaterial(
347 348
              color: Colors.cyan,
              elevation: 8.0,
349
            ),
350
          ),
351 352
      );
      final RenderPhysicalShape model = getModel(tester);
353
      // Shouldn't change, as it was under a light color scheme.
354 355 356
      expect(model.color, equals(Colors.cyan));
    });

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
    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)));
    });
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

    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)));
    });
407 408
  });

409
  group('Transparency clipping', () {
410
    testWidgets('No clip by default', (WidgetTester tester) async {
411
      final GlobalKey materialKey = GlobalKey();
412
      await tester.pumpWidget(
413
          Material(
414 415 416
            key: materialKey,
            type: MaterialType.transparency,
            child: const SizedBox(width: 100.0, height: 100.0),
417
          ),
418 419 420
      );

      expect(find.byKey(materialKey), hasNoImmediateClip);
421
    });
422 423

    testWidgets('clips to bounding rect by default 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
          clipBehavior: Clip.antiAlias,
430
          child: const SizedBox(width: 100.0, height: 100.0),
431
        ),
432 433
      );

434
      expect(find.byKey(materialKey), clipsWithBoundingRect);
435 436
    });

437
    testWidgets('clips to rounded rect when borderRadius provided given Clip.antiAlias', (WidgetTester tester) async {
438
      final GlobalKey materialKey = GlobalKey();
439
      await tester.pumpWidget(
440
        Material(
441 442
          key: materialKey,
          type: MaterialType.transparency,
443
          borderRadius: const BorderRadius.all(Radius.circular(10.0)),
444
          clipBehavior: Clip.antiAlias,
445
          child: const SizedBox(width: 100.0, height: 100.0),
446
        ),
447 448 449 450 451
      );

      expect(
        find.byKey(materialKey),
        clipsWithBoundingRRect(
452
          borderRadius: const BorderRadius.all(Radius.circular(10.0)),
453 454 455
        ),
      );
    });
456

457
    testWidgets('clips to shape when provided given Clip.antiAlias', (WidgetTester tester) async {
458
      final GlobalKey materialKey = GlobalKey();
459
      await tester.pumpWidget(
460
        Material(
461 462 463
          key: materialKey,
          type: MaterialType.transparency,
          shape: const StadiumBorder(),
464
          clipBehavior: Clip.antiAlias,
465
          child: const SizedBox(width: 100.0, height: 100.0),
466
        ),
467 468 469 470 471 472 473 474 475
      );

      expect(
        find.byKey(materialKey),
        clipsWithShapeBorder(
          shape: const StadiumBorder(),
        ),
      );
    });
476 477 478 479 480 481 482 483 484

    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,
          clipBehavior: Clip.antiAlias,
485
          child: const SizedBox(width: 100.0, height: 100.0),
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 525 526 527 528 529 530 531 532 533 534 535 536 537 538
        );
      }
      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',
      ]);
    });
539 540 541 542
  });

  group('PhysicalModels', () {
    testWidgets('canvas', (WidgetTester tester) async {
543
      final GlobalKey materialKey = GlobalKey();
544
      await tester.pumpWidget(
545
        Material(
546 547
          key: materialKey,
          type: MaterialType.canvas,
548
          child: const SizedBox(width: 100.0, height: 100.0),
549
        ),
550 551 552 553 554 555 556 557 558 559
      );

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

    testWidgets('canvas with borderRadius and elevation', (WidgetTester tester) async {
560
      final GlobalKey materialKey = GlobalKey();
561
      await tester.pumpWidget(
562
        Material(
563 564
          key: materialKey,
          type: MaterialType.canvas,
565
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
566
          elevation: 1.0,
567
          child: const SizedBox(width: 100.0, height: 100.0),
568
        ),
569 570 571 572
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
573
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
574 575 576 577
          elevation: 1.0,
      ));
    });

578
    testWidgets('canvas with shape and elevation', (WidgetTester tester) async {
579
      final GlobalKey materialKey = GlobalKey();
580
      await tester.pumpWidget(
581
        Material(
582 583 584 585
          key: materialKey,
          type: MaterialType.canvas,
          shape: const StadiumBorder(),
          elevation: 1.0,
586
          child: const SizedBox(width: 100.0, height: 100.0),
587
        ),
588 589 590 591 592 593 594 595
      );

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

596
    testWidgets('card', (WidgetTester tester) async {
597
      final GlobalKey materialKey = GlobalKey();
598
      await tester.pumpWidget(
599
        Material(
600 601 602
          key: materialKey,
          type: MaterialType.card,
          child: const SizedBox(width: 100.0, height: 100.0),
603
        ),
604 605 606 607
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
608
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
609 610 611 612 613
          elevation: 0.0,
      ));
    });

    testWidgets('card with borderRadius and elevation', (WidgetTester tester) async {
614
      final GlobalKey materialKey = GlobalKey();
615
      await tester.pumpWidget(
616
        Material(
617 618
          key: materialKey,
          type: MaterialType.card,
619
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
620 621
          elevation: 5.0,
          child: const SizedBox(width: 100.0, height: 100.0),
622
        ),
623 624 625 626
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
627
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
628 629 630 631
          elevation: 5.0,
      ));
    });

632
    testWidgets('card with shape and elevation', (WidgetTester tester) async {
633
      final GlobalKey materialKey = GlobalKey();
634
      await tester.pumpWidget(
635
        Material(
636 637 638 639 640
          key: materialKey,
          type: MaterialType.card,
          shape: const StadiumBorder(),
          elevation: 5.0,
          child: const SizedBox(width: 100.0, height: 100.0),
641
        ),
642 643 644 645 646 647 648 649
      );

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

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

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

    testWidgets('button', (WidgetTester tester) async {
668
      final GlobalKey materialKey = GlobalKey();
669
      await tester.pumpWidget(
670
        Material(
671 672 673
          key: materialKey,
          type: MaterialType.button,
          color: const Color(0xFF0000FF),
674
          child: const SizedBox(width: 100.0, height: 100.0),
675
        ),
676 677 678 679
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
680
          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
681 682 683 684 685
          elevation: 0.0,
      ));
    });

    testWidgets('button with elevation and borderRadius', (WidgetTester tester) async {
686
      final GlobalKey materialKey = GlobalKey();
687
      await tester.pumpWidget(
688
        Material(
689 690 691
          key: materialKey,
          type: MaterialType.button,
          color: const Color(0xFF0000FF),
692
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
693
          elevation: 4.0,
694
          child: const SizedBox(width: 100.0, height: 100.0),
695
        ),
696 697 698 699
      );

      expect(find.byKey(materialKey), rendersOnPhysicalModel(
          shape: BoxShape.rectangle,
700
          borderRadius: const BorderRadius.all(Radius.circular(6.0)),
701 702 703
          elevation: 4.0,
      ));
    });
704 705

    testWidgets('button with elevation and shape', (WidgetTester tester) async {
706
      final GlobalKey materialKey = GlobalKey();
707
      await tester.pumpWidget(
708
        Material(
709 710 711 712 713
          key: materialKey,
          type: MaterialType.button,
          color: const Color(0xFF0000FF),
          shape: const StadiumBorder(),
          elevation: 4.0,
714
          child: const SizedBox(width: 100.0, height: 100.0),
715
        ),
716 717 718 719 720 721 722
      );

      expect(find.byKey(materialKey), rendersOnPhysicalShape(
          shape: const StadiumBorder(),
          elevation: 4.0,
      ));
    });
723
  });
724 725 726

  group('Border painting', () {
    testWidgets('border is painted on physical layers', (WidgetTester tester) async {
727
      final GlobalKey materialKey = GlobalKey();
728
      await tester.pumpWidget(
729
        Material(
730 731 732 733
          key: materialKey,
          type: MaterialType.button,
          color: const Color(0xFF0000FF),
          shape: const CircleBorder(
734
            side: BorderSide(
735
              width: 2.0,
736
              color: Color(0xFF0000FF),
737
            ),
738
          ),
739
          child: const SizedBox(width: 100.0, height: 100.0),
740
        ),
741 742 743 744 745 746 747
      );

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

    testWidgets('border is painted for transparent material', (WidgetTester tester) async {
748
      final GlobalKey materialKey = GlobalKey();
749
      await tester.pumpWidget(
750
        Material(
751 752 753
          key: materialKey,
          type: MaterialType.transparency,
          shape: const CircleBorder(
754
            side: BorderSide(
755
              width: 2.0,
756
              color: Color(0xFF0000FF),
757
            ),
758
          ),
759
          child: const SizedBox(width: 100.0, height: 100.0),
760
        ),
761 762 763 764 765 766 767
      );

      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 {
768
      final GlobalKey materialKey = GlobalKey();
769
      await tester.pumpWidget(
770
        Material(
771 772 773
          key: materialKey,
          type: MaterialType.transparency,
          shape: const CircleBorder(),
774
          child: const SizedBox(width: 100.0, height: 100.0),
775
        ),
776 777 778 779 780
      );

      final RenderBox box = tester.renderObject(find.byKey(materialKey));
      expect(box, isNot(paints..circle()));
    });
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804

    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,
805
                      ),
806 807 808 809
                    ],
                  ),
                ),
              ),
810 811
            ),
          ),
812 813 814 815 816
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
817
        matchesGoldenFile('material.border_paint_above.png'),
818
      );
819
    });
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844

    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,
845
                      ),
846 847 848 849
                    ],
                  ),
                ),
              ),
850 851
            ),
          ),
852 853 854 855 856
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
857
        matchesGoldenFile('material.border_paint_below.png'),
858
      );
859
    });
860
  });
861
}