Unverified Commit 715cb513 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Fix bugs in contrast guideline and improve heuristic (#31000)

parent 301eaa8c
...@@ -501,7 +501,7 @@ void main() { ...@@ -501,7 +501,7 @@ void main() {
await tester.pumpWidget(MaterialApp(theme: theme, home: BottomAppBarDemo())); await tester.pumpWidget(MaterialApp(theme: theme, home: BottomAppBarDemo()));
await expectLater(tester, meetsGuideline(textContrastGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose(); handle.dispose();
}); }, skip: theme == ThemeData.light());
testWidgets('bottom_navigation_demo $themeName', (WidgetTester tester) async { testWidgets('bottom_navigation_demo $themeName', (WidgetTester tester) async {
tester.binding.addTime(const Duration(seconds: 3)); tester.binding.addTime(const Duration(seconds: 3));
......
...@@ -200,7 +200,7 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -200,7 +200,7 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
final ByteData byteData = await tester.binding.runAsync<ByteData>(() async { final ByteData byteData = await tester.binding.runAsync<ByteData>(() async {
// Needs to be the same pixel ratio otherwise our dimensions won't match the // Needs to be the same pixel ratio otherwise our dimensions won't match the
// last transform layer. // last transform layer.
image = await layer.toImage(renderView.paintBounds, pixelRatio: 1.0); image = await layer.toImage(renderView.paintBounds, pixelRatio: 1 / 3);
return image.toByteData(); return image.toByteData();
}); });
...@@ -214,10 +214,12 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -214,10 +214,12 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
children.add(child); children.add(child);
return true; return true;
}); });
for (SemanticsNode child in children) for (SemanticsNode child in children) {
result += await evaluateNode(child); result += await evaluateNode(child);
if (_shouldSkipNode(data)) }
if (_shouldSkipNode(data)) {
return result; return result;
}
// We need to look up the inherited text properties to determine the // We need to look up the inherited text properties to determine the
// contrast ratio based on text size/weight. // contrast ratio based on text size/weight.
...@@ -225,14 +227,22 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -225,14 +227,22 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
bool isBold; bool isBold;
final String text = (data.label?.isEmpty == true) ? data.value : data.label; final String text = (data.label?.isEmpty == true) ? data.value : data.label;
final List<Element> elements = find.text(text).hitTestable().evaluate().toList(); final List<Element> elements = find.text(text).hitTestable().evaluate().toList();
Rect paintBounds;
if (elements.length == 1) { if (elements.length == 1) {
final Element element = elements.single; final Element element = elements.single;
final RenderBox renderObject = element.renderObject;
element.renderObject.paintBounds;
paintBounds = Rect.fromPoints(
renderObject.localToGlobal(element.renderObject.paintBounds.topLeft - const Offset(4.0, 4.0)),
renderObject.localToGlobal(element.renderObject.paintBounds.bottomRight + const Offset(4.0, 4.0)),
);
final Widget widget = element.widget; final Widget widget = element.widget;
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(element); final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(element);
if (widget is Text) { if (widget is Text) {
TextStyle effectiveTextStyle = widget.style; TextStyle effectiveTextStyle = widget.style;
if (widget.style == null || widget.style.inherit) if (widget.style == null || widget.style.inherit) {
effectiveTextStyle = defaultTextStyle.style.merge(widget.style); effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
}
fontSize = effectiveTextStyle.fontSize; fontSize = effectiveTextStyle.fontSize;
isBold = effectiveTextStyle.fontWeight == FontWeight.bold; isBold = effectiveTextStyle.fontWeight == FontWeight.bold;
} else if (widget is EditableText) { } else if (widget is EditableText) {
...@@ -249,21 +259,14 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -249,21 +259,14 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
return result; return result;
} }
// Transform local coordinate to screen coordinates. if (_isNodeOffScreen(paintBounds)) {
Rect paintBounds = node.rect;
SemanticsNode current = node;
while (current != null && current.parent != null) {
if (current.transform != null)
paintBounds = MatrixUtils.transformRect(current.transform, paintBounds);
paintBounds = paintBounds.shift(current.parent?.rect?.topLeft ?? Offset.zero);
current = current.parent;
}
if (_isNodeOffScreen(paintBounds))
return result; return result;
}
final List<int> subset = _subsetToRect(byteData, paintBounds, image.width, image.height); final List<int> subset = _subsetToRect(byteData, paintBounds, image.width, image.height);
// Node was too far off screen. // Node was too far off screen.
if (subset.isEmpty) if (subset.isEmpty) {
return result; return result;
}
final _ContrastReport report = _ContrastReport(subset); final _ContrastReport report = _ContrastReport(subset);
final double contrastRatio = report.contrastRatio(); final double contrastRatio = report.contrastRatio();
const double delta = -0.01; const double delta = -0.01;
...@@ -342,8 +345,9 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -342,8 +345,9 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
class _ContrastReport { class _ContrastReport {
factory _ContrastReport(List<int> colors) { factory _ContrastReport(List<int> colors) {
final Map<int, int> colorHistogram = <int, int>{}; final Map<int, int> colorHistogram = <int, int>{};
for (int color in colors) for (int color in colors) {
colorHistogram[color] = (colorHistogram[color] ?? 0) + 1; colorHistogram[color] = (colorHistogram[color] ?? 0) + 1;
}
if (colorHistogram.length == 1) { if (colorHistogram.length == 1) {
final Color hslColor = Color(colorHistogram.keys.first); final Color hslColor = Color(colorHistogram.keys.first);
return _ContrastReport._(hslColor, hslColor); return _ContrastReport._(hslColor, hslColor);
......
...@@ -92,13 +92,17 @@ void main() { ...@@ -92,13 +92,17 @@ void main() {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Scaffold( home: Scaffold(
body: Center( body: Center(
child: TextField( child: SizedBox(
controller: TextEditingController(text: 'this is a test'), width: 100,
child: TextField(
controller: TextEditingController(text: 'this is a test'),
),
), ),
), ),
), ),
), ),
); );
await tester.idle();
await expectLater(tester, meetsGuideline(textContrastGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose(); handle.dispose();
}); });
...@@ -121,9 +125,9 @@ void main() { ...@@ -121,9 +125,9 @@ void main() {
expect(result.reason, expect(result.reason,
'SemanticsNode#21(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), label: "this is a test",' 'SemanticsNode#21(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), label: "this is a test",'
' textDirection: ltr):\nExpected contrast ratio of at least ' ' textDirection: ltr):\nExpected contrast ratio of at least '
'4.5 but found 0.88 for a font size of 14.0. ' '4.5 but found 1.17 for a font size of 14.0. The '
'The computed foreground color was: Color(0xffffeb3b), ' 'computed foreground color was: Color(0xfffafafa), The computed background color was:'
'The computed background color was: Color(0xffffff00)\n' ' Color(0xffffeb3b)\n'
'See also: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html'); 'See also: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html');
handle.dispose(); handle.dispose();
}); });
...@@ -418,6 +422,24 @@ void main() { ...@@ -418,6 +422,24 @@ void main() {
}); });
}); });
testWidgets('regression test for material widget', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Scaffold(
backgroundColor: Colors.white,
body: RaisedButton(
color: const Color(0xFFFBBC04),
elevation: 0,
onPressed: () {},
child: const Text('Button', style: TextStyle(color: Colors.black)),
),
),
));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
} }
Widget _boilerplate(Widget child) { Widget _boilerplate(Widget child) {
......
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