| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // 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'; |
| import 'package:flutter/painting.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../rendering/mock_canvas.dart'; |
| import '../widgets/test_border.dart' show TestBorder; |
| |
| class NotifyMaterial extends StatelessWidget { |
| const NotifyMaterial({ Key? key }) : super(key: key); |
| @override |
| Widget build(BuildContext context) { |
| LayoutChangedNotification().dispatch(context); |
| return Container(); |
| } |
| } |
| |
| Widget buildMaterial({ |
| double elevation = 0.0, |
| Color shadowColor = const Color(0xFF00FF00), |
| Color color = const Color(0xFF0000FF), |
| }) { |
| return Center( |
| child: SizedBox( |
| height: 100.0, |
| width: 100.0, |
| child: Material( |
| color: color, |
| shadowColor: shadowColor, |
| elevation: elevation, |
| shape: const CircleBorder(), |
| ), |
| ), |
| ); |
| } |
| |
| RenderPhysicalShape getModel(WidgetTester tester) { |
| return tester.renderObject(find.byType(PhysicalShape)); |
| } |
| |
| class PaintRecorder extends CustomPainter { |
| PaintRecorder(this.log); |
| |
| final List<Size> log; |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| log.add(size); |
| final Paint paint = Paint()..color = const Color(0xFF0000FF); |
| canvas.drawRect(Offset.zero & size, paint); |
| } |
| |
| @override |
| bool shouldRepaint(PaintRecorder oldDelegate) => false; |
| } |
| |
| class ElevationColor { |
| const ElevationColor(this.elevation, this.color); |
| final double elevation; |
| final Color color; |
| } |
| |
| void main() { |
| 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), |
| shadowColor: Color(0xffff0000), |
| 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)', |
| 'shadowColor: Color(0xffff0000)', |
| 'textStyle.inherit: true', |
| 'textStyle.color: Color(0xff00ff00)', |
| 'borderRadius: BorderRadiusDirectional.circular(10.0)', |
| ]); |
| }); |
| |
| testWidgets('LayoutChangedNotification test', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const Material( |
| child: NotifyMaterial(), |
| ), |
| ); |
| }); |
| |
| testWidgets('ListView scroll does not repaint', (WidgetTester tester) async { |
| final List<Size> log = <Size>[]; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| SizedBox( |
| width: 150.0, |
| height: 150.0, |
| child: CustomPaint( |
| painter: PaintRecorder(log), |
| ), |
| ), |
| Expanded( |
| child: Material( |
| child: Column( |
| children: <Widget>[ |
| Expanded( |
| child: ListView( |
| children: <Widget>[ |
| Container( |
| height: 2000.0, |
| color: const Color(0xFF00FF00), |
| ), |
| ], |
| ), |
| ), |
| SizedBox( |
| width: 100.0, |
| height: 100.0, |
| child: CustomPaint( |
| painter: PaintRecorder(log), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| // 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(); |
| |
| await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); |
| await tester.pump(); |
| |
| expect(log, isEmpty); |
| }); |
| |
| testWidgets('Shadows animate smoothly', (WidgetTester tester) async { |
| // This code verifies that the PhysicalModel's elevation animates over |
| // a kThemeChangeDuration time interval. |
| |
| await tester.pumpWidget(buildMaterial(elevation: 0.0)); |
| final RenderPhysicalShape modelA = getModel(tester); |
| expect(modelA.elevation, equals(0.0)); |
| |
| await tester.pumpWidget(buildMaterial(elevation: 9.0)); |
| final RenderPhysicalShape modelB = getModel(tester); |
| expect(modelB.elevation, equals(0.0)); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| final RenderPhysicalShape modelC = getModel(tester); |
| expect(modelC.elevation, moreOrLessEquals(0.0, epsilon: 0.001)); |
| |
| await tester.pump(kThemeChangeDuration ~/ 2); |
| final RenderPhysicalShape modelD = getModel(tester); |
| expect(modelD.elevation, isNot(moreOrLessEquals(0.0, epsilon: 0.001))); |
| |
| await tester.pump(kThemeChangeDuration); |
| final RenderPhysicalShape modelE = getModel(tester); |
| expect(modelE.elevation, equals(9.0)); |
| }); |
| |
| testWidgets('Shadow colors animate smoothly', (WidgetTester tester) async { |
| // This code verifies that the PhysicalModel's shadowColor animates over |
| // a kThemeChangeDuration time interval. |
| |
| await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFF00FF00))); |
| final RenderPhysicalShape modelA = getModel(tester); |
| expect(modelA.shadowColor, equals(const Color(0xFF00FF00))); |
| |
| await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFFFF0000))); |
| final RenderPhysicalShape modelB = getModel(tester); |
| expect(modelB.shadowColor, equals(const Color(0xFF00FF00))); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| final RenderPhysicalShape modelC = getModel(tester); |
| expect(modelC.shadowColor, within<Color>(distance: 1, from: const Color(0xFF00FF00))); |
| |
| await tester.pump(kThemeChangeDuration ~/ 2); |
| final RenderPhysicalShape modelD = getModel(tester); |
| expect(modelD.shadowColor, isNot(within<Color>(distance: 1, from: const Color(0xFF00FF00)))); |
| |
| await tester.pump(kThemeChangeDuration); |
| final RenderPhysicalShape modelE = getModel(tester); |
| expect(modelE.shadowColor, equals(const Color(0xFFFF0000))); |
| }); |
| |
| 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>[ |
| ElevatedButton( |
| onPressed: () { |
| pressed = true; |
| }, |
| child: null, |
| ), |
| Material( |
| type: MaterialType.transparency, |
| child: Container( |
| width: 400.0, |
| height: 500.0, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| await tester.tap(find.byType(ElevatedButton)); |
| expect(pressed, isTrue); |
| }); |
| |
| 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), |
| ), |
| child: buildMaterial(color: surfaceColor, elevation: 8.0), |
| )); |
| final RenderPhysicalShape model = getModel(tester); |
| expect(model.color, equals(surfaceColor)); |
| }); |
| |
| 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; |
| |
| // The colors we should get with a base surface color of 0xFF121212 for |
| // and a given elevation |
| const List<ElevationColor> elevationColors = <ElevationColor>[ |
| ElevationColor(0.0, Color(0xFF121212)), |
| 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)), |
| ]; |
| |
| for (final ElevationColor test in elevationColors) { |
| await tester.pumpWidget( |
| Theme( |
| data: ThemeData( |
| applyElevationOverlayColor: true, |
| colorScheme: const ColorScheme.dark().copyWith( |
| surface: surfaceColor, |
| onSurface: onSurfaceColor, |
| ), |
| ), |
| child: buildMaterial( |
| color: surfaceColor, |
| elevation: test.elevation, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); // wait for the elevation animation to finish |
| final RenderPhysicalShape model = getModel(tester); |
| expect(model.color, equals(test.color)); |
| } |
| }); |
| |
| 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)); |
| }); |
| |
| testWidgets('overlay will not apply to materials using a light theme', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Theme( |
| data: ThemeData( |
| applyElevationOverlayColor: true, |
| colorScheme: const ColorScheme.light(), |
| ), |
| child: buildMaterial( |
| color: Colors.cyan, |
| elevation: 8.0, |
| ), |
| ), |
| ); |
| final RenderPhysicalShape model = getModel(tester); |
| // Shouldn't change, as it was under a light color scheme. |
| expect(model.color, equals(Colors.cyan)); |
| }); |
| |
| 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))); |
| }); |
| }); |
| |
| group('Transparency clipping', () { |
| testWidgets('No clip by default', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.transparency, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), hasNoImmediateClip); |
| }); |
| |
| testWidgets('clips to bounding rect by default given Clip.antiAlias', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.transparency, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| clipBehavior: Clip.antiAlias, |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), clipsWithBoundingRect); |
| }); |
| |
| testWidgets('clips to rounded rect when borderRadius provided given Clip.antiAlias', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.transparency, |
| borderRadius: const BorderRadius.all(Radius.circular(10.0)), |
| child: const SizedBox(width: 100.0, height: 100.0), |
| clipBehavior: Clip.antiAlias, |
| ), |
| ); |
| |
| expect( |
| find.byKey(materialKey), |
| clipsWithBoundingRRect( |
| borderRadius: const BorderRadius.all(Radius.circular(10.0)) |
| ), |
| ); |
| }); |
| |
| testWidgets('clips to shape when provided given Clip.antiAlias', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.transparency, |
| shape: const StadiumBorder(), |
| child: const SizedBox(width: 100.0, height: 100.0), |
| clipBehavior: Clip.antiAlias, |
| ), |
| ); |
| |
| expect( |
| find.byKey(materialKey), |
| clipsWithShapeBorder( |
| shape: const StadiumBorder(), |
| ), |
| ); |
| }); |
| |
| 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', |
| ]); |
| }); |
| }); |
| |
| group('PhysicalModels', () { |
| testWidgets('canvas', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.canvas, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.rectangle, |
| borderRadius: BorderRadius.zero, |
| elevation: 0.0, |
| )); |
| }); |
| |
| testWidgets('canvas with borderRadius and elevation', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.canvas, |
| borderRadius: const BorderRadius.all(Radius.circular(5.0)), |
| child: const SizedBox(width: 100.0, height: 100.0), |
| elevation: 1.0, |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.rectangle, |
| borderRadius: const BorderRadius.all(Radius.circular(5.0)), |
| elevation: 1.0, |
| )); |
| }); |
| |
| testWidgets('canvas with shape and elevation', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.canvas, |
| shape: const StadiumBorder(), |
| child: const SizedBox(width: 100.0, height: 100.0), |
| elevation: 1.0, |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalShape( |
| shape: const StadiumBorder(), |
| elevation: 1.0, |
| )); |
| }); |
| |
| testWidgets('card', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.card, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.rectangle, |
| borderRadius: const BorderRadius.all(Radius.circular(2.0)), |
| elevation: 0.0, |
| )); |
| }); |
| |
| testWidgets('card with borderRadius and elevation', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.card, |
| borderRadius: const BorderRadius.all(Radius.circular(5.0)), |
| elevation: 5.0, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.rectangle, |
| borderRadius: const BorderRadius.all(Radius.circular(5.0)), |
| elevation: 5.0, |
| )); |
| }); |
| |
| testWidgets('card with shape and elevation', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.card, |
| shape: const StadiumBorder(), |
| elevation: 5.0, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalShape( |
| shape: const StadiumBorder(), |
| elevation: 5.0, |
| )); |
| }); |
| |
| testWidgets('circle', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.circle, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| color: const Color(0xFF0000FF), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.circle, |
| elevation: 0.0, |
| )); |
| }); |
| |
| testWidgets('button', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.button, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| color: const Color(0xFF0000FF), |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.rectangle, |
| borderRadius: const BorderRadius.all(Radius.circular(2.0)), |
| elevation: 0.0, |
| )); |
| }); |
| |
| testWidgets('button with elevation and borderRadius', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.button, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| color: const Color(0xFF0000FF), |
| borderRadius: const BorderRadius.all(Radius.circular(6.0)), |
| elevation: 4.0, |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalModel( |
| shape: BoxShape.rectangle, |
| borderRadius: const BorderRadius.all(Radius.circular(6.0)), |
| elevation: 4.0, |
| )); |
| }); |
| |
| testWidgets('button with elevation and shape', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.button, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| color: const Color(0xFF0000FF), |
| shape: const StadiumBorder(), |
| elevation: 4.0, |
| ), |
| ); |
| |
| expect(find.byKey(materialKey), rendersOnPhysicalShape( |
| shape: const StadiumBorder(), |
| elevation: 4.0, |
| )); |
| }); |
| }); |
| |
| group('Border painting', () { |
| testWidgets('border is painted on physical layers', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.button, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| color: const Color(0xFF0000FF), |
| shape: const CircleBorder( |
| side: BorderSide( |
| width: 2.0, |
| color: Color(0xFF0000FF), |
| ), |
| ), |
| ), |
| ); |
| |
| final RenderBox box = tester.renderObject(find.byKey(materialKey)); |
| expect(box, paints..circle()); |
| }); |
| |
| testWidgets('border is painted for transparent material', (WidgetTester tester) async { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.transparency, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| shape: const CircleBorder( |
| side: BorderSide( |
| width: 2.0, |
| color: Color(0xFF0000FF), |
| ), |
| ), |
| ), |
| ); |
| |
| 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 { |
| final GlobalKey materialKey = GlobalKey(); |
| await tester.pumpWidget( |
| Material( |
| key: materialKey, |
| type: MaterialType.transparency, |
| child: const SizedBox(width: 100.0, height: 100.0), |
| shape: const CircleBorder(), |
| ), |
| ); |
| |
| final RenderBox box = tester.renderObject(find.byKey(materialKey)); |
| expect(box, isNot(paints..circle())); |
| }); |
| |
| 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, |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| )); |
| |
| await expectLater( |
| find.byKey(painterKey), |
| matchesGoldenFile('material.border_paint_above.png'), |
| ); |
| }); |
| |
| 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, |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| )); |
| |
| await expectLater( |
| find.byKey(painterKey), |
| matchesGoldenFile('material.border_paint_below.png'), |
| ); |
| }); |
| }); |
| } |