| // 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/gestures.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| group('text contrast guideline', () { |
| testWidgets('black text on white background - Text Widget - direct style', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| const 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', (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', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Container( |
| width: 200.0, |
| height: 200.0, |
| color: Colors.black, |
| child: const Text( |
| 'this is a test', |
| style: TextStyle(fontSize: 14.0, color: Colors.white), |
| ), |
| ), |
| ), |
| ); |
| await expectLater(tester, meetsGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| |
| testWidgets('White text on white background fails contrast test', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Container( |
| width: 200.0, |
| height: 300.0, |
| color: Colors.white, |
| child: Column( |
| children: const <Widget>[ |
| Text( |
| 'this is a white text', |
| style: TextStyle(fontSize: 14.0, color: Colors.white), |
| ), |
| SizedBox(height: 50), |
| Text( |
| 'this is a black text test1', |
| style: TextStyle(fontSize: 14.0, color: Colors.black), |
| ), |
| SizedBox(height: 50), |
| Text( |
| 'this is a black text test2', |
| style: TextStyle(fontSize: 14.0, color: Colors.black), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| await expectLater(tester, doesNotMeetGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| |
| const Color surface = Color(0xFFF0F0F0); |
| |
| /// Shades of blue with contrast ratio of 2.9, 4.4, 4.5 from [surface]. |
| const Color blue29 = Color(0xFF7E7EFB); |
| const Color blue44 = Color(0xFF5757FF); |
| const Color blue45 = Color(0xFF5252FF); |
| const List<TextStyle> textStylesMeetingGuideline = <TextStyle>[ |
| TextStyle(color: blue44, backgroundColor: surface, fontSize: 18), |
| TextStyle(color: blue44, backgroundColor: surface, fontSize: 14, fontWeight: FontWeight.bold), |
| TextStyle(color: blue45, backgroundColor: surface), |
| ]; |
| const List<TextStyle> textStylesDoesNotMeetingGuideline = <TextStyle>[ |
| TextStyle(color: blue44, backgroundColor: surface), |
| TextStyle(color: blue29, backgroundColor: surface, fontSize: 18), |
| ]; |
| |
| Widget appWithTextWidget(TextStyle style) => _boilerplate( |
| Text('this is text', style: style.copyWith(height: 30.0)), |
| ); |
| |
| for (final TextStyle style in textStylesMeetingGuideline) { |
| testWidgets('text with style $style', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(appWithTextWidget(style)); |
| await expectLater(tester, meetsGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| } |
| |
| for (final TextStyle style in textStylesDoesNotMeetingGuideline) { |
| testWidgets('text with $style', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(appWithTextWidget(style)); |
| await expectLater(tester, doesNotMeetGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| } |
| |
| testWidgets('black text on white background - Text Widget - direct style', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| const Text( |
| 'this is a test', |
| style: TextStyle(fontSize: 14.0, color: Colors.black), |
| ), |
| ), |
| ); |
| await expectLater(tester, meetsGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| |
| testWidgets('white text on black background - Text Widget - direct style', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Container( |
| width: 200.0, |
| height: 200.0, |
| color: Colors.black, |
| child: const Text( |
| 'this is a test', |
| style: TextStyle(fontSize: 14.0, color: Colors.white), |
| ), |
| ), |
| ), |
| ); |
| await expectLater(tester, meetsGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Material text field - amber on amber', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Container( |
| width: 200.0, |
| height: 200.0, |
| color: Colors.amberAccent, |
| child: TextField( |
| style: const TextStyle(color: Colors.amber), |
| controller: TextEditingController(text: 'this is a test'), |
| ), |
| ), |
| ), |
| ); |
| await expectLater(tester, doesNotMeetGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Material text field - default style', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| SizedBox( |
| width: 100, |
| child: TextField( |
| controller: TextEditingController(text: 'this is a test'), |
| ), |
| ), |
| ), |
| ); |
| await tester.idle(); |
| await expectLater(tester, meetsGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| |
| testWidgets('yellow text on yellow background fails with correct message', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Container( |
| width: 200.0, |
| height: 200.0, |
| color: Colors.yellow, |
| child: const Text( |
| 'this is a test', |
| style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent), |
| ), |
| ), |
| ), |
| ); |
| final Evaluation result = await textContrastGuideline.evaluate(tester); |
| expect(result.passed, false); |
| expect( |
| result.reason, |
| 'SemanticsNode#4(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), ' |
| 'label: "this is a test", textDirection: ltr):\n' |
| 'Expected contrast ratio of at least 4.5 but found 1.17 for a font ' |
| 'size of 14.0.\n' |
| 'The computed colors was:\n' |
| 'light - Color(0xfffafafa), dark - Color(0xffffeb3b)\n' |
| 'See also: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html', |
| ); |
| handle.dispose(); |
| }); |
| |
| testWidgets('label without corresponding text is skipped', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Semantics( |
| label: 'This is not text', |
| container: true, |
| child: const SizedBox( |
| width: 200.0, |
| height: 200.0, |
| child: Placeholder(), |
| ), |
| ), |
| ), |
| ); |
| |
| final Evaluation result = await textContrastGuideline.evaluate(tester); |
| expect(result.passed, true); |
| handle.dispose(); |
| }); |
| |
| testWidgets('offscreen text is skipped', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| Stack( |
| children: <Widget>[ |
| Positioned( |
| left: -300.0, |
| child: Container( |
| width: 200.0, |
| height: 200.0, |
| color: Colors.yellow, |
| child: const Text( |
| 'this is a test', |
| style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| final Evaluation result = await textContrastGuideline.evaluate(tester); |
| expect(result.passed, true); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Disabled button is excluded from text contrast guideline', |
| (WidgetTester tester) async { |
| // Regression test https://github.com/flutter/flutter/issues/94428 |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget( |
| _boilerplate( |
| ElevatedButton( |
| onPressed: null, |
| child: Container( |
| width: 200.0, |
| height: 200.0, |
| color: Colors.yellow, |
| child: const Text( |
| 'this is a test', |
| style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent), |
| ), |
| ), |
| ), |
| ), |
| ); |
| await expectLater(tester, meetsGuideline(textContrastGuideline)); |
| handle.dispose(); |
| }); |
| }); |
| |
| group('custom minimum contrast guideline', () { |
| Widget iconWidget({ |
| IconData icon = Icons.search, |
| required Color color, |
| required Color background, |
| }) { |
| return Container( |
| padding: const EdgeInsets.all(8.0), |
| color: background, |
| child: Icon(icon, color: color), |
| ); |
| } |
| |
| Widget textWidget({ |
| String text = 'Text', |
| required Color color, |
| required Color background, |
| }) { |
| return Container( |
| padding: const EdgeInsets.all(8.0), |
| color: background, |
| child: Text(text, style: TextStyle(color: color)), |
| ); |
| } |
| |
| Widget rowWidget(List<Widget> widgets) => _boilerplate(Row(children: widgets)); |
| |
| final Finder findIcons = find.byWidgetPredicate((Widget widget) => widget is Icon); |
| final Finder findTexts = find.byWidgetPredicate((Widget widget) => widget is Text); |
| final Finder findIconsAndTexts = find.byWidgetPredicate((Widget widget) => widget is Icon || widget is Text); |
| |
| testWidgets('Black icons on white background', (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.black, background: Colors.white), |
| iconWidget(color: Colors.black, background: Colors.white), |
| ])); |
| |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('Black icons on black background', (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.black, background: Colors.black), |
| iconWidget(color: Colors.black, background: Colors.black), |
| ])); |
| |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('White icons on black background ("dark mode")', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.white, background: Colors.black), |
| iconWidget(color: Colors.white, background: Colors.black), |
| ])); |
| |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('Using different icons', (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.black, background: Colors.white, icon: Icons.more_horiz), |
| iconWidget(color: Colors.black, background: Colors.white, icon: Icons.description), |
| iconWidget(color: Colors.black, background: Colors.white, icon: Icons.image), |
| iconWidget(color: Colors.black, background: Colors.white, icon: Icons.beach_access), |
| ])); |
| |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('One invalid instance fails entire test', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.black, background: Colors.white), |
| iconWidget(color: Colors.black, background: Colors.black), |
| ])); |
| |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('White on different colors, passing', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.white, background: Colors.red[800]!, icon: Icons.more_horiz), |
| iconWidget(color: Colors.white, background: Colors.green[800]!, icon: Icons.description), |
| iconWidget(color: Colors.white, background: Colors.blue[800]!, icon: Icons.image), |
| iconWidget(color: Colors.white, background: Colors.purple[800]!, icon: Icons.beach_access), |
| ])); |
| |
| await expectLater(tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons))); |
| }); |
| |
| testWidgets('White on different colors, failing', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.white, background: Colors.red[200]!, icon: Icons.more_horiz), |
| iconWidget(color: Colors.white, background: Colors.green[400]!, icon: Icons.description), |
| iconWidget(color: Colors.white, background: Colors.blue[600]!, icon: Icons.image), |
| iconWidget(color: Colors.white, background: Colors.purple[800]!, icon: Icons.beach_access), |
| ])); |
| |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('Absence of icons, passing', (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[])); |
| |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('Absence of icons, passing - 2nd test', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| textWidget(color: Colors.black, background: Colors.white), |
| textWidget(color: Colors.black, background: Colors.black), |
| ])); |
| |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| }); |
| |
| testWidgets('Guideline ignores widgets of other types', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.black, background: Colors.white), |
| iconWidget(color: Colors.black, background: Colors.white), |
| textWidget(color: Colors.black, background: Colors.white), |
| textWidget(color: Colors.black, background: Colors.black), |
| ])); |
| |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findTexts)), |
| ); |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIconsAndTexts)), |
| ); |
| }); |
| |
| testWidgets('Custom minimum ratio - Icons', (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.blue, background: Colors.white), |
| iconWidget(color: Colors.black, background: Colors.white), |
| ])); |
| |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons, minimumRatio: 3.0)), |
| ); |
| }); |
| |
| testWidgets('Custom minimum ratio - Texts', (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| textWidget(color: Colors.blue, background: Colors.white), |
| textWidget(color: Colors.black, background: Colors.white), |
| ])); |
| |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findTexts)), |
| ); |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findTexts, minimumRatio: 3.0)), |
| ); |
| }); |
| |
| testWidgets( |
| 'Custom minimum ratio - Different standards for icons and texts', |
| (WidgetTester tester) async { |
| await tester.pumpWidget(rowWidget(<Widget>[ |
| iconWidget(color: Colors.blue, background: Colors.white), |
| iconWidget(color: Colors.black, background: Colors.white), |
| textWidget(color: Colors.blue, background: Colors.white), |
| textWidget(color: Colors.black, background: Colors.white), |
| ])); |
| |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)), |
| ); |
| await expectLater( |
| tester, |
| meetsGuideline(CustomMinimumContrastGuideline(finder: findTexts, minimumRatio: 3.0)), |
| ); |
| }); |
| }); |
| |
| group('tap target size guideline', () { |
| testWidgets('Tappable box at 48 by 48', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate( |
| SizedBox( |
| width: 48.0, |
| height: 48.0, |
| child: GestureDetector(onTap: () {}), |
| ), |
| )); |
| await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Tappable box at 47 by 48', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate( |
| SizedBox( |
| width: 47.0, |
| height: 48.0, |
| child: GestureDetector(onTap: () {}), |
| ), |
| )); |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(androidTapTargetGuideline), |
| ); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Tappable box at 48 by 47', (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate( |
| SizedBox( |
| width: 48.0, |
| height: 47.0, |
| child: GestureDetector(onTap: () {}), |
| ), |
| )); |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(androidTapTargetGuideline), |
| ); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Tappable box at 48 by 48 shrunk by transform', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate( |
| Transform.scale( |
| scale: 0.5, // should have new height of 24 by 24. |
| child: SizedBox( |
| width: 48.0, |
| height: 48.0, |
| child: GestureDetector(onTap: () {}), |
| ), |
| ), |
| )); |
| await expectLater( |
| tester, |
| doesNotMeetGuideline(androidTapTargetGuideline), |
| ); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Too small tap target fails with the correct message', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate( |
| SizedBox( |
| width: 48.0, |
| height: 47.0, |
| child: GestureDetector(onTap: () {}), |
| ), |
| )); |
| final Evaluation result = await androidTapTargetGuideline.evaluate(tester); |
| expect(result.passed, false); |
| expect( |
| result.reason, |
| 'SemanticsNode#4(Rect.fromLTRB(376.0, 276.5, 424.0, 323.5), ' |
| 'actions: [tap]): expected tap ' |
| 'target size of at least Size(48.0, 48.0), ' |
| 'but found Size(48.0, 47.0)\n' |
| 'See also: https://support.google.com/accessibility/android/answer/7101858?hl=en', |
| ); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Box that overlaps edge of window is skipped', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| final Widget smallBox = SizedBox( |
| width: 48.0, |
| height: 47.0, |
| child: GestureDetector(onTap: () {}), |
| ); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 0.0, |
| top: -1.0, |
| child: smallBox, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| final Evaluation overlappingTopResult = await androidTapTargetGuideline.evaluate(tester); |
| expect(overlappingTopResult.passed, true); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Stack( |
| children: <Widget>[ |
| Positioned( |
| left: -1.0, |
| top: 0.0, |
| child: smallBox, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| final Evaluation overlappingLeftResult = await androidTapTargetGuideline.evaluate(tester); |
| expect(overlappingLeftResult.passed, true); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Stack( |
| children: <Widget>[ |
| Positioned( |
| bottom: -1.0, |
| child: smallBox, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| final Evaluation overlappingBottomResult = await androidTapTargetGuideline.evaluate(tester); |
| expect(overlappingBottomResult.passed, true); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Stack( |
| children: <Widget>[ |
| Positioned( |
| right: -1.0, |
| child: smallBox, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| final Evaluation overlappingRightResult = await androidTapTargetGuideline.evaluate(tester); |
| expect(overlappingRightResult.passed, true); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Does not fail on mergedIntoParent child', |
| (WidgetTester tester) async { |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate(MergeSemantics( |
| child: Semantics( |
| container: true, |
| child: SizedBox( |
| width: 50.0, |
| height: 50.0, |
| child: Semantics( |
| container: true, |
| child: GestureDetector( |
| onTap: () {}, |
| child: const SizedBox(width: 4.0, height: 4.0), |
| ), |
| ), |
| ), |
| ), |
| ))); |
| |
| final Evaluation overlappingRightResult = await androidTapTargetGuideline.evaluate(tester); |
| expect(overlappingRightResult.passed, true); |
| handle.dispose(); |
| }); |
| |
| testWidgets('Does not fail on links', (WidgetTester tester) async { |
| Widget textWithLink() { |
| return Builder( |
| builder: (BuildContext context) { |
| return RichText( |
| text: TextSpan( |
| children: <InlineSpan>[ |
| const TextSpan(text: 'See examples at '), |
| TextSpan( |
| text: 'flutter repo', |
| recognizer: TapGestureRecognizer()..onTap = () {}, |
| ), |
| ], |
| ), |
| ); |
| }, |
| ); |
| } |
| |
| final SemanticsHandle handle = tester.ensureSemantics(); |
| await tester.pumpWidget(_boilerplate(textWithLink())); |
| |
| await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); |
| 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, |
| onTap: () {}, |
| label: 'test', |
| child: const SizedBox(width: 10.0, height: 10.0), |
| ))); |
| 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, |
| onLongPress: () {}, |
| label: '', |
| child: const SizedBox(width: 10.0, height: 10.0), |
| ))); |
| 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, |
| onTap: () {}, |
| label: '', |
| child: const SizedBox(width: 10.0, height: 10.0), |
| ))); |
| 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(); |
| }); |
| }); |
| |
| 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: ElevatedButton( |
| style: ElevatedButton.styleFrom( |
| backgroundColor: 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) => |
| MaterialApp(home: Scaffold(body: Center(child: child))); |