Unverified Commit f477c8b1 authored by Ankur Jain's avatar Ankur Jain Committed by GitHub

Update accessibility contrast test coverage (#109784)

parent fa2deadd
...@@ -331,27 +331,54 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -331,27 +331,54 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
if (shouldSkipNode(data)) { if (shouldSkipNode(data)) {
return result; return result;
} }
final String text = data.label.isEmpty ? data.value : data.label;
final Iterable<Element> elements = find.text(text).hitTestable().evaluate();
for (final Element element in elements) {
result += await _evaluateElement(node, element, tester, image, byteData);
}
return result;
}
Future<Evaluation> _evaluateElement(
SemanticsNode node,
Element element,
WidgetTester tester,
ui.Image image,
ByteData byteData,
) async {
// Look up inherited text properties to determine text size and weight. // Look up inherited text properties to determine text size and weight.
late bool isBold; late bool isBold;
double? fontSize; double? fontSize;
final String text = data.label.isEmpty ? data.value : data.label;
final List<Element> elements = find.text(text).hitTestable().evaluate().toList();
late final Rect paintBounds; late final Rect paintBounds;
late final Rect paintBoundsWithOffset;
if (elements.length == 1) {
final Element element = elements.single;
final RenderObject? renderBox = element.renderObject; final RenderObject? renderBox = element.renderObject;
if (renderBox is! RenderBox) { if (renderBox is! RenderBox) {
throw StateError('Unexpected renderObject type: $renderBox'); throw StateError('Unexpected renderObject type: $renderBox');
} }
const Offset offset = Offset(4.0, 4.0); const Offset offset = Offset(4.0, 4.0);
paintBounds = Rect.fromPoints( paintBoundsWithOffset = Rect.fromPoints(
renderBox.localToGlobal(renderBox.paintBounds.topLeft - offset), renderBox.localToGlobal(renderBox.paintBounds.topLeft - offset),
renderBox.localToGlobal(renderBox.paintBounds.bottomRight + offset), renderBox.localToGlobal(renderBox.paintBounds.bottomRight + offset),
); );
paintBounds = Rect.fromPoints(
renderBox.localToGlobal(renderBox.paintBounds.topLeft),
renderBox.localToGlobal(renderBox.paintBounds.bottomRight),
);
final Offset? nodeOffset = node.transform != null ? MatrixUtils.getAsTranslation(node.transform!) : null;
final Rect nodeBounds = node.rect.shift(nodeOffset ?? Offset.zero);
final Rect intersection = nodeBounds.intersect(paintBounds);
if (intersection.width <= 0 || intersection.height <= 0) {
// Skip this element since it doesn't correspond to the given semantic
// node.
return const Evaluation.pass();
}
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) {
...@@ -367,25 +394,16 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -367,25 +394,16 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
} else { } else {
throw StateError('Unexpected widget type: ${widget.runtimeType}'); throw StateError('Unexpected widget type: ${widget.runtimeType}');
} }
} else if (elements.length > 1) {
return Evaluation.fail(
'Multiple nodes with the same label: ${data.label}\n',
);
} else {
// If we can't find the text node then assume the label does not
// correspond to actual text.
return result;
}
if (isNodeOffScreen(paintBounds, tester.binding.window)) { if (isNodeOffScreen(paintBoundsWithOffset, tester.binding.window)) {
return result; return const Evaluation.pass();
} }
final Map<Color, int> colorHistogram = _colorsWithinRect(byteData, paintBounds, image.width, image.height); final Map<Color, int> colorHistogram = _colorsWithinRect(byteData, paintBoundsWithOffset, image.width, image.height);
// Node was too far off screen. // Node was too far off screen.
if (colorHistogram.isEmpty) { if (colorHistogram.isEmpty) {
return result; return const Evaluation.pass();
} }
final _ContrastReport report = _ContrastReport(colorHistogram); final _ContrastReport report = _ContrastReport(colorHistogram);
...@@ -394,10 +412,9 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { ...@@ -394,10 +412,9 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline {
final double targetContrastRatio = this.targetContrastRatio(fontSize, bold: isBold); final double targetContrastRatio = this.targetContrastRatio(fontSize, bold: isBold);
if (contrastRatio - targetContrastRatio >= _tolerance) { if (contrastRatio - targetContrastRatio >= _tolerance) {
return result + const Evaluation.pass(); return const Evaluation.pass();
} }
return result + return Evaluation.fail(
Evaluation.fail(
'$node:\n' '$node:\n'
'Expected contrast ratio of at least $targetContrastRatio ' 'Expected contrast ratio of at least $targetContrastRatio '
'but found ${contrastRatio.toStringAsFixed(2)} ' 'but found ${contrastRatio.toStringAsFixed(2)} '
......
...@@ -23,6 +23,61 @@ void main() { ...@@ -23,6 +23,61 @@ void main() {
handle.dispose(); handle.dispose();
}); });
testWidgets('Multiple text with same label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
_boilerplate(
Column(
children: const <Widget>[
Text(
'this is a test',
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
Text(
'this is a test',
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
],
),
),
);
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
testWidgets(
'Multiple text with same label but Nodes excluded from '
'semantic tree have failing contrast should pass a11y guideline ',
(WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
_boilerplate(
Column(
children: const <Widget>[
Text(
'this is a test',
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
SizedBox(height: 50),
Text(
'this is a test',
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
SizedBox(height: 50),
ExcludeSemantics(
child: Text(
'this is a test',
style: TextStyle(fontSize: 14.0, color: Colors.white),
),
),
],
),
),
);
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
testWidgets('white text on black background - Text Widget - direct style', testWidgets('white text on black background - Text Widget - direct style',
(WidgetTester tester) async { (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics(); final SemanticsHandle handle = tester.ensureSemantics();
......
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