From 73957b94e7f0e08cef5438b68ae52f6d4fa267c1 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Fri, 3 May 2019 16:13:42 -0700 Subject: [PATCH] Test Material buttons against a11y contrast guidelines (#32050) --- .../test/material/flat_button_test.dart | 29 +++- .../test/material/material_button_test.dart | 37 +++++ .../test/material/outline_button_test.dart | 136 +++++++++++------- .../test/material/raised_button_test.dart | 63 ++++++++ 4 files changed, 210 insertions(+), 55 deletions(-) create mode 100644 packages/flutter/test/material/material_button_test.dart create mode 100644 packages/flutter/test/material/raised_button_test.dart diff --git a/packages/flutter/test/material/flat_button_test.dart b/packages/flutter/test/material/flat_button_test.dart index 097ede9f74..9323b8a47a 100644 --- a/packages/flutter/test/material/flat_button_test.dart +++ b/packages/flutter/test/material/flat_button_test.dart @@ -9,7 +9,7 @@ import 'package:flutter/rendering.dart'; import '../rendering/mock_canvas.dart'; void main() { - testWidgets('FlatButton implements debugFillDescription', (WidgetTester tester) async { + testWidgets('FlatButton implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); FlatButton( onPressed: () { }, @@ -32,6 +32,33 @@ void main() { ]); }); + testWidgets('Default FlatButton meets a11y contrast guidelines', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: FlatButton( + child: const Text('FlatButton'), + onPressed: () { }, + ), + ), + ), + ), + ); + + // Default, not disabled. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + // Highlighted (pressed). + final Offset center = tester.getCenter(find.byType(FlatButton)); + await tester.startGesture(center); + await tester.pump(); // Start the splash and highlight animations. + await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + }, + semanticsEnabled: true, + ); + testWidgets('FlatButton has no clip by default', (WidgetTester tester) async { await tester.pumpWidget( Directionality( diff --git a/packages/flutter/test/material/material_button_test.dart b/packages/flutter/test/material/material_button_test.dart new file mode 100644 index 0000000000..c31676c18c --- /dev/null +++ b/packages/flutter/test/material/material_button_test.dart @@ -0,0 +1,37 @@ +// Copyright 2019 The Chromium 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_test/flutter_test.dart'; +import 'package:flutter/rendering.dart'; + +void main() { + testWidgets('Default MaterialButton meets a11y contrast guidelines', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: MaterialButton( + child: const Text('MaterialButton'), + onPressed: () { }, + ), + ), + ), + ), + ); + + // Default, not disabled. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + // Highlighted (pressed). + final Offset center = tester.getCenter(find.byType(MaterialButton)); + await tester.startGesture(center); + await tester.pump(); // Start the splash and highlight animations. + await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + }, + semanticsEnabled: true, + ); + +} diff --git a/packages/flutter/test/material/outline_button_test.dart b/packages/flutter/test/material/outline_button_test.dart index 8503089009..205e7d080d 100644 --- a/packages/flutter/test/material/outline_button_test.dart +++ b/packages/flutter/test/material/outline_button_test.dart @@ -10,27 +10,58 @@ import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; void main() { - PhysicalModelLayer findPhysicalLayer(Element element) { - expect(element, isNotNull); - RenderObject object = element.renderObject; - while (object != null && object is! RenderRepaintBoundary && object is! RenderView) { - object = object.parent; - } - expect(object.debugLayer, isNotNull); - expect(object.debugLayer.firstChild, isInstanceOf()); - final PhysicalModelLayer layer = object.debugLayer.firstChild; - return layer.firstChild is PhysicalModelLayer ? layer.firstChild : layer; - } + testWidgets('OutlineButton implements debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + OutlineButton( + onPressed: () { }, + textColor: const Color(0xFF00FF00), + disabledTextColor: const Color(0xFFFF0000), + color: const Color(0xFF000000), + highlightColor: const Color(0xFF1565C0), + splashColor: const Color(0xFF9E9E9E), + child: const Text('Hello'), + ).debugFillProperties(builder); + + final List description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()).toList(); + + expect(description, [ + 'textColor: Color(0xff00ff00)', + 'disabledTextColor: Color(0xffff0000)', + 'color: Color(0xff000000)', + 'highlightColor: Color(0xff1565c0)', + 'splashColor: Color(0xff9e9e9e)', + ]); + }); + + testWidgets('Default OutlineButton meets a11y contrast guidelines', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: OutlineButton( + child: const Text('OutlineButton'), + onPressed: () { }, + ), + ), + ), + ), + ); + + // Default, not disabled. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + // Highlighted (pressed). + final Offset center = tester.getCenter(find.byType(OutlineButton)); + await tester.startGesture(center); + await tester.pump(); // Start the splash and highlight animations. + await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + }, + semanticsEnabled: true, + ); - void checkPhysicalLayer(Element element, Color expectedColor, {Path clipPath, Rect clipRect}) { - final PhysicalModelLayer expectedLayer = findPhysicalLayer(element); - expect(expectedLayer.elevation, 0.0); - expect(expectedLayer.color, expectedColor); - if (clipPath != null) { - expect(clipRect, isNotNull); - expect(expectedLayer.clipPath, coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))); - } - } testWidgets('Outline button responds to tap when enabled', (WidgetTester tester) async { int pressedCount = 0; @@ -136,7 +167,7 @@ void main() { outlineButton, paints ..path(color: disabledBorderColor, strokeWidth: borderWidth)); - checkPhysicalLayer( + _checkPhysicalLayer( tester.element(outlineButton), const Color(0), clipPath: clipPath, @@ -158,7 +189,7 @@ void main() { paints ..path(color: borderColor, strokeWidth: borderWidth)); // initially, the interior of the button is transparent - checkPhysicalLayer( + _checkPhysicalLayer( tester.element(outlineButton), fillColor.withAlpha(0x00), clipPath: clipPath, @@ -175,7 +206,7 @@ void main() { outlineButton, paints ..path(color: highlightedBorderColor, strokeWidth: borderWidth)); - checkPhysicalLayer( + _checkPhysicalLayer( tester.element(outlineButton), fillColor.withAlpha(0xFF), clipPath: clipPath, @@ -189,7 +220,7 @@ void main() { outlineButton, paints ..path(color: borderColor, strokeWidth: borderWidth)); - checkPhysicalLayer( + _checkPhysicalLayer( tester.element(outlineButton), fillColor.withAlpha(0x00), clipPath: clipPath, @@ -331,31 +362,6 @@ void main() { expect(tester.getSize(find.byType(Text)).height, equals(42.0)); }); - testWidgets('OutlineButton implements debugFillProperties', (WidgetTester tester) async { - final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); - OutlineButton( - onPressed: () { }, - textColor: const Color(0xFF00FF00), - disabledTextColor: const Color(0xFFFF0000), - color: const Color(0xFF000000), - highlightColor: const Color(0xFF1565C0), - splashColor: const Color(0xFF9E9E9E), - child: const Text('Hello'), - ).debugFillProperties(builder); - - final List description = builder.properties - .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) - .map((DiagnosticsNode node) => node.toString()).toList(); - - expect(description, [ - 'textColor: Color(0xff00ff00)', - 'disabledTextColor: Color(0xffff0000)', - 'color: Color(0xff000000)', - 'highlightColor: Color(0xff1565c0)', - 'splashColor: Color(0xff9e9e9e)', - ]); - }); - testWidgets('OutlineButton pressed fillColor default', (WidgetTester tester) async { Widget buildFrame(ThemeData theme) { return MaterialApp( @@ -385,18 +391,18 @@ void main() { Color fillColor = Colors.grey[850]; // Initially the interior of the button is transparent. - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); + _checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); // Tap-press gesture on the button triggers the fill animation. TestGesture gesture = await tester.startGesture(center); await tester.pump(); // Start the button fill animation. await tester.pump(const Duration(milliseconds: 200)); // Animation is complete. - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF)); + _checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF)); // Tap gesture completes, button returns to its initial configuration. await gesture.up(); await tester.pumpAndSettle(); - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); + _checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); await tester.pumpWidget(buildFrame(ThemeData.light())); await tester.pumpAndSettle(); // Finish the theme change animation. @@ -412,11 +418,33 @@ void main() { gesture = await tester.startGesture(center); await tester.pump(); // Start the button fill animation. await tester.pump(const Duration(milliseconds: 200)); // Animation is complete. - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF)); + _checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF)); // Tap gesture completes, button returns to its initial configuration. await gesture.up(); await tester.pumpAndSettle(); - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); + _checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); }); } + +PhysicalModelLayer _findPhysicalLayer(Element element) { + expect(element, isNotNull); + RenderObject object = element.renderObject; + while (object != null && object is! RenderRepaintBoundary && object is! RenderView) { + object = object.parent; + } + expect(object.debugLayer, isNotNull); + expect(object.debugLayer.firstChild, isInstanceOf()); + final PhysicalModelLayer layer = object.debugLayer.firstChild; + return layer.firstChild is PhysicalModelLayer ? layer.firstChild : layer; +} + +void _checkPhysicalLayer(Element element, Color expectedColor, { Path clipPath, Rect clipRect }) { + final PhysicalModelLayer expectedLayer = _findPhysicalLayer(element); + expect(expectedLayer.elevation, 0.0); + expect(expectedLayer.color, expectedColor); + if (clipPath != null) { + expect(clipRect, isNotNull); + expect(expectedLayer.clipPath, coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))); + } +} diff --git a/packages/flutter/test/material/raised_button_test.dart b/packages/flutter/test/material/raised_button_test.dart new file mode 100644 index 0000000000..67e173206f --- /dev/null +++ b/packages/flutter/test/material/raised_button_test.dart @@ -0,0 +1,63 @@ +// Copyright 2019 The Chromium 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_test/flutter_test.dart'; +import 'package:flutter/rendering.dart'; + +void main() { + testWidgets('RaisedButton implements debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + RaisedButton( + onPressed: () { }, + textColor: const Color(0xFF00FF00), + disabledTextColor: const Color(0xFFFF0000), + color: const Color(0xFF000000), + highlightColor: const Color(0xFF1565C0), + splashColor: const Color(0xFF9E9E9E), + child: const Text('Hello'), + ).debugFillProperties(builder); + + final List description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()).toList(); + + expect(description, [ + 'textColor: Color(0xff00ff00)', + 'disabledTextColor: Color(0xffff0000)', + 'color: Color(0xff000000)', + 'highlightColor: Color(0xff1565c0)', + 'splashColor: Color(0xff9e9e9e)', + ]); + }); + + testWidgets('Default RaisedButton meets a11y contrast guidelines', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: RaisedButton( + child: const Text('RaisedButton'), + onPressed: () { }, + ), + ), + ), + ), + ); + + // Default, not disabled. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + // Highlighted (pressed). + final Offset center = tester.getCenter(find.byType(RaisedButton)); + await tester.startGesture(center); + await tester.pump(); // Start the splash and highlight animations. + await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. + await expectLater(tester, meetsGuideline(textContrastGuideline)); + }, + semanticsEnabled: true, + ); + + +}