"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)));