material_test.dart 27.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])

9
import 'package:flutter/material.dart';
10
import 'package:flutter/rendering.dart';
11 12
import 'package:flutter_test/flutter_test.dart';

13
import '../rendering/mock_canvas.dart';
14
import '../widgets/test_border.dart' show TestBorder;
15

16
class NotifyMaterial extends StatelessWidget {
17
  const NotifyMaterial({ Key? key }) : super(key: key);
18 19
  @override
  Widget build(BuildContext context) {
20 21
    LayoutChangedNotification().dispatch(context);
    return Container();
22 23 24
  }
}

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

44
RenderPhysicalShape getModel(WidgetTester tester) {
45
  return tester.renderObject(find.byType(PhysicalShape));
46 47
}

48 49 50
class PaintRecorder extends CustomPainter {
  PaintRecorder(this.log);

51
  final List<Size> log;
52 53 54 55

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

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

64 65 66 67 68 69
class ElevationColor {
  const ElevationColor(this.elevation, this.color);
  final double elevation;
  final Color color;
}

70
void main() {
71 72 73 74 75 76 77 78 79 80 81 82 83 84
  // 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);
  });

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  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(
      color: Color(0xFFFFFFFF),
101
      shadowColor: Color(0xffff0000),
102 103 104 105 106 107 108 109 110 111 112 113
      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)',
114
      'shadowColor: Color(0xffff0000)',
115 116
      'textStyle.inherit: true',
      'textStyle.color: Color(0xff00ff00)',
117
      'borderRadius: BorderRadiusDirectional.circular(10.0)',
118 119 120
    ]);
  });

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

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

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

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

182
    await tester.drag(find.byType(ListView), const Offset(0.0, -300.0));
183 184 185 186
    await tester.pump();

    expect(log, isEmpty);
  });
187 188

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

192
    await tester.pumpWidget(buildMaterial());
193
    final RenderPhysicalShape modelA = getModel(tester);
194 195
    expect(modelA.elevation, equals(0.0));

196
    await tester.pumpWidget(buildMaterial(elevation: 9.0));
197
    final RenderPhysicalShape modelB = getModel(tester);
198
    expect(modelB.elevation, equals(0.0));
199 200

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

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

    await tester.pump(kThemeChangeDuration);
209
    final RenderPhysicalShape modelE = getModel(tester);
210
    expect(modelE.elevation, equals(9.0));
211
  });
212 213

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

217
    await tester.pumpWidget(buildMaterial());
218
    final RenderPhysicalShape modelA = getModel(tester);
219 220 221
    expect(modelA.shadowColor, equals(const Color(0xFF00FF00)));

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

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

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

    await tester.pump(kThemeChangeDuration);
234
    final RenderPhysicalShape modelE = getModel(tester);
235 236
    expect(modelE.shadowColor, equals(const Color(0xFFFF0000)));
  });
237

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

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

283 284 285 286
    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;

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

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

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

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

360 361 362 363 364 365 366 367
    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,
368
            colorScheme: const ColorScheme.dark(),
369 370 371 372 373 374 375 376 377 378 379 380
          ),
          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)));
    });
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

    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)));
    });
410 411
  });

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

      expect(find.byKey(materialKey), hasNoImmediateClip);
424
    });
425 426

    testWidgets('clips to bounding rect by default given Clip.antiAlias', (WidgetTester tester) async {
427
      final GlobalKey materialKey = GlobalKey();
428
      await tester.pumpWidget(
429
        Material(
430 431
          key: materialKey,
          type: MaterialType.transparency,
432
          clipBehavior: Clip.antiAlias,
433
          child: const SizedBox(width: 100.0, height: 100.0),
434
        ),
435 436
      );

437
      expect(find.byKey(materialKey), clipsWithBoundingRect);
438 439
    });

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

      expect(
        find.byKey(materialKey),
        clipsWithBoundingRRect(
455
          borderRadius: const BorderRadius.all(Radius.circular(10.0)),
456 457 458
        ),
      );
    });
459

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

      expect(
        find.byKey(materialKey),
        clipsWithShapeBorder(
          shape: const StadiumBorder(),
        ),
      );
    });
479 480 481 482 483 484 485 486 487

    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,
488
          child: const SizedBox(width: 100.0, height: 100.0),
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 539 540 541
        );
      }
      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',
      ]);
    });
542 543 544 545
  });

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

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

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

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

579
    testWidgets('canvas with shape and elevation', (WidgetTester tester) async {
580
      final GlobalKey materialKey = GlobalKey();
581
      await tester.pumpWidget(
582
        Material(
583 584 585
          key: materialKey,
          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

    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,
795 796 797
                  shape: const RoundedRectangleBorder(
                    side: BorderSide(color: Colors.grey, width: 6),
                    borderRadius: BorderRadius.all(Radius.circular(8)),
798 799 800 801 802 803
                  ),
                  child: Column(
                    children: <Widget>[
                      Container(
                        color: Colors.green,
                        height: 150,
804
                      ),
805 806 807 808
                    ],
                  ),
                ),
              ),
809 810
            ),
          ),
811 812 813 814 815
        ),
      ));

      await expectLater(
        find.byKey(painterKey),
816
        matchesGoldenFile('material.border_paint_above.png'),
817
      );
818
    });
819 820 821 822 823 824 825 826 827 828 829 830 831 832

    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,
833 834 835
                  shape: const RoundedRectangleBorder(
                    side: BorderSide(color: Colors.grey, width: 6),
                    borderRadius: BorderRadius.all(Radius.circular(8)),
836 837 838 839 840 841 842
                  ),
                  borderOnForeground: false,
                  child: Column(
                    children: <Widget>[
                      Container(
                        color: Colors.green,
                        height: 150,
843
                      ),
844 845 846 847
                    ],
                  ),
                ),
              ),
848 849
            ),
          ),
850 851 852 853 854
        ),
      ));

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