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