"Fossies" - the Fresh Open Source Software Archive

Member "flutter-3.7.1/packages/flutter_test/test/accessibility_test.dart" (1 Feb 2023, 30492 Bytes) of package /linux/misc/flutter-3.7.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "accessibility_test.dart": 3.3.10_vs_3.7.0.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 import 'package:flutter/gestures.dart';
    6 import 'package:flutter/material.dart';
    7 import 'package:flutter_test/flutter_test.dart';
    8 
    9 void main() {
   10   group('text contrast guideline', () {
   11     testWidgets('black text on white background - Text Widget - direct style',
   12         (WidgetTester tester) async {
   13       final SemanticsHandle handle = tester.ensureSemantics();
   14       await tester.pumpWidget(
   15         _boilerplate(
   16           const Text(
   17             'this is a test',
   18             style: TextStyle(fontSize: 14.0, color: Colors.black),
   19           ),
   20         ),
   21       );
   22       await expectLater(tester, meetsGuideline(textContrastGuideline));
   23       handle.dispose();
   24     });
   25 
   26     testWidgets('Multiple text with same label', (WidgetTester tester) async {
   27       final SemanticsHandle handle = tester.ensureSemantics();
   28       await tester.pumpWidget(
   29         _boilerplate(
   30           Column(
   31             children: const <Widget>[
   32               Text(
   33                 'this is a test',
   34                 style: TextStyle(fontSize: 14.0, color: Colors.black),
   35               ),
   36               Text(
   37                 'this is a test',
   38                 style: TextStyle(fontSize: 14.0, color: Colors.black),
   39               ),
   40             ],
   41           ),
   42         ),
   43       );
   44       await expectLater(tester, meetsGuideline(textContrastGuideline));
   45       handle.dispose();
   46     });
   47 
   48     testWidgets(
   49       'Multiple text with same label but Nodes excluded from '
   50       'semantic tree have failing contrast should pass a11y guideline ',
   51       (WidgetTester tester) async {
   52         final SemanticsHandle handle = tester.ensureSemantics();
   53         await tester.pumpWidget(
   54           _boilerplate(
   55             Column(
   56               children: const <Widget>[
   57                 Text(
   58                   'this is a test',
   59                   style: TextStyle(fontSize: 14.0, color: Colors.black),
   60                 ),
   61                 SizedBox(height: 50),
   62                 Text(
   63                   'this is a test',
   64                   style: TextStyle(fontSize: 14.0, color: Colors.black),
   65                 ),
   66                 SizedBox(height: 50),
   67                 ExcludeSemantics(
   68                   child: Text(
   69                     'this is a test',
   70                     style: TextStyle(fontSize: 14.0, color: Colors.white),
   71                   ),
   72                 ),
   73               ],
   74             ),
   75           ),
   76         );
   77         await expectLater(tester, meetsGuideline(textContrastGuideline));
   78         handle.dispose();
   79     });
   80 
   81     testWidgets('white text on black background - Text Widget - direct style',
   82         (WidgetTester tester) async {
   83       final SemanticsHandle handle = tester.ensureSemantics();
   84       await tester.pumpWidget(
   85         _boilerplate(
   86           Container(
   87             width: 200.0,
   88             height: 200.0,
   89             color: Colors.black,
   90             child: const Text(
   91               'this is a test',
   92               style: TextStyle(fontSize: 14.0, color: Colors.white),
   93             ),
   94           ),
   95         ),
   96       );
   97       await expectLater(tester, meetsGuideline(textContrastGuideline));
   98       handle.dispose();
   99     });
  100 
  101     testWidgets('White text on white background fails contrast test',
  102       (WidgetTester tester) async {
  103     final SemanticsHandle handle = tester.ensureSemantics();
  104     await tester.pumpWidget(
  105       _boilerplate(
  106         Container(
  107           width: 200.0,
  108           height: 300.0,
  109           color: Colors.white,
  110           child: Column(
  111             children: const <Widget>[
  112               Text(
  113                 'this is a white text',
  114                 style: TextStyle(fontSize: 14.0, color: Colors.white),
  115               ),
  116               SizedBox(height: 50),
  117               Text(
  118                 'this is a black text test1',
  119                 style: TextStyle(fontSize: 14.0, color: Colors.black),
  120               ),
  121               SizedBox(height: 50),
  122               Text(
  123                 'this is a black text test2',
  124                 style: TextStyle(fontSize: 14.0, color: Colors.black),
  125               ),
  126             ],
  127           ),
  128         ),
  129       ),
  130     );
  131     await expectLater(tester, doesNotMeetGuideline(textContrastGuideline));
  132     handle.dispose();
  133   });
  134 
  135     const Color surface = Color(0xFFF0F0F0);
  136 
  137     /// Shades of blue with contrast ratio of 2.9, 4.4, 4.5 from [surface].
  138     const Color blue29 = Color(0xFF7E7EFB);
  139     const Color blue44 = Color(0xFF5757FF);
  140     const Color blue45 = Color(0xFF5252FF);
  141     const List<TextStyle> textStylesMeetingGuideline = <TextStyle>[
  142       TextStyle(color: blue44, backgroundColor: surface, fontSize: 18),
  143       TextStyle(color: blue44, backgroundColor: surface, fontSize: 14, fontWeight: FontWeight.bold),
  144       TextStyle(color: blue45, backgroundColor: surface),
  145     ];
  146     const List<TextStyle> textStylesDoesNotMeetingGuideline = <TextStyle>[
  147       TextStyle(color: blue44, backgroundColor: surface),
  148       TextStyle(color: blue29, backgroundColor: surface, fontSize: 18),
  149     ];
  150 
  151     Widget appWithTextWidget(TextStyle style) => _boilerplate(
  152       Text('this is text', style: style.copyWith(height: 30.0)),
  153     );
  154 
  155     for (final TextStyle style in textStylesMeetingGuideline) {
  156       testWidgets('text with style $style', (WidgetTester tester) async {
  157         final SemanticsHandle handle = tester.ensureSemantics();
  158         await tester.pumpWidget(appWithTextWidget(style));
  159         await expectLater(tester, meetsGuideline(textContrastGuideline));
  160         handle.dispose();
  161       });
  162     }
  163 
  164     for (final TextStyle style in textStylesDoesNotMeetingGuideline) {
  165       testWidgets('text with $style', (WidgetTester tester) async {
  166         final SemanticsHandle handle = tester.ensureSemantics();
  167         await tester.pumpWidget(appWithTextWidget(style));
  168         await expectLater(tester, doesNotMeetGuideline(textContrastGuideline));
  169         handle.dispose();
  170       });
  171     }
  172 
  173     testWidgets('black text on white background - Text Widget - direct style',
  174         (WidgetTester tester) async {
  175       final SemanticsHandle handle = tester.ensureSemantics();
  176       await tester.pumpWidget(
  177         _boilerplate(
  178           const Text(
  179             'this is a test',
  180             style: TextStyle(fontSize: 14.0, color: Colors.black),
  181           ),
  182         ),
  183       );
  184       await expectLater(tester, meetsGuideline(textContrastGuideline));
  185       handle.dispose();
  186     });
  187 
  188     testWidgets('white text on black background - Text Widget - direct style',
  189         (WidgetTester tester) async {
  190       final SemanticsHandle handle = tester.ensureSemantics();
  191       await tester.pumpWidget(
  192         _boilerplate(
  193           Container(
  194             width: 200.0,
  195             height: 200.0,
  196             color: Colors.black,
  197             child: const Text(
  198               'this is a test',
  199               style: TextStyle(fontSize: 14.0, color: Colors.white),
  200             ),
  201           ),
  202         ),
  203       );
  204       await expectLater(tester, meetsGuideline(textContrastGuideline));
  205       handle.dispose();
  206     });
  207 
  208     testWidgets('Material text field - amber on amber',
  209         (WidgetTester tester) async {
  210       final SemanticsHandle handle = tester.ensureSemantics();
  211       await tester.pumpWidget(
  212         _boilerplate(
  213           Container(
  214             width: 200.0,
  215             height: 200.0,
  216             color: Colors.amberAccent,
  217             child: TextField(
  218               style: const TextStyle(color: Colors.amber),
  219               controller: TextEditingController(text: 'this is a test'),
  220             ),
  221           ),
  222         ),
  223       );
  224       await expectLater(tester, doesNotMeetGuideline(textContrastGuideline));
  225       handle.dispose();
  226     });
  227 
  228     testWidgets('Correctly identify failures in complex transforms', (WidgetTester tester) async {
  229       final SemanticsHandle handle = tester.ensureSemantics();
  230       await tester.pumpWidget(
  231         _boilerplate(
  232           Padding(
  233             padding: const EdgeInsets.only(left: 100),
  234             child: Semantics(
  235               container: true,
  236               child: Padding(
  237                 padding: const EdgeInsets.only(left: 100),
  238                 child: Semantics(
  239                   container: true,
  240                   child: Container(
  241                     width: 100.0,
  242                     height: 200.0,
  243                     color: Colors.amberAccent,
  244                     child: const Text(
  245                       'this',
  246                       style: TextStyle(color: Colors.amber),
  247                     ),
  248                   ),
  249                 ),
  250               ),
  251             ),
  252           ),
  253         ),
  254       );
  255       await expectLater(tester, doesNotMeetGuideline(textContrastGuideline));
  256       handle.dispose();
  257     });
  258 
  259     testWidgets('Material text field - default style',
  260         (WidgetTester tester) async {
  261       final SemanticsHandle handle = tester.ensureSemantics();
  262       await tester.pumpWidget(
  263         _boilerplate(
  264           SizedBox(
  265             width: 100,
  266             child: TextField(
  267               controller: TextEditingController(text: 'this is a test'),
  268             ),
  269           ),
  270         ),
  271       );
  272       await tester.idle();
  273       await expectLater(tester, meetsGuideline(textContrastGuideline));
  274       handle.dispose();
  275     });
  276 
  277     testWidgets('yellow text on yellow background fails with correct message',
  278         (WidgetTester tester) async {
  279       final SemanticsHandle handle = tester.ensureSemantics();
  280       await tester.pumpWidget(
  281         _boilerplate(
  282           Container(
  283             width: 200.0,
  284             height: 200.0,
  285             color: Colors.yellow,
  286             child: const Text(
  287               'this is a test',
  288               style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent),
  289             ),
  290           ),
  291         ),
  292       );
  293       final Evaluation result = await textContrastGuideline.evaluate(tester);
  294       expect(result.passed, false);
  295       expect(
  296         result.reason,
  297         'SemanticsNode#4(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), '
  298         'label: "this is a test", textDirection: ltr):\n'
  299         'Expected contrast ratio of at least 4.5 but found 1.17 for a font '
  300         'size of 14.0.\n'
  301         'The computed colors was:\n'
  302         'light - Color(0xfffafafa), dark - Color(0xffffeb3b)\n'
  303          'See also: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html',
  304       );
  305       handle.dispose();
  306     });
  307 
  308     testWidgets('label without corresponding text is skipped',
  309         (WidgetTester tester) async {
  310       final SemanticsHandle handle = tester.ensureSemantics();
  311       await tester.pumpWidget(
  312         _boilerplate(
  313           Semantics(
  314             label: 'This is not text',
  315             container: true,
  316             child: const SizedBox(
  317               width: 200.0,
  318               height: 200.0,
  319               child: Placeholder(),
  320             ),
  321           ),
  322         ),
  323       );
  324 
  325       final Evaluation result = await textContrastGuideline.evaluate(tester);
  326       expect(result.passed, true);
  327       handle.dispose();
  328     });
  329 
  330     testWidgets('offscreen text is skipped', (WidgetTester tester) async {
  331       final SemanticsHandle handle = tester.ensureSemantics();
  332       await tester.pumpWidget(
  333         _boilerplate(
  334           Stack(
  335             children: <Widget>[
  336               Positioned(
  337                 left: -300.0,
  338                 child: Container(
  339                   width: 200.0,
  340                   height: 200.0,
  341                   color: Colors.yellow,
  342                   child: const Text(
  343                     'this is a test',
  344                     style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent),
  345                   ),
  346                 ),
  347               ),
  348             ],
  349           ),
  350         ),
  351       );
  352 
  353       final Evaluation result = await textContrastGuideline.evaluate(tester);
  354       expect(result.passed, true);
  355       handle.dispose();
  356     });
  357 
  358     testWidgets('Disabled button is excluded from text contrast guideline',
  359         (WidgetTester tester) async {
  360       // Regression test https://github.com/flutter/flutter/issues/94428
  361       final SemanticsHandle handle = tester.ensureSemantics();
  362       await tester.pumpWidget(
  363         _boilerplate(
  364           ElevatedButton(
  365             onPressed: null,
  366             child: Container(
  367               width: 200.0,
  368               height: 200.0,
  369               color: Colors.yellow,
  370               child: const Text(
  371                 'this is a test',
  372                 style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent),
  373               ),
  374             ),
  375           ),
  376         ),
  377       );
  378       await expectLater(tester, meetsGuideline(textContrastGuideline));
  379       handle.dispose();
  380     });
  381   });
  382 
  383   group('custom minimum contrast guideline', () {
  384     Widget iconWidget({
  385       IconData icon = Icons.search,
  386       required Color color,
  387       required Color background,
  388     }) {
  389       return Container(
  390         padding: const EdgeInsets.all(8.0),
  391         color: background,
  392         child: Icon(icon, color: color),
  393       );
  394     }
  395 
  396     Widget textWidget({
  397       String text = 'Text',
  398       required Color color,
  399       required Color background,
  400     }) {
  401       return Container(
  402         padding: const EdgeInsets.all(8.0),
  403         color: background,
  404         child: Text(text, style: TextStyle(color: color)),
  405       );
  406     }
  407 
  408     Widget rowWidget(List<Widget> widgets) => _boilerplate(Row(children: widgets));
  409 
  410     final Finder findIcons = find.byWidgetPredicate((Widget widget) => widget is Icon);
  411     final Finder findTexts = find.byWidgetPredicate((Widget widget) => widget is Text);
  412     final Finder findIconsAndTexts = find.byWidgetPredicate((Widget widget) => widget is Icon || widget is Text);
  413 
  414     testWidgets('Black icons on white background', (WidgetTester tester) async {
  415       await tester.pumpWidget(rowWidget(<Widget>[
  416         iconWidget(color: Colors.black, background: Colors.white),
  417         iconWidget(color: Colors.black, background: Colors.white),
  418       ]));
  419 
  420       await expectLater(
  421         tester,
  422         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  423       );
  424     });
  425 
  426     testWidgets('Black icons on black background', (WidgetTester tester) async {
  427       await tester.pumpWidget(rowWidget(<Widget>[
  428         iconWidget(color: Colors.black, background: Colors.black),
  429         iconWidget(color: Colors.black, background: Colors.black),
  430       ]));
  431 
  432       await expectLater(
  433         tester,
  434         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  435       );
  436     });
  437 
  438     testWidgets('White icons on black background ("dark mode")',
  439         (WidgetTester tester) async {
  440       await tester.pumpWidget(rowWidget(<Widget>[
  441         iconWidget(color: Colors.white, background: Colors.black),
  442         iconWidget(color: Colors.white, background: Colors.black),
  443       ]));
  444 
  445       await expectLater(
  446         tester,
  447         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  448       );
  449     });
  450 
  451     testWidgets('Using different icons', (WidgetTester tester) async {
  452       await tester.pumpWidget(rowWidget(<Widget>[
  453         iconWidget(color: Colors.black, background: Colors.white, icon: Icons.more_horiz),
  454         iconWidget(color: Colors.black, background: Colors.white, icon: Icons.description),
  455         iconWidget(color: Colors.black, background: Colors.white, icon: Icons.image),
  456         iconWidget(color: Colors.black, background: Colors.white, icon: Icons.beach_access),
  457       ]));
  458 
  459       await expectLater(
  460         tester,
  461         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  462       );
  463     });
  464 
  465     testWidgets('One invalid instance fails entire test',
  466         (WidgetTester tester) async {
  467       await tester.pumpWidget(rowWidget(<Widget>[
  468         iconWidget(color: Colors.black, background: Colors.white),
  469         iconWidget(color: Colors.black, background: Colors.black),
  470       ]));
  471 
  472       await expectLater(
  473         tester,
  474         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  475       );
  476     });
  477 
  478     testWidgets('White on different colors, passing',
  479         (WidgetTester tester) async {
  480       await tester.pumpWidget(rowWidget(<Widget>[
  481         iconWidget(color: Colors.white, background: Colors.red[800]!, icon: Icons.more_horiz),
  482         iconWidget(color: Colors.white, background: Colors.green[800]!, icon: Icons.description),
  483         iconWidget(color: Colors.white, background: Colors.blue[800]!, icon: Icons.image),
  484         iconWidget(color: Colors.white, background: Colors.purple[800]!, icon: Icons.beach_access),
  485       ]));
  486 
  487       await expectLater(tester,
  488           meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)));
  489     });
  490 
  491     testWidgets('White on different colors, failing',
  492         (WidgetTester tester) async {
  493       await tester.pumpWidget(rowWidget(<Widget>[
  494         iconWidget(color: Colors.white, background: Colors.red[200]!, icon: Icons.more_horiz),
  495         iconWidget(color: Colors.white, background: Colors.green[400]!, icon: Icons.description),
  496         iconWidget(color: Colors.white, background: Colors.blue[600]!, icon: Icons.image),
  497         iconWidget(color: Colors.white, background: Colors.purple[800]!, icon: Icons.beach_access),
  498       ]));
  499 
  500       await expectLater(
  501         tester,
  502         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  503       );
  504     });
  505 
  506     testWidgets('Absence of icons, passing', (WidgetTester tester) async {
  507       await tester.pumpWidget(rowWidget(<Widget>[]));
  508 
  509       await expectLater(
  510         tester,
  511         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  512       );
  513     });
  514 
  515     testWidgets('Absence of icons, passing - 2nd test',
  516         (WidgetTester tester) async {
  517       await tester.pumpWidget(rowWidget(<Widget>[
  518         textWidget(color: Colors.black, background: Colors.white),
  519         textWidget(color: Colors.black, background: Colors.black),
  520       ]));
  521 
  522       await expectLater(
  523         tester,
  524         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  525       );
  526     });
  527 
  528     testWidgets('Guideline ignores widgets of other types',
  529         (WidgetTester tester) async {
  530       await tester.pumpWidget(rowWidget(<Widget>[
  531         iconWidget(color: Colors.black, background: Colors.white),
  532         iconWidget(color: Colors.black, background: Colors.white),
  533         textWidget(color: Colors.black, background: Colors.white),
  534         textWidget(color: Colors.black, background: Colors.black),
  535       ]));
  536 
  537       await expectLater(
  538         tester,
  539         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  540       );
  541       await expectLater(
  542         tester,
  543         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findTexts)),
  544       );
  545       await expectLater(
  546         tester,
  547         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIconsAndTexts)),
  548       );
  549     });
  550 
  551     testWidgets('Custom minimum ratio - Icons', (WidgetTester tester) async {
  552       await tester.pumpWidget(rowWidget(<Widget>[
  553         iconWidget(color: Colors.blue, background: Colors.white),
  554         iconWidget(color: Colors.black, background: Colors.white),
  555       ]));
  556 
  557       await expectLater(
  558         tester,
  559         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  560       );
  561       await expectLater(
  562         tester,
  563         meetsGuideline(CustomMinimumContrastGuideline(finder: findIcons, minimumRatio: 3.0)),
  564       );
  565     });
  566 
  567     testWidgets('Custom minimum ratio - Texts', (WidgetTester tester) async {
  568       await tester.pumpWidget(rowWidget(<Widget>[
  569         textWidget(color: Colors.blue, background: Colors.white),
  570         textWidget(color: Colors.black, background: Colors.white),
  571       ]));
  572 
  573       await expectLater(
  574         tester,
  575         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findTexts)),
  576       );
  577       await expectLater(
  578         tester,
  579         meetsGuideline(CustomMinimumContrastGuideline(finder: findTexts, minimumRatio: 3.0)),
  580       );
  581     });
  582 
  583     testWidgets(
  584         'Custom minimum ratio - Different standards for icons and texts',
  585         (WidgetTester tester) async {
  586       await tester.pumpWidget(rowWidget(<Widget>[
  587         iconWidget(color: Colors.blue, background: Colors.white),
  588         iconWidget(color: Colors.black, background: Colors.white),
  589         textWidget(color: Colors.blue, background: Colors.white),
  590         textWidget(color: Colors.black, background: Colors.white),
  591       ]));
  592 
  593       await expectLater(
  594         tester,
  595         doesNotMeetGuideline(CustomMinimumContrastGuideline(finder: findIcons)),
  596       );
  597       await expectLater(
  598         tester,
  599         meetsGuideline(CustomMinimumContrastGuideline(finder: findTexts, minimumRatio: 3.0)),
  600       );
  601     });
  602   });
  603 
  604   group('tap target size guideline', () {
  605     testWidgets('Tappable box at 48 by 48', (WidgetTester tester) async {
  606       final SemanticsHandle handle = tester.ensureSemantics();
  607       await tester.pumpWidget(_boilerplate(
  608         SizedBox(
  609           width: 48.0,
  610           height: 48.0,
  611           child: GestureDetector(onTap: () {}),
  612         ),
  613       ));
  614       await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
  615       handle.dispose();
  616     });
  617 
  618     testWidgets('Tappable box at 47 by 48', (WidgetTester tester) async {
  619       final SemanticsHandle handle = tester.ensureSemantics();
  620       await tester.pumpWidget(_boilerplate(
  621         SizedBox(
  622           width: 47.0,
  623           height: 48.0,
  624           child: GestureDetector(onTap: () {}),
  625         ),
  626       ));
  627       await expectLater(
  628         tester,
  629         doesNotMeetGuideline(androidTapTargetGuideline),
  630       );
  631       handle.dispose();
  632     });
  633 
  634     testWidgets('Tappable box at 48 by 47', (WidgetTester tester) async {
  635       final SemanticsHandle handle = tester.ensureSemantics();
  636       await tester.pumpWidget(_boilerplate(
  637         SizedBox(
  638           width: 48.0,
  639           height: 47.0,
  640           child: GestureDetector(onTap: () {}),
  641         ),
  642       ));
  643       await expectLater(
  644         tester,
  645         doesNotMeetGuideline(androidTapTargetGuideline),
  646       );
  647       handle.dispose();
  648     });
  649 
  650     testWidgets('Tappable box at 48 by 48 shrunk by transform',
  651         (WidgetTester tester) async {
  652       final SemanticsHandle handle = tester.ensureSemantics();
  653       await tester.pumpWidget(_boilerplate(
  654         Transform.scale(
  655           scale: 0.5, // should have new height of 24 by 24.
  656           child: SizedBox(
  657             width: 48.0,
  658             height: 48.0,
  659             child: GestureDetector(onTap: () {}),
  660           ),
  661         ),
  662       ));
  663       await expectLater(
  664         tester,
  665         doesNotMeetGuideline(androidTapTargetGuideline),
  666       );
  667       handle.dispose();
  668     });
  669 
  670     testWidgets('Too small tap target fails with the correct message',
  671         (WidgetTester tester) async {
  672       final SemanticsHandle handle = tester.ensureSemantics();
  673       await tester.pumpWidget(_boilerplate(
  674         SizedBox(
  675           width: 48.0,
  676           height: 47.0,
  677           child: GestureDetector(onTap: () {}),
  678         ),
  679       ));
  680       final Evaluation result = await androidTapTargetGuideline.evaluate(tester);
  681       expect(result.passed, false);
  682       expect(
  683         result.reason,
  684         'SemanticsNode#4(Rect.fromLTRB(376.0, 276.5, 424.0, 323.5), '
  685         'actions: [tap]): expected tap '
  686         'target size of at least Size(48.0, 48.0), '
  687         'but found Size(48.0, 47.0)\n'
  688         'See also: https://support.google.com/accessibility/android/answer/7101858?hl=en',
  689       );
  690       handle.dispose();
  691     });
  692 
  693     testWidgets('Box that overlaps edge of window is skipped',
  694         (WidgetTester tester) async {
  695       final SemanticsHandle handle = tester.ensureSemantics();
  696       final Widget smallBox = SizedBox(
  697         width: 48.0,
  698         height: 47.0,
  699         child: GestureDetector(onTap: () {}),
  700       );
  701       await tester.pumpWidget(
  702         MaterialApp(
  703           home: Stack(
  704             children: <Widget>[
  705               Positioned(
  706                 left: 0.0,
  707                 top: -1.0,
  708                 child: smallBox,
  709               ),
  710             ],
  711           ),
  712         ),
  713       );
  714 
  715       final Evaluation overlappingTopResult = await androidTapTargetGuideline.evaluate(tester);
  716       expect(overlappingTopResult.passed, true);
  717 
  718       await tester.pumpWidget(
  719         MaterialApp(
  720           home: Stack(
  721             children: <Widget>[
  722               Positioned(
  723                 left: -1.0,
  724                 top: 0.0,
  725                 child: smallBox,
  726               ),
  727             ],
  728           ),
  729         ),
  730       );
  731 
  732       final Evaluation overlappingLeftResult = await androidTapTargetGuideline.evaluate(tester);
  733       expect(overlappingLeftResult.passed, true);
  734 
  735       await tester.pumpWidget(
  736         MaterialApp(
  737           home: Stack(
  738             children: <Widget>[
  739               Positioned(
  740                 bottom: -1.0,
  741                 child: smallBox,
  742               ),
  743             ],
  744           ),
  745         ),
  746       );
  747 
  748       final Evaluation overlappingBottomResult = await androidTapTargetGuideline.evaluate(tester);
  749       expect(overlappingBottomResult.passed, true);
  750 
  751       await tester.pumpWidget(
  752         MaterialApp(
  753           home: Stack(
  754             children: <Widget>[
  755               Positioned(
  756                 right: -1.0,
  757                 child: smallBox,
  758               ),
  759             ],
  760           ),
  761         ),
  762       );
  763 
  764       final Evaluation overlappingRightResult = await androidTapTargetGuideline.evaluate(tester);
  765       expect(overlappingRightResult.passed, true);
  766       handle.dispose();
  767     });
  768 
  769     testWidgets('Does not fail on mergedIntoParent child',
  770         (WidgetTester tester) async {
  771       final SemanticsHandle handle = tester.ensureSemantics();
  772       await tester.pumpWidget(_boilerplate(MergeSemantics(
  773         child: Semantics(
  774           container: true,
  775           child: SizedBox(
  776             width: 50.0,
  777             height: 50.0,
  778             child: Semantics(
  779               container: true,
  780               child: GestureDetector(
  781                 onTap: () {},
  782                 child: const SizedBox(width: 4.0, height: 4.0),
  783               ),
  784             ),
  785           ),
  786         ),
  787       )));
  788 
  789       final Evaluation overlappingRightResult = await androidTapTargetGuideline.evaluate(tester);
  790       expect(overlappingRightResult.passed, true);
  791       handle.dispose();
  792     });
  793 
  794     testWidgets('Does not fail on links', (WidgetTester tester) async {
  795       Widget textWithLink() {
  796         return Builder(
  797           builder: (BuildContext context) {
  798             return RichText(
  799               text: TextSpan(
  800                 children: <InlineSpan>[
  801                   const TextSpan(text: 'See examples at '),
  802                   TextSpan(
  803                     text: 'flutter repo',
  804                     recognizer: TapGestureRecognizer()..onTap = () {},
  805                   ),
  806                 ],
  807               ),
  808             );
  809           },
  810         );
  811       }
  812 
  813       final SemanticsHandle handle = tester.ensureSemantics();
  814       await tester.pumpWidget(_boilerplate(textWithLink()));
  815 
  816       await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
  817       handle.dispose();
  818     });
  819   });
  820 
  821   group('Labeled tappable node guideline', () {
  822     testWidgets('Passes when node is labeled', (WidgetTester tester) async {
  823       final SemanticsHandle handle = tester.ensureSemantics();
  824       await tester.pumpWidget(_boilerplate(Semantics(
  825         container: true,
  826         onTap: () {},
  827         label: 'test',
  828         child: const SizedBox(width: 10.0, height: 10.0),
  829       )));
  830       final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
  831       expect(result.passed, true);
  832       handle.dispose();
  833     });
  834     testWidgets('Fails if long-press has no label',
  835         (WidgetTester tester) async {
  836       final SemanticsHandle handle = tester.ensureSemantics();
  837       await tester.pumpWidget(_boilerplate(Semantics(
  838         container: true,
  839         onLongPress: () {},
  840         label: '',
  841         child: const SizedBox(width: 10.0, height: 10.0),
  842       )));
  843       final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
  844       expect(result.passed, false);
  845       handle.dispose();
  846     });
  847 
  848     testWidgets('Fails if tap has no label', (WidgetTester tester) async {
  849       final SemanticsHandle handle = tester.ensureSemantics();
  850       await tester.pumpWidget(_boilerplate(Semantics(
  851         container: true,
  852         onTap: () {},
  853         label: '',
  854         child: const SizedBox(width: 10.0, height: 10.0),
  855       )));
  856       final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
  857       expect(result.passed, false);
  858       handle.dispose();
  859     });
  860 
  861     testWidgets('Passes if tap is merged into labeled node',
  862         (WidgetTester tester) async {
  863       final SemanticsHandle handle = tester.ensureSemantics();
  864       await tester.pumpWidget(_boilerplate(Semantics(
  865         container: true,
  866         onLongPress: () {},
  867         label: '',
  868         child: Semantics(
  869           label: 'test',
  870           child: const SizedBox(width: 10.0, height: 10.0),
  871         ),
  872       )));
  873       final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
  874       expect(result.passed, true);
  875       handle.dispose();
  876     });
  877 
  878     testWidgets('Passes if text field does not have label', (WidgetTester tester) async {
  879       final SemanticsHandle handle = tester.ensureSemantics();
  880       await tester.pumpWidget(_boilerplate(const TextField()));
  881       final Evaluation result = await labeledTapTargetGuideline.evaluate(tester);
  882       expect(result.passed, true);
  883       handle.dispose();
  884     });
  885   });
  886 
  887   testWidgets('regression test for material widget',
  888       (WidgetTester tester) async {
  889     final SemanticsHandle handle = tester.ensureSemantics();
  890     await tester.pumpWidget(MaterialApp(
  891       theme: ThemeData.light(),
  892       home: Scaffold(
  893         backgroundColor: Colors.white,
  894         body: ElevatedButton(
  895           style: ElevatedButton.styleFrom(
  896             backgroundColor: const Color(0xFFFBBC04),
  897             elevation: 0,
  898           ),
  899           onPressed: () {},
  900           child: const Text('Button', style: TextStyle(color: Colors.black)),
  901         ),
  902       ),
  903     ));
  904     await expectLater(tester, meetsGuideline(textContrastGuideline));
  905     handle.dispose();
  906   });
  907 }
  908 
  909 Widget _boilerplate(Widget child) =>
  910   MaterialApp(home: Scaffold(body: Center(child: child)));