Commit 2144a896 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fill in the test for button color splash and highlights (#9224)

Fixes https://github.com/flutter/flutter/pull/8643

Fixes https://github.com/flutter/flutter/issues/8804 by adding a test
to verify that Material elevation changes animate.

Also tweak some dartdocs, add some trailing commas, improve some
toString output, make the inkwell test agnostic to the PhysicalModel
logic, and move the material buttons test to the material test
directory.
parent 954b35bf
......@@ -113,9 +113,11 @@ class ButtonTheme extends InheritedWidget {
}
}
/// A material design button.
/// The framework for building material design buttons.
///
/// Rather than using this class directly, consider using [FlatButton] or [RaisedButton].
/// Rather than using this class directly, consider using [FlatButton] or
/// [RaisedButton], which configure this class with appropriate defaults that
/// match the material design specification.
///
/// MaterialButtons whose [onPressed] handler is null will be disabled. To have
/// an enabled button, make sure to pass a non-null value for onPressed.
......
......@@ -117,7 +117,7 @@ class Material extends StatefulWidget {
this.color,
this.textStyle,
this.borderRadius,
this.child
this.child,
}) : super(key: key) {
assert(type != null);
assert(elevation != null);
......@@ -180,8 +180,10 @@ class Material extends StatefulWidget {
description.add('elevation: $elevation');
if (color != null)
description.add('color: $color');
if (textStyle != null)
description.add('textStyle: $textStyle');
if (textStyle != null) {
for (String entry in '$textStyle'.split('\n'))
description.add('textStyle.$entry');
}
if (borderRadius != null)
description.add('borderRadius: $borderRadius');
}
......
......@@ -55,6 +55,26 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
..configuration = createLocalImageConfiguration(context)
..position = position;
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
String label;
if (position != null) {
switch (position) {
case DecorationPosition.background:
label = 'bg';
break;
case DecorationPosition.foreground:
label = 'fg';
break;
}
} else {
description.add('position: NULL');
label = 'decoration';
}
description.add(decoration != null ? '$label: $decoration' : 'no decoration');
}
}
/// A convenience widget that combines common painting, positioning, and sizing
......
......@@ -7,7 +7,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
......@@ -41,100 +42,86 @@ void main() {
semantics.dispose();
});
Widget _buttonWidget({
Key buttonKey,
Key materialKey,
Color color, Color
highlightColor,
Color splashColor,
double minWidth = 150.0,
double height = 60.0,
bool useTheme = false
}) {
final Key definedMaterialKey = materialKey ?? new UniqueKey();
final Key definedButtonKey = buttonKey ?? new UniqueKey();
testWidgets('Does button highlight + splash colors work if set directly', (WidgetTester tester) async {
final Color directSplashColor = new Color(0xFF000011);
final Color directHighlightColor = new Color(0xFF000011);
Widget buttonWidget = new Material(
key: definedMaterialKey,
child: new Center(
child: new MaterialButton(
key: definedButtonKey,
color: color,
highlightColor: !useTheme ? highlightColor : null,
splashColor: !useTheme ? splashColor : null,
minWidth: minWidth,
height: height,
onPressed: () { },
splashColor: directSplashColor,
highlightColor: directHighlightColor,
onPressed: () { /* to make sure the button is enabled */ },
),
),
);
if (useTheme) {
final ThemeData themeData = new ThemeData(
accentColor: color,
highlightColor: highlightColor,
splashColor: splashColor,
);
buttonWidget = new Theme(
data: themeData,
child: buttonWidget,
);
}
return buttonWidget;
}
testWidgets('Does button highlight + splash colors work if set directly', (WidgetTester tester) async {
final Color buttonColor = new Color(0xFFFFFF00);
final Color highlightColor = new Color(0xDD0000FF);
final Color splashColor = new Color(0xAA0000FF);
final Key materialKey = new UniqueKey();
final Key buttonKey = new UniqueKey();
await tester.pumpWidget(
_buttonWidget(
materialKey: materialKey,
buttonKey: buttonKey,
color: buttonColor,
highlightColor: highlightColor,
splashColor: splashColor,
new Theme(
data: new ThemeData(),
child: buttonWidget,
),
);
final Point center = tester.getCenter(find.byKey(buttonKey));
final Point center = tester.getCenter(find.byType(MaterialButton));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(new Duration(milliseconds: 200));
await tester.pump(); // start gesture
await tester.pump(new Duration(milliseconds: 200)); // wait for splash to be well under way
expect(
Material.of(tester.element(find.byType(MaterialButton))),
paints
..circle(color: directSplashColor)
..rrect(color: directHighlightColor)
);
// TODO(ianh) - the object returned by renderObject does not contain splash or highlights (??)
final Color themeSplashColor1 = new Color(0xFF001100);
final Color themeHighlightColor1 = new Color(0xFF001100);
await gesture.up();
});
buttonWidget = new Material(
child: new Center(
child: new MaterialButton(
onPressed: () { /* to make sure the button is enabled */ },
),
),
);
testWidgets('Does button highlight color work if set via theme', (WidgetTester tester) async {
final Color buttonColor = new Color(0xFFFFFF00);
final Color highlightColor = new Color(0xDD0000FF);
final Color splashColor = new Color(0xAA0000FF);
await tester.pumpWidget(
new Theme(
data: new ThemeData(
highlightColor: themeHighlightColor1,
splashColor: themeSplashColor1,
),
child: buttonWidget,
),
);
final Key materialKey = new UniqueKey();
final Key buttonKey = new UniqueKey();
expect(
Material.of(tester.element(find.byType(MaterialButton))),
paints
..circle(color: themeSplashColor1)
..rrect(color: themeHighlightColor1)
);
final Color themeSplashColor2 = new Color(0xFF002200);
final Color themeHighlightColor2 = new Color(0xFF002200);
await tester.pumpWidget(
_buttonWidget(
useTheme: true, // use a theme wrapper
materialKey: materialKey,
buttonKey: buttonKey,
color: buttonColor,
highlightColor: highlightColor,
splashColor: splashColor,
new Theme(
data: new ThemeData(
highlightColor: themeHighlightColor2,
splashColor: themeSplashColor2,
),
child: buttonWidget, // same widget, so does not get updated because of us
),
);
final Point center = tester.getCenter(find.byKey(buttonKey));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(new Duration(milliseconds: 200));
// TODO(ianh) - the object returned by renderObject does not contain splash or highlights (??)
expect(
Material.of(tester.element(find.byType(MaterialButton))),
paints
..circle(color: themeSplashColor2)
..rrect(color: themeHighlightColor2)
);
await gesture.up();
});
......
......@@ -9,8 +9,6 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
Material.debugEnablePhysicalModel = true;
testWidgets('Does the ink widget render a border radius', (WidgetTester tester) async {
final Color highlightColor = new Color(0xAAFF0000);
final Color splashColor = new Color(0xAA0000FF);
......@@ -38,7 +36,7 @@ void main() {
await tester.pump(); // start gesture
await tester.pump(new Duration(milliseconds: 200)); // wait for splash to be well under way
final RenderBox box = tester.renderObject<RenderPhysicalModel>(find.byType(PhysicalModel)).child;
final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as dynamic;
expect(
box,
paints
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
class NotifyMaterial extends StatelessWidget {
......@@ -13,6 +14,25 @@ class NotifyMaterial extends StatelessWidget {
}
}
Widget buildMaterial(int elevation) {
return new Center(
child: new SizedBox(
height: 100.0,
width: 100.0,
child: new Material(
color: const Color(0xFF00FF00),
elevation: elevation,
),
),
);
}
List<BoxShadow> getShadow(WidgetTester tester) {
final RenderDecoratedBox box = tester.renderObject(find.byType(DecoratedBox).first);
final BoxDecoration decoration = box.decoration;
return decoration.boxShadow;
}
class PaintRecorder extends CustomPainter {
PaintRecorder(this.log);
......@@ -95,4 +115,69 @@ void main() {
expect(log, isEmpty);
});
testWidgets('Shadows animate smoothly', (WidgetTester tester) async {
await tester.pumpWidget(buildMaterial(0));
final List<BoxShadow> shadowA = getShadow(tester);
await tester.pumpWidget(buildMaterial(9));
final List<BoxShadow> shadowB = getShadow(tester);
await tester.pump(const Duration(milliseconds: 1));
final List<BoxShadow> shadowC = getShadow(tester);
await tester.pump(kThemeChangeDuration ~/ 2);
final List<BoxShadow> shadowD = getShadow(tester);
await tester.pump(kThemeChangeDuration);
final List<BoxShadow> shadowE = getShadow(tester);
// This code verifies the following:
// 1. When the elevation is zero, there's no shadow.
// 2. When the elevation isn't zero, there's three shadows.
// 3. When the elevation changes from 0 to 9, one millisecond later, the
// shadows are still more or less indistinguishable from zero.
// 4. Have a kThemeChangeDuration later, they are distinguishable form
// zero.
// 5. ...but still distinguishable from the actual 9 elevation.
// 6. After kThemeChangeDuration, the shadows are exactly the elevation 9
// shadows.
// The point being to verify that the shadows animate, and do so
// continually, not in discrete increments (e.g. not jumping from elevation
// 0 to 1 to 2 to 3 and so forth).
// TODO(ianh): Port this test when we turn the physical model back on.
// 1
expect(shadowA, isNull);
expect(shadowB, isNull);
// 2
expect(shadowC, hasLength(3));
// 3
expect(shadowC[0].offset.dy, closeTo(0.0, 0.001));
expect(shadowC[1].offset.dy, closeTo(0.0, 0.001));
expect(shadowC[2].offset.dy, closeTo(0.0, 0.001));
expect(shadowC[0].blurRadius, closeTo(0.0, 0.001));
expect(shadowC[1].blurRadius, closeTo(0.0, 0.001));
expect(shadowC[2].blurRadius, closeTo(0.0, 0.001));
expect(shadowC[0].spreadRadius, closeTo(0.0, 0.001));
expect(shadowC[1].spreadRadius, closeTo(0.0, 0.001));
expect(shadowC[2].spreadRadius, closeTo(0.0, 0.001));
// 4
expect(shadowD[0].offset.dy, isNot(closeTo(0.0, 0.001)));
expect(shadowD[1].offset.dy, isNot(closeTo(0.0, 0.001)));
expect(shadowD[2].offset.dy, isNot(closeTo(0.0, 0.001)));
expect(shadowD[0].blurRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[1].blurRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[2].blurRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[0].spreadRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[1].spreadRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[2].spreadRadius, isNot(closeTo(0.0, 0.001)));
// 5
expect(shadowD[0], isNot(shadowE[0]));
expect(shadowD[1], isNot(shadowE[1]));
expect(shadowD[2], isNot(shadowE[2]));
// 6
expect(shadowE, kElevationToShadow[9]);
});
}
......@@ -18,8 +18,11 @@ import 'package:flutter/rendering.dart';
/// ```
///
/// In some cases it may be useful to define a subclass that overrides the
/// Canvas methods the test is checking and squirrels away the parameters
/// [Canvas] methods the test is checking and squirrels away the parameters
/// that the test requires.
///
/// For simple tests, consider using the [paints] matcher, which overlays a
/// pattern matching API over [TestRecordingCanvas].
class TestRecordingCanvas implements Canvas {
/// All of the method calls on this canvas.
final List<Invocation> invocations = <Invocation>[];
......@@ -70,8 +73,7 @@ class TestRecordingPaintingContext implements PaintingContext {
}
@override
void noSuchMethod(Invocation invocation) {
}
void noSuchMethod(Invocation invocation) { }
}
class _MethodCall implements Invocation {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment