blob: e4bf4271df936dede98ca43171c5627b8deec57c [file]
// Copyright 2014 The Flutter 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/rendering.dart';
import 'package:flutter/src/foundation/_features.dart';
import 'package:flutter/src/widgets/_accessibility_evaluations.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'widgets_app_tester.dart';
void main() {
group('MinimumTapTargetEvaluation', () {
late final Set<String> originalFeatureFlags;
setUpAll(() {
originalFeatureFlags = {...debugEnabledFeatureFlags};
debugEnabledFeatureFlags.add('accessibility_evaluations');
});
tearDownAll(() {
debugEnabledFeatureFlags.clear();
debugEnabledFeatureFlags.addAll(originalFeatureFlags);
});
const evaluation = MinimumTapTargetEvaluation(size: Size(48.0, 48.0));
testWidgets('passes for valid targets', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: SizedBox.square(
dimension: 48.0,
child: Semantics(label: 'button', onTap: () {}),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('fails for small targets', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Center(
child: SizedBox.square(
dimension: 40.0,
child: Semantics(label: 'button', onTap: () {}),
),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, hasLength(1));
expect(
result.violations.first.reason,
contains('expected tap target size of at least Size(48.0, 48.0)'),
);
handle.dispose();
});
testWidgets('skips hidden nodes', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: SizedBox.square(
dimension: 40.0,
child: Semantics(label: 'button', onTap: () {}, hidden: true),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
});
group('LabeledTapTargetEvaluation', () {
late final Set<String> originalFeatureFlags;
setUpAll(() {
originalFeatureFlags = {...debugEnabledFeatureFlags};
debugEnabledFeatureFlags.add('accessibility_evaluations');
});
tearDownAll(() {
debugEnabledFeatureFlags.clear();
debugEnabledFeatureFlags.addAll(originalFeatureFlags);
});
const evaluation = LabeledTapTargetEvaluation();
testWidgets('passes for labeled targets', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: SizedBox.square(
dimension: 48.0,
child: Semantics(label: 'button', onTap: () {}),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('fails for unlabeled targets', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: SizedBox(width: 48.0, height: 48.0, child: Semantics(onTap: () {})),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, hasLength(1));
expect(
result.violations.first.reason,
contains('expected tappable node to have semantic label'),
);
handle.dispose();
});
});
group('MinimumTextContrastEvaluation', () {
late final Set<String> originalFeatureFlags;
setUpAll(() {
originalFeatureFlags = {...debugEnabledFeatureFlags};
debugEnabledFeatureFlags.add('accessibility_evaluations');
});
tearDownAll(() {
debugEnabledFeatureFlags.clear();
debugEnabledFeatureFlags.addAll(originalFeatureFlags);
});
const evaluation = MinimumTextContrastEvaluation(
minNormalTextContrastRatio: 4.5,
minLargeTextContrastRatio: 3.0,
);
testWidgets('passes for high contrast', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
const TestWidgetsApp(
home: SizedBox.square(
dimension: 100.0,
child: ColoredBox(
color: Color(0xFFFFFFFF),
child: Center(
child: Text('test', style: TextStyle(color: Color(0xFF000000), fontSize: 14)),
),
),
),
),
);
final EvaluationResult? result = await tester.runAsync<EvaluationResult>(() async {
return await evaluation.evaluate(tester.binding);
});
expect(result!.violations, isEmpty);
handle.dispose();
});
testWidgets('fails for low contrast', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
const TestWidgetsApp(
home: SizedBox.square(
dimension: 100,
child: ColoredBox(
color: Color(0xFFFFFFFF),
child: Center(
child: Text('test', style: TextStyle(color: Color(0xFFEEEEEE), fontSize: 14)),
),
),
),
),
);
final EvaluationResult? result = await tester.runAsync<EvaluationResult>(() async {
return await evaluation.evaluate(tester.binding);
});
expect(result!.violations, hasLength(1));
expect(result.violations.first.reason, contains('Expected contrast ratio of at least 4.5'));
handle.dispose();
});
});
group('UnlabeledLeafNodeEvaluation', () {
late final Set<String> originalFeatureFlags;
setUpAll(() {
originalFeatureFlags = {...debugEnabledFeatureFlags};
debugEnabledFeatureFlags.add('accessibility_evaluations');
});
tearDownAll(() {
debugEnabledFeatureFlags.clear();
debugEnabledFeatureFlags.addAll(originalFeatureFlags);
});
const evaluation = UnlabeledLeafNodeEvaluation();
testWidgets('Passes if node has label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
label: 'test',
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Passes if node has tooltip', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
tooltip: 'test',
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Passes if node has value', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
onTap: () {},
value: 'test',
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Passes if node has hint', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
onTap: () {},
hint: 'test',
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Fails if node has no label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
onTap: () {},
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, hasLength(1));
expect(
result.violations.first.reason,
contains('expected leaf semantics node to have a label'),
);
handle.dispose();
});
testWidgets('Fails if button node has no label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
button: true,
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, hasLength(1));
expect(
result.violations.first.reason,
contains('expected leaf semantics node to have a label'),
);
handle.dispose();
});
testWidgets('Fails if focusable node has no label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
focusable: true,
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, hasLength(1));
expect(
result.violations.first.reason,
contains('expected leaf semantics node to have a label'),
);
handle.dispose();
});
testWidgets('Passes if actionable node is hidden', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
hidden: true,
button: true,
child: const SizedBox(width: 10.0, height: 10.0),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Passes if node is not focusable or actionable', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
// For a container without any actions or focusable flags, even though it has no label,
// it should not produce a violation because it is not "important".
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(container: true, child: const SizedBox(width: 10.0, height: 10.0)),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Passes if node is not a leaf', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: Semantics(
container: true,
onTap: () {},
child: Column(
children: <Widget>[
Semantics(
label: 'Child 1',
onTap: () {},
child: const SizedBox(width: 10, height: 10),
),
],
),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty);
handle.dispose();
});
testWidgets('Passes if node merges descendants', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: MergeSemantics(
child: Column(
children: <Widget>[
Semantics(
label: 'Child 1',
onTap: () {},
child: const SizedBox(width: 10, height: 10),
),
],
),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, isEmpty); // merged node has label from child.
handle.dispose();
});
testWidgets('Fails if node merges descendants but has no label', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
TestWidgetsApp(
home: MergeSemantics(
child: Semantics(
container: true,
onTap: () {},
child: const SizedBox(width: 10, height: 10),
),
),
),
);
final EvaluationResult result = await evaluation.evaluate(tester.binding);
expect(result.violations, hasLength(1));
expect(
result.violations.first.reason,
contains('expected leaf semantics node to have a label'),
);
handle.dispose();
});
});
}