From 178ad8aba8ac1077f7a5dd087e209c8a809129dd Mon Sep 17 00:00:00 2001 From: amirh Date: Tue, 30 Jan 2018 12:54:52 -0800 Subject: [PATCH] Add unit tests for the Material widget (for clipping and elevation) (#14334) --- .../flutter/test/material/material_test.dart | 163 ++++++++++++++++++ packages/flutter_test/lib/src/matchers.dart | 140 +++++++++++++++ 2 files changed, 303 insertions(+) diff --git a/packages/flutter/test/material/material_test.dart b/packages/flutter/test/material/material_test.dart index 2aa31ee378..59e4e366cd 100644 --- a/packages/flutter/test/material/material_test.dart +++ b/packages/flutter/test/material/material_test.dart @@ -165,4 +165,167 @@ void main() { final RenderPhysicalModel modelE = getShadow(tester); expect(modelE.shadowColor, equals(const Color(0xFFFF0000))); }); + + group('Transparency clipping', () { + testWidgets('clips to bounding rect by default', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new Material( + key: materialKey, + type: MaterialType.transparency, + child: const SizedBox(width: 100.0, height: 100.0) + ) + ); + + expect(find.byKey(materialKey), clipsWithBoundingRect); + }); + + testWidgets('clips to rounded rect when borderRadius provided', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new Material( + key: materialKey, + type: MaterialType.transparency, + borderRadius: const BorderRadius.all(const Radius.circular(10.0)), + child: const SizedBox(width: 100.0, height: 100.0) + ) + ); + + expect( + find.byKey(materialKey), + clipsWithBoundingRRect( + borderRadius: const BorderRadius.all(const Radius.circular(10.0)) + ), + ); + }); + }); + + group('PhysicalModels', () { + testWidgets('canvas', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new 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 = new GlobalKey(); + await tester.pumpWidget( + new Material( + key: materialKey, + type: MaterialType.canvas, + borderRadius: const BorderRadius.all(const 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(const Radius.circular(5.0)), + elevation: 1.0, + )); + }); + + testWidgets('card', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new 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(const Radius.circular(2.0)), + elevation: 0.0, + )); + }); + + testWidgets('card with borderRadius and elevation', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new Material( + key: materialKey, + type: MaterialType.card, + borderRadius: const BorderRadius.all(const 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(const Radius.circular(5.0)), + elevation: 5.0, + )); + }); + + testWidgets('circle', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new 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 = new GlobalKey(); + await tester.pumpWidget( + new 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(const Radius.circular(2.0)), + elevation: 0.0, + )); + }); + + testWidgets('button with elevation and borderRadius', (WidgetTester tester) async { + final GlobalKey materialKey = new GlobalKey(); + await tester.pumpWidget( + new Material( + key: materialKey, + type: MaterialType.button, + child: const SizedBox(width: 100.0, height: 100.0), + color: const Color(0xFF0000FF), + borderRadius: const BorderRadius.all(const Radius.circular(6.0)), + elevation: 4.0, + ) + ); + + expect(find.byKey(materialKey), rendersOnPhysicalModel( + shape: BoxShape.rectangle, + borderRadius: const BorderRadius.all(const Radius.circular(6.0)), + elevation: 4.0, + )); + }); + }); } diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index d309f47ad2..54ca7e625b 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; import 'package:test/test.dart'; @@ -780,3 +781,142 @@ class _IsMethodCall extends Matcher { } } +/// Asserts that a [Finder] locates a single object whose root RenderObject +/// is a [RenderClipRect] with no clipper set. +const Matcher clipsWithBoundingRect = const _ClipsWithBoundingRect(); + +/// Asserts that a [Finder] locates a single object whose root RenderObject +/// is a [RenderClipRRect] with no clipper set, and border radius equals to +/// [borderRadius]. +Matcher clipsWithBoundingRRect({@required BorderRadius borderRadius}) { + return new _ClipsWithBoundingRRect(borderRadius: borderRadius); +} + +/// Asserts that a [Finder] locates a single object whose root RenderObject +/// is a [RenderPhysicalModel]. +/// +/// - If [shape] is non null asserts that [RenderPhysicalModel.shape] is equal to +/// [shape]. +/// - If [borderRadius] is non null asserts that [RenderPhysicalModel.borderRadius] is equal to +/// [borderRadius]. +/// - If [elevation] is non null asserts that [RenderPhysicalModel.elevation] is equal to +/// [elevation]. +Matcher rendersOnPhysicalModel({ + BoxShape shape, + BorderRadius borderRadius, + double elevation, +}) { + return new _RendersOnPhysicalModel( + shape: shape, + borderRadius: borderRadius, + elevation: elevation, + ); +} + +abstract class _MatchRenderObject extends Matcher { + const _MatchRenderObject(); + + bool renderObjectMatches(Map matchState, T renderObject); + + @override + bool matches(covariant Finder finder, Map matchState) { + final Iterable nodes = finder.evaluate(); + if (nodes.length != 1) + return failWithDescription(matchState, 'did not have a exactly one child element'); + final RenderObject renderObject = nodes.single.renderObject; + if (renderObject.runtimeType != T) + return failWithDescription(matchState, 'had a root render object of type: ${renderObject.runtimeType}'); + + return renderObjectMatches(matchState, renderObject); + } + + bool failWithDescription(Map matchState, String description) { + matchState['failure'] = description; + return false; + } + + @override + Description describeMismatch( + dynamic item, + Description mismatchDescription, + Map matchState, + bool verbose + ) { + return mismatchDescription.add(matchState['failure']); + } +} + +class _RendersOnPhysicalModel extends _MatchRenderObject { + const _RendersOnPhysicalModel({ + this.shape, + this.borderRadius, + this.elevation, + }); + + final BoxShape shape; + final BorderRadius borderRadius; + final double elevation; + + @override + bool renderObjectMatches(Map matchState, RenderPhysicalModel renderObject) { + if (shape != null && renderObject.shape != shape) + return failWithDescription(matchState, 'had shape: ${renderObject.shape}'); + + if (borderRadius != null && renderObject.borderRadius != borderRadius) + return failWithDescription(matchState, 'had borderRadius: ${renderObject.borderRadius}'); + + if (elevation != null && renderObject.elevation != elevation) + return failWithDescription(matchState, 'had elevation: ${renderObject.elevation}'); + + return true; + } + + @override + Description describe(Description description) { + description.add('renders on a physical model'); + if (shape != null) + description.add(' with shape $shape'); + if (borderRadius != null) + description.add(' with borderRadius $borderRadius'); + if (elevation != null) + description.add(' with elevation $elevation'); + return description; + } +} + +class _ClipsWithBoundingRect extends _MatchRenderObject { + const _ClipsWithBoundingRect(); + + @override + bool renderObjectMatches(Map matchState, RenderClipRect renderObject) { + if (renderObject.clipper != null) + return failWithDescription(matchState, 'had a non null clipper ${renderObject.clipper}'); + return true; + } + + @override + Description describe(Description description) => + description.add('clips with bounding rectangle'); +} + +class _ClipsWithBoundingRRect extends _MatchRenderObject { + const _ClipsWithBoundingRRect({@required this.borderRadius}); + + final BorderRadius borderRadius; + + + @override + bool renderObjectMatches(Map matchState, RenderClipRRect renderObject) { + if (renderObject.clipper != null) + return failWithDescription(matchState, 'had a non null clipper ${renderObject.clipper}'); + + if (renderObject.borderRadius != borderRadius) + return failWithDescription(matchState, 'had borderRadius: ${renderObject.borderRadius}'); + + return true; + } + + @override + Description describe(Description description) => + description.add('clips with bounding rounded rectangle with borderRadius: $borderRadius'); +}