Unverified Commit 313059cd authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add labeled tappable target guideline and gallery tests (#22824)

parent 3188e5c6
...@@ -124,6 +124,41 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline { ...@@ -124,6 +124,41 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline {
String get description => 'Tappable objects should be at least $size'; String get description => 'Tappable objects should be at least $size';
} }
/// A guideline which enforces that all nodes with a tap or long press action
/// also have a label.
@visibleForTesting
class LabeledTapTargetGuideline extends AccessibilityGuideline {
const LabeledTapTargetGuideline._();
@override
String get description => 'Tappable widgets should have a semantic label';
@override
FutureOr<Evaluation> evaluate(WidgetTester tester) {
final SemanticsNode root = tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;
Evaluation traverse(SemanticsNode node) {
Evaluation result = const Evaluation.pass();
node.visitChildren((SemanticsNode child) {
result += traverse(child);
return true;
});
if (node.isMergedIntoParent)
return result;
final SemanticsData data = node.getSemanticsData();
// Skip node if it has no actions, or is marked as hidden.
if (!data.hasAction(ui.SemanticsAction.longPress) && !data.hasAction(ui.SemanticsAction.tap))
return result;
if (data.label == null || data.label.isEmpty) {
result += Evaluation.fail(
'$node: expected tappable node to have semantic label, but none was found\n',
);
}
return result;
}
return traverse(root);
}
}
/// A guideline which verifies that all nodes that contribute semantics via text /// A guideline which verifies that all nodes that contribute semantics via text
/// meet minimum contrast levels. /// meet minimum contrast levels.
/// ///
...@@ -403,3 +438,7 @@ const AccessibilityGuideline iOSTapTargetGuideline = MinimumTapTargetGuideline._ ...@@ -403,3 +438,7 @@ const AccessibilityGuideline iOSTapTargetGuideline = MinimumTapTargetGuideline._
/// foreground and background colors. The contrast ratio is calculated from /// foreground and background colors. The contrast ratio is calculated from
/// these colors according to the [WCAG](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#contrast-ratiodef) /// these colors according to the [WCAG](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#contrast-ratiodef)
const AccessibilityGuideline textContrastGuideline = MinimumTextContrastGuideline._(); const AccessibilityGuideline textContrastGuideline = MinimumTextContrastGuideline._();
/// A guideline which enforces that all nodes with a tap or long press action
/// also have a label.
const AccessibilityGuideline labeledTapTargetGuideline = LabeledTapTargetGuideline._();
...@@ -361,6 +361,63 @@ void main() { ...@@ -361,6 +361,63 @@ void main() {
handle.dispose(); handle.dispose();
}); });
}); });
group('Labeled tappable node guideline', () {
testWidgets('Passes when node is labeled', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(Semantics(
container: true,
child: const SizedBox(width: 10.0, height: 10.0),
onTap: () {},
label: 'test',
)));
final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
expect(result.passed, true);
handle.dispose();
});
testWidgets('Fails if long-press has no label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(Semantics(
container: true,
child: const SizedBox(width: 10.0, height: 10.0),
onLongPress: () {},
label: '',
)));
final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
expect(result.passed, false);
handle.dispose();
});
testWidgets('Fails if tap has no label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(Semantics(
container: true,
child: const SizedBox(width: 10.0, height: 10.0),
onTap: () {},
label: '',
)));
final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
expect(result.passed, false);
handle.dispose();
});
testWidgets('Passes if tap is merged into labeled node', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(Semantics(
container: true,
onLongPress: () {},
label: '',
child: Semantics(
label: 'test',
child: const SizedBox(width: 10.0, height: 10.0),
),
)));
final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
expect(result.passed, true);
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