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
5import 'dart:io';
6
7import 'package:flutter/material.dart';
8import 'package:flutter/rendering.dart';
9import 'package:flutter_test/flutter_test.dart';
10
11const List<Widget> fooBarTexts = <Text>[
12 Text('foo', textDirection: TextDirection.ltr),
13 Text('bar', textDirection: TextDirection.ltr),
14];
15
16void main() {
17 group('image', () {
18 testWidgets('finds Image widgets', (WidgetTester tester) async {
19 await tester.pumpWidget(_boilerplate(Image(image: FileImage(File('test')))));
20 expect(find.image(FileImage(File('test'))), findsOneWidget);
21 });
22
23 testWidgets('finds Button widgets with Image', (WidgetTester tester) async {
24 await tester.pumpWidget(
25 _boilerplate(ElevatedButton(onPressed: null, child: Image(image: FileImage(File('test'))))),
26 );
27 expect(find.widgetWithImage(ElevatedButton, FileImage(File('test'))), findsOneWidget);
28 });
29 });
30
31 group('text', () {
32 testWidgets('finds Text widgets', (WidgetTester tester) async {
33 await tester.pumpWidget(_boilerplate(const Text('test')));
34 expect(find.text('test'), findsOneWidget);
35 });
36
37 testWidgets('finds Text.rich widgets', (WidgetTester tester) async {
38 await tester.pumpWidget(
39 _boilerplate(
40 const Text.rich(
41 TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'e'), TextSpan(text: 'st')]),
42 ),
43 ),
44 );
45
46 expect(find.text('test'), findsOneWidget);
47 });
48
49 group('findRichText', () {
50 testWidgets('finds RichText widgets when enabled', (WidgetTester tester) async {
51 await tester.pumpWidget(
52 _boilerplate(
53 RichText(text: const TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est')])),
54 ),
55 );
56
57 expect(find.text('test', findRichText: true), findsOneWidget);
58 });
59
60 testWidgets('finds Text widgets once when enabled', (WidgetTester tester) async {
61 await tester.pumpWidget(_boilerplate(const Text('test2')));
62
63 expect(find.text('test2', findRichText: true), findsOneWidget);
64 });
65
66 testWidgets('does not find RichText widgets when disabled', (WidgetTester tester) async {
67 await tester.pumpWidget(
68 _boilerplate(
69 RichText(text: const TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est')])),
70 ),
71 );
72
73 expect(find.text('test'), findsNothing);
74 });
75
76 testWidgets('does not find Text and RichText separated by semantics widgets twice', (
77 WidgetTester tester,
78 ) async {
79 // If rich: true found both Text and RichText, this would find two widgets.
80 await tester.pumpWidget(_boilerplate(const Text('test', semanticsLabel: 'foo')));
81
82 expect(find.text('test'), findsOneWidget);
83 });
84
85 testWidgets('finds Text.rich widgets when enabled', (WidgetTester tester) async {
86 await tester.pumpWidget(
87 _boilerplate(
88 const Text.rich(
89 TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est'), TextSpan(text: '3')]),
90 ),
91 ),
92 );
93
94 expect(find.text('test3', findRichText: true), findsOneWidget);
95 });
96
97 testWidgets('finds Text.rich widgets when disabled', (WidgetTester tester) async {
98 await tester.pumpWidget(
99 _boilerplate(
100 const Text.rich(
101 TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est'), TextSpan(text: '3')]),
102 ),
103 ),
104 );
105
106 expect(find.text('test3'), findsOneWidget);
107 });
108 });
109 });
110
111 group('textContaining', () {
112 testWidgets('finds Text widgets', (WidgetTester tester) async {
113 await tester.pumpWidget(_boilerplate(const Text('this is a test')));
114 expect(find.textContaining(RegExp(r'test')), findsOneWidget);
115 expect(find.textContaining('test'), findsOneWidget);
116 expect(find.textContaining('a'), findsOneWidget);
117 expect(find.textContaining('s'), findsOneWidget);
118 });
119
120 testWidgets('finds Text.rich widgets', (WidgetTester tester) async {
121 await tester.pumpWidget(
122 _boilerplate(
123 const Text.rich(
124 TextSpan(
125 text: 'this',
126 children: <TextSpan>[
127 TextSpan(text: 'is'),
128 TextSpan(text: 'a'),
129 TextSpan(text: 'test'),
130 ],
131 ),
132 ),
133 ),
134 );
135
136 expect(find.textContaining(RegExp(r'isatest')), findsOneWidget);
137 expect(find.textContaining('isatest'), findsOneWidget);
138 });
139
140 testWidgets('finds EditableText widgets', (WidgetTester tester) async {
141 await tester.pumpWidget(
142 MaterialApp(
143 home: Scaffold(
144 body: _boilerplate(
145 TextField(controller: TextEditingController()..text = 'this is test'),
146 ),
147 ),
148 ),
149 );
150
151 expect(find.textContaining(RegExp(r'test')), findsOneWidget);
152 expect(find.textContaining('test'), findsOneWidget);
153 });
154
155 group('findRichText', () {
156 testWidgets('finds RichText widgets when enabled', (WidgetTester tester) async {
157 await tester.pumpWidget(
158 _boilerplate(
159 RichText(text: const TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est')])),
160 ),
161 );
162
163 expect(find.textContaining('te', findRichText: true), findsOneWidget);
164 });
165
166 testWidgets('finds Text widgets once when enabled', (WidgetTester tester) async {
167 await tester.pumpWidget(_boilerplate(const Text('test2')));
168
169 expect(find.textContaining('tes', findRichText: true), findsOneWidget);
170 });
171
172 testWidgets('does not find RichText widgets when disabled', (WidgetTester tester) async {
173 await tester.pumpWidget(
174 _boilerplate(
175 RichText(text: const TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est')])),
176 ),
177 );
178
179 expect(find.textContaining('te'), findsNothing);
180 });
181
182 testWidgets('does not find Text and RichText separated by semantics widgets twice', (
183 WidgetTester tester,
184 ) async {
185 // If rich: true found both Text and RichText, this would find two widgets.
186 await tester.pumpWidget(_boilerplate(const Text('test', semanticsLabel: 'foo')));
187
188 expect(find.textContaining('tes'), findsOneWidget);
189 });
190
191 testWidgets('finds Text.rich widgets when enabled', (WidgetTester tester) async {
192 await tester.pumpWidget(
193 _boilerplate(
194 const Text.rich(
195 TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est'), TextSpan(text: '3')]),
196 ),
197 ),
198 );
199
200 expect(find.textContaining('t3', findRichText: true), findsOneWidget);
201 });
202
203 testWidgets('finds Text.rich widgets when disabled', (WidgetTester tester) async {
204 await tester.pumpWidget(
205 _boilerplate(
206 const Text.rich(
207 TextSpan(text: 't', children: <TextSpan>[TextSpan(text: 'est'), TextSpan(text: '3')]),
208 ),
209 ),
210 );
211
212 expect(find.textContaining('t3'), findsOneWidget);
213 });
214 });
215 });
216
217 group('semantics', () {
218 testWidgets('Throws StateError if semantics are not enabled', (WidgetTester tester) async {
219 expect(() => find.bySemanticsLabel('Add'), throwsStateError);
220 }, semanticsEnabled: false);
221
222 testWidgets('finds Semantically labeled widgets', (WidgetTester tester) async {
223 final SemanticsHandle semanticsHandle = tester.ensureSemantics();
224 await tester.pumpWidget(
225 _boilerplate(
226 Semantics(
227 label: 'Add',
228 button: true,
229 child: const TextButton(onPressed: null, child: Text('+')),
230 ),
231 ),
232 );
233 expect(find.bySemanticsLabel('Add'), findsOneWidget);
234 semanticsHandle.dispose();
235 });
236
237 testWidgets('finds Semantically labeled widgets by RegExp', (WidgetTester tester) async {
238 final SemanticsHandle semanticsHandle = tester.ensureSemantics();
239 await tester.pumpWidget(
240 _boilerplate(
241 Semantics(
242 container: true,
243 child: const Row(children: <Widget>[Text('Hello'), Text('World')]),
244 ),
245 ),
246 );
247 expect(find.bySemanticsLabel('Hello'), findsNothing);
248 expect(find.bySemanticsLabel(RegExp(r'^Hello')), findsOneWidget);
249 semanticsHandle.dispose();
250 });
251
252 testWidgets('finds Semantically labeled widgets without explicit Semantics', (
253 WidgetTester tester,
254 ) async {
255 final SemanticsHandle semanticsHandle = tester.ensureSemantics();
256 await tester.pumpWidget(_boilerplate(const SimpleCustomSemanticsWidget('Foo')));
257 expect(find.bySemanticsLabel('Foo'), findsOneWidget);
258 semanticsHandle.dispose();
259 });
260
261 testWidgets(
262 'Throws StateError if semantics are not enabled (bySemanticsIdentifier)',
263 (WidgetTester tester) async {
264 expect(
265 () => find.bySemanticsIdentifier('Add'),
266 throwsA(
267 isA<StateError>().having(
268 (StateError e) => e.message,
269 'message',
270 contains('Semantics are not enabled'),
271 ),
272 ),
273 );
274 },
275 semanticsEnabled: false,
276 );
277
278 testWidgets('finds Semantically labeled widgets by identifier', (WidgetTester tester) async {
279 final SemanticsHandle semanticsHandle = tester.ensureSemantics();
280 await tester.pumpWidget(
281 _boilerplate(
282 Semantics(
283 identifier: 'Add',
284 button: true,
285 child: const TextButton(onPressed: null, child: Text('+')),
286 ),
287 ),
288 );
289 expect(find.bySemanticsIdentifier('Add'), findsOneWidget);
290 semanticsHandle.dispose();
291 });
292
293 testWidgets('finds Semantically labeled widgets by identifier RegExp', (
294 WidgetTester tester,
295 ) async {
296 final SemanticsHandle semanticsHandle = tester.ensureSemantics();
297 // list of elements with a prefixed identifier
298 await tester.pumpWidget(
299 _boilerplate(
300 Row(
301 children: <Widget>[
302 Semantics(identifier: 'item-1', child: const Text('Item 1')),
303 Semantics(identifier: 'item-2', child: const Text('Item 2')),
304 ],
305 ),
306 ),
307 );
308 expect(find.bySemanticsIdentifier('item'), findsNothing);
309 expect(find.bySemanticsIdentifier(RegExp(r'^item-')), findsNWidgets(2));
310 semanticsHandle.dispose();
311 });
312 });
313
314 group('byTooltip', () {
315 testWidgets('finds widgets by tooltip', (WidgetTester tester) async {
316 await tester.pumpWidget(
317 _boilerplate(const Tooltip(message: 'Tooltip Message', child: Text('+'))),
318 );
319 expect(find.byTooltip('Tooltip Message'), findsOneWidget);
320 });
321
322 testWidgets('finds widgets with tooltip by RegExp', (WidgetTester tester) async {
323 await tester.pumpWidget(
324 _boilerplate(const Tooltip(message: 'Tooltip Message', child: Text('+'))),
325 );
326 expect(find.byTooltip('Tooltip'), findsNothing);
327 expect(find.byTooltip(RegExp(r'^Tooltip')), findsOneWidget);
328 });
329
330 testWidgets('finds widgets by rich text tooltip', (WidgetTester tester) async {
331 await tester.pumpWidget(
332 _boilerplate(
333 const Tooltip(
334 richMessage: TextSpan(
335 children: <InlineSpan>[TextSpan(text: 'Tooltip '), TextSpan(text: 'Message')],
336 ),
337 child: Text('+'),
338 ),
339 ),
340 );
341 expect(find.byTooltip('Tooltip Message'), findsOneWidget);
342 });
343
344 testWidgets('finds widgets with rich text tooltip by RegExp', (WidgetTester tester) async {
345 await tester.pumpWidget(
346 _boilerplate(
347 const Tooltip(
348 richMessage: TextSpan(
349 children: <InlineSpan>[TextSpan(text: 'Tooltip '), TextSpan(text: 'Message')],
350 ),
351 child: Text('+'),
352 ),
353 ),
354 );
355 expect(find.byTooltip('Tooltip M'), findsNothing);
356 expect(find.byTooltip(RegExp(r'^Tooltip M')), findsOneWidget);
357 });
358
359 testWidgets('finds empty string with tooltip', (WidgetTester tester) async {
360 await tester.pumpWidget(_boilerplate(const Tooltip(message: '', child: Text('+'))));
361 expect(find.byTooltip(''), findsOneWidget);
362
363 await tester.pumpWidget(
364 _boilerplate(
365 const Tooltip(
366 richMessage: TextSpan(children: <InlineSpan>[TextSpan(text: '')]),
367 child: Text('+'),
368 ),
369 ),
370 );
371 expect(find.byTooltip(''), findsOneWidget);
372
373 await tester.pumpWidget(_boilerplate(const Tooltip(message: '', child: Text('+'))));
374 expect(find.byTooltip(RegExp(r'^$')), findsOneWidget);
375
376 await tester.pumpWidget(
377 _boilerplate(
378 const Tooltip(
379 richMessage: TextSpan(children: <InlineSpan>[TextSpan(text: '')]),
380 child: Text('+'),
381 ),
382 ),
383 );
384 expect(find.byTooltip(RegExp(r'^$')), findsOneWidget);
385 });
386 });
387
388 group('hitTestable', () {
389 testWidgets('excludes non-hit-testable widgets', (WidgetTester tester) async {
390 await tester.pumpWidget(
391 _boilerplate(
392 IndexedStack(
393 sizing: StackFit.expand,
394 children: <Widget>[
395 GestureDetector(
396 key: const ValueKey<int>(0),
397 behavior: HitTestBehavior.opaque,
398 onTap: () {},
399 child: const SizedBox.expand(),
400 ),
401 GestureDetector(
402 key: const ValueKey<int>(1),
403 behavior: HitTestBehavior.opaque,
404 onTap: () {},
405 child: const SizedBox.expand(),
406 ),
407 ],
408 ),
409 ),
410 );
411 expect(find.byType(GestureDetector), findsOneWidget);
412 expect(find.byType(GestureDetector, skipOffstage: false), findsNWidgets(2));
413 final Finder hitTestable = find.byType(GestureDetector, skipOffstage: false).hitTestable();
414 expect(hitTestable, findsOneWidget);
415 expect(tester.widget(hitTestable).key, const ValueKey<int>(0));
416 });
417 });
418
419 group('text range finders', () {
420 testWidgets('basic text span test', (WidgetTester tester) async {
421 await tester.pumpWidget(
422 _boilerplate(
423 const IndexedStack(
424 sizing: StackFit.expand,
425 children: <Widget>[
426 Text.rich(
427 TextSpan(
428 text: 'sub',
429 children: <InlineSpan>[
430 TextSpan(text: 'stringsub'),
431 TextSpan(text: 'stringsub'),
432 TextSpan(text: 'stringsub'),
433 ],
434 ),
435 ),
436 Text('substringsub'),
437 ],
438 ),
439 ),
440 );
441
442 expect(
443 find.textRange.ofSubstring('substringsub'),
444 findsExactly(2),
445 ); // Pattern skips overlapping matches.
446 expect(
447 find.textRange.ofSubstring('substringsub').first.evaluate().single.textRange,
448 const TextRange(start: 0, end: 12),
449 );
450 expect(
451 find.textRange.ofSubstring('substringsub').last.evaluate().single.textRange,
452 const TextRange(start: 18, end: 30),
453 );
454
455 expect(
456 find.textRange.ofSubstring('substringsub').first.evaluate().single.renderObject,
457 find.textRange.ofSubstring('substringsub').last.evaluate().single.renderObject,
458 );
459
460 expect(find.textRange.ofSubstring('substringsub', skipOffstage: false), findsExactly(3));
461 });
462
463 testWidgets('basic text span test', (WidgetTester tester) async {
464 await tester.pumpWidget(
465 _boilerplate(
466 const IndexedStack(
467 sizing: StackFit.expand,
468 children: <Widget>[
469 Text.rich(
470 TextSpan(
471 text: 'sub',
472 children: <InlineSpan>[
473 TextSpan(text: 'stringsub'),
474 TextSpan(text: 'stringsub'),
475 TextSpan(text: 'stringsub'),
476 ],
477 ),
478 ),
479 Text('substringsub'),
480 ],
481 ),
482 ),
483 );
484
485 expect(
486 find.textRange.ofSubstring('substringsub'),
487 findsExactly(2),
488 ); // Pattern skips overlapping matches.
489 expect(
490 find.textRange.ofSubstring('substringsub').first.evaluate().single.textRange,
491 const TextRange(start: 0, end: 12),
492 );
493 expect(
494 find.textRange.ofSubstring('substringsub').last.evaluate().single.textRange,
495 const TextRange(start: 18, end: 30),
496 );
497
498 expect(
499 find.textRange.ofSubstring('substringsub').first.evaluate().single.renderObject,
500 find.textRange.ofSubstring('substringsub').last.evaluate().single.renderObject,
501 );
502
503 expect(find.textRange.ofSubstring('substringsub', skipOffstage: false), findsExactly(3));
504 });
505
506 testWidgets('descendentOf', (WidgetTester tester) async {
507 await tester.pumpWidget(
508 _boilerplate(
509 const Column(
510 children: <Widget>[
511 Text.rich(TextSpan(text: 'text')),
512 Text.rich(TextSpan(text: 'text')),
513 ],
514 ),
515 ),
516 );
517
518 expect(find.textRange.ofSubstring('text'), findsExactly(2));
519 expect(find.textRange.ofSubstring('text', descendentOf: find.text('text').first), findsOne);
520 });
521
522 testWidgets('finds only static text for now', (WidgetTester tester) async {
523 await tester.pumpWidget(
524 _boilerplate(
525 EditableText(
526 controller: TextEditingController(text: 'text'),
527 focusNode: FocusNode(),
528 style: const TextStyle(),
529 cursorColor: const Color(0x00000000),
530 backgroundCursorColor: const Color(0x00000000),
531 ),
532 ),
533 );
534
535 expect(find.textRange.ofSubstring('text'), findsNothing);
536 });
537 });
538
539 testWidgets('ChainedFinders chain properly', (WidgetTester tester) async {
540 final GlobalKey key1 = GlobalKey();
541 await tester.pumpWidget(
542 _boilerplate(
543 Column(children: <Widget>[Container(key: key1, child: const Text('1')), const Text('2')]),
544 ),
545 );
546
547 // Get the text back. By correctly chaining the descendant finder's
548 // candidates, it should find 1 instead of 2. If the _LastFinder wasn't
549 // correctly chained after the descendant's candidates, the last element
550 // with a Text widget would have been 2.
551 final Text text =
552 find
553 .descendant(of: find.byKey(key1), matching: find.byType(Text))
554 .last
555 .evaluate()
556 .single
557 .widget
558 as Text;
559
560 expect(text.data, '1');
561 });
562
563 testWidgets('finds multiple subtypes', (WidgetTester tester) async {
564 await tester.pumpWidget(
565 _boilerplate(
566 Row(
567 children: <Widget>[
568 const Column(children: <Widget>[Text('Hello'), Text('World')]),
569 Column(children: <Widget>[Image(image: FileImage(File('test')))]),
570 const Column(
571 children: <Widget>[
572 SimpleGenericWidget<int>(child: Text('one')),
573 SimpleGenericWidget<double>(child: Text('pi')),
574 SimpleGenericWidget<String>(child: Text('two')),
575 ],
576 ),
577 ],
578 ),
579 ),
580 );
581
582 expect(find.bySubtype<Row>(), findsOneWidget);
583 expect(find.bySubtype<Column>(), findsNWidgets(3));
584 // Finds both rows and columns.
585 expect(find.bySubtype<Flex>(), findsNWidgets(4));
586
587 // Finds only the requested generic subtypes.
588 expect(find.bySubtype<SimpleGenericWidget<int>>(), findsOneWidget);
589 expect(find.bySubtype<SimpleGenericWidget<num>>(), findsNWidgets(2));
590 expect(find.bySubtype<SimpleGenericWidget<Object>>(), findsNWidgets(3));
591
592 // Finds all widgets.
593 final int totalWidgetCount = find.byWidgetPredicate((_) => true).evaluate().length;
594 expect(find.bySubtype<Widget>(), findsNWidgets(totalWidgetCount));
595 });
596
597 group('find.byElementPredicate', () {
598 testWidgets('fails with a custom description in the message', (WidgetTester tester) async {
599 await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
600
601 const String customDescription = 'custom description';
602 late TestFailure failure;
603 try {
604 expect(
605 find.byElementPredicate((_) => false, description: customDescription),
606 findsOneWidget,
607 );
608 } on TestFailure catch (e) {
609 failure = e;
610 }
611
612 expect(failure, isNotNull);
613 expect(
614 failure.message,
615 contains('Actual: _ElementPredicateWidgetFinder:<Found 0 widgets with $customDescription'),
616 );
617 });
618 });
619
620 group('find.byWidgetPredicate', () {
621 testWidgets('fails with a custom description in the message', (WidgetTester tester) async {
622 await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
623
624 const String customDescription = 'custom description';
625 late TestFailure failure;
626 try {
627 expect(
628 find.byWidgetPredicate((_) => false, description: customDescription),
629 findsOneWidget,
630 );
631 } on TestFailure catch (e) {
632 failure = e;
633 }
634
635 expect(failure, isNotNull);
636 expect(
637 failure.message,
638 contains('Actual: _WidgetPredicateWidgetFinder:<Found 0 widgets with $customDescription'),
639 );
640 });
641 });
642
643 group('find.descendant', () {
644 testWidgets('finds one descendant', (WidgetTester tester) async {
645 await tester.pumpWidget(
646 const Row(
647 textDirection: TextDirection.ltr,
648 children: <Widget>[Column(children: fooBarTexts)],
649 ),
650 );
651
652 expect(
653 find.descendant(of: find.widgetWithText(Row, 'foo'), matching: find.text('bar')),
654 findsOneWidget,
655 );
656 });
657
658 testWidgets('finds two descendants with different ancestors', (WidgetTester tester) async {
659 await tester.pumpWidget(
660 const Row(
661 textDirection: TextDirection.ltr,
662 children: <Widget>[Column(children: fooBarTexts), Column(children: fooBarTexts)],
663 ),
664 );
665
666 expect(
667 find.descendant(of: find.widgetWithText(Column, 'foo'), matching: find.text('bar')),
668 findsNWidgets(2),
669 );
670 });
671
672 testWidgets('fails with a descriptive message', (WidgetTester tester) async {
673 await tester.pumpWidget(
674 const Row(
675 textDirection: TextDirection.ltr,
676 children: <Widget>[
677 Column(children: <Text>[Text('foo', textDirection: TextDirection.ltr)]),
678 Text('bar', textDirection: TextDirection.ltr),
679 ],
680 ),
681 );
682
683 late TestFailure failure;
684 try {
685 expect(
686 find.descendant(of: find.widgetWithText(Column, 'foo'), matching: find.text('bar')),
687 findsOneWidget,
688 );
689 } on TestFailure catch (e) {
690 failure = e;
691 }
692
693 expect(failure, isNotNull);
694 expect(
695 failure.message,
696 contains(
697 'Actual: _DescendantWidgetFinder:<Found 0 widgets with text "bar" descending from widgets with type "Column" that are ancestors of widgets with text "foo"',
698 ),
699 );
700 });
701 });
702
703 group('find.ancestor', () {
704 testWidgets('finds one ancestor', (WidgetTester tester) async {
705 await tester.pumpWidget(
706 const Row(
707 textDirection: TextDirection.ltr,
708 children: <Widget>[Column(children: fooBarTexts)],
709 ),
710 );
711
712 expect(
713 find.ancestor(of: find.text('bar'), matching: find.widgetWithText(Row, 'foo')),
714 findsOneWidget,
715 );
716 });
717
718 testWidgets('finds two matching ancestors, one descendant', (WidgetTester tester) async {
719 await tester.pumpWidget(
720 const Directionality(
721 textDirection: TextDirection.ltr,
722 child: Row(children: <Widget>[Row(children: fooBarTexts)]),
723 ),
724 );
725
726 expect(find.ancestor(of: find.text('bar'), matching: find.byType(Row)), findsNWidgets(2));
727 });
728
729 testWidgets('fails with a descriptive message', (WidgetTester tester) async {
730 await tester.pumpWidget(
731 const Row(
732 textDirection: TextDirection.ltr,
733 children: <Widget>[
734 Column(children: <Text>[Text('foo', textDirection: TextDirection.ltr)]),
735 Text('bar', textDirection: TextDirection.ltr),
736 ],
737 ),
738 );
739
740 late TestFailure failure;
741 try {
742 expect(
743 find.ancestor(of: find.text('bar'), matching: find.widgetWithText(Column, 'foo')),
744 findsOneWidget,
745 );
746 } on TestFailure catch (e) {
747 failure = e;
748 }
749
750 expect(failure, isNotNull);
751 expect(
752 failure.message,
753 contains(
754 'Actual: _AncestorWidgetFinder:<Found 0 widgets with type "Column" that are ancestors of widgets with text "foo" that are ancestors of widgets with text "bar"',
755 ),
756 );
757 });
758
759 testWidgets('Root not matched by default', (WidgetTester tester) async {
760 await tester.pumpWidget(
761 const Row(
762 textDirection: TextDirection.ltr,
763 children: <Widget>[Column(children: fooBarTexts)],
764 ),
765 );
766
767 expect(
768 find.ancestor(of: find.byType(Column), matching: find.widgetWithText(Column, 'foo')),
769 findsNothing,
770 );
771 });
772
773 testWidgets('Match the root', (WidgetTester tester) async {
774 await tester.pumpWidget(
775 const Row(
776 textDirection: TextDirection.ltr,
777 children: <Widget>[Column(children: fooBarTexts)],
778 ),
779 );
780
781 expect(
782 find.descendant(
783 of: find.byType(Column),
784 matching: find.widgetWithText(Column, 'foo'),
785 matchRoot: true,
786 ),
787 findsOneWidget,
788 );
789 });
790
791 testWidgets('is fast in deep tree', (WidgetTester tester) async {
792 await tester.pumpWidget(
793 Directionality(
794 textDirection: TextDirection.ltr,
795 child: _deepWidgetTree(
796 depth: 500,
797 child: Row(
798 children: <Widget>[
799 _deepWidgetTree(depth: 500, child: const Column(children: fooBarTexts)),
800 ],
801 ),
802 ),
803 ),
804 );
805
806 expect(find.ancestor(of: find.text('bar'), matching: find.byType(Row)), findsOneWidget);
807 });
808 });
809
810 group('CommonSemanticsFinders', () {
811 final Widget semanticsTree = _boilerplate(
812 Semantics(
813 container: true,
814 header: true,
815 readOnly: true,
816 onCopy: () {},
817 onLongPress: () {},
818 value: 'value1',
819 hint: 'hint1',
820 label: 'label1',
821 child: Semantics(
822 container: true,
823 textField: true,
824 onSetText: (_) {},
825 onPaste: () {},
826 onLongPress: () {},
827 value: 'value2',
828 hint: 'hint2',
829 label: 'label2',
830 child: Semantics(
831 container: true,
832 readOnly: true,
833 onCopy: () {},
834 value: 'value3',
835 hint: 'hint3',
836 label: 'label3',
837 child: Semantics(
838 container: true,
839 readOnly: true,
840 onLongPress: () {},
841 value: 'value4',
842 hint: 'hint4',
843 label: 'label4',
844 child: Semantics(
845 container: true,
846 onLongPress: () {},
847 onCopy: () {},
848 value: 'value5',
849 hint: 'hint5',
850 label: 'label5',
851 ),
852 ),
853 ),
854 ),
855 ),
856 );
857
858 group('ancestor', () {
859 testWidgets('finds matching ancestor nodes', (WidgetTester tester) async {
860 await tester.pumpWidget(semanticsTree);
861
862 final FinderBase<SemanticsNode> finder = find.semantics.ancestor(
863 of: find.semantics.byLabel('label4'),
864 matching: find.semantics.byAction(SemanticsAction.copy),
865 );
866
867 expect(finder, findsExactly(2));
868 });
869
870 testWidgets('fails with descriptive message', (WidgetTester tester) async {
871 late TestFailure failure;
872 await tester.pumpWidget(semanticsTree);
873
874 final FinderBase<SemanticsNode> finder = find.semantics.ancestor(
875 of: find.semantics.byLabel('label4'),
876 matching: find.semantics.byAction(SemanticsAction.copy),
877 );
878
879 try {
880 expect(finder, findsExactly(3));
881 } on TestFailure catch (e) {
882 failure = e;
883 }
884
885 expect(
886 failure.message,
887 contains(
888 'Actual: _AncestorSemanticsFinder:<Found 2 SemanticsNodes with action "SemanticsAction.copy" that are ancestors of SemanticsNodes with label "label4"',
889 ),
890 );
891 });
892 });
893
894 group('descendant', () {
895 testWidgets('finds matching descendant nodes', (WidgetTester tester) async {
896 await tester.pumpWidget(semanticsTree);
897
898 final FinderBase<SemanticsNode> finder = find.semantics.descendant(
899 of: find.semantics.byLabel('label4'),
900 matching: find.semantics.byAction(SemanticsAction.copy),
901 );
902
903 expect(finder, findsOne);
904 });
905
906 testWidgets('fails with descriptive message', (WidgetTester tester) async {
907 late TestFailure failure;
908 await tester.pumpWidget(semanticsTree);
909
910 final FinderBase<SemanticsNode> finder = find.semantics.descendant(
911 of: find.semantics.byLabel('label4'),
912 matching: find.semantics.byAction(SemanticsAction.copy),
913 );
914
915 try {
916 expect(finder, findsNothing);
917 } on TestFailure catch (e) {
918 failure = e;
919 }
920
921 expect(
922 failure.message,
923 contains(
924 'Actual: _DescendantSemanticsFinder:<Found 1 SemanticsNode with action "SemanticsAction.copy" descending from SemanticsNode with label "label4"',
925 ),
926 );
927 });
928 });
929
930 group('byPredicate', () {
931 testWidgets('finds nodes matching given predicate', (WidgetTester tester) async {
932 final RegExp replaceRegExp = RegExp(r'^[^\d]+');
933 await tester.pumpWidget(semanticsTree);
934
935 final SemanticsFinder finder = find.semantics.byPredicate((SemanticsNode node) {
936 final int labelNum = int.tryParse(node.label.replaceAll(replaceRegExp, '')) ?? -1;
937 return labelNum > 1;
938 });
939
940 expect(finder, findsExactly(4));
941 });
942
943 testWidgets('fails with default message', (WidgetTester tester) async {
944 late TestFailure failure;
945 final RegExp replaceRegExp = RegExp(r'^[^\d]+');
946 await tester.pumpWidget(semanticsTree);
947
948 final SemanticsFinder finder = find.semantics.byPredicate((SemanticsNode node) {
949 final int labelNum = int.tryParse(node.label.replaceAll(replaceRegExp, '')) ?? -1;
950 return labelNum > 1;
951 });
952 try {
953 expect(finder, findsExactly(5));
954 } on TestFailure catch (e) {
955 failure = e;
956 }
957
958 expect(
959 failure.message,
960 contains('Actual: _PredicateSemanticsFinder:<Found 4 matching semantics predicate'),
961 );
962 });
963
964 testWidgets('fails with given message', (WidgetTester tester) async {
965 late TestFailure failure;
966 const String expected = 'custom error message';
967 final RegExp replaceRegExp = RegExp(r'^[^\d]+');
968 await tester.pumpWidget(semanticsTree);
969
970 final SemanticsFinder finder = find.semantics.byPredicate((SemanticsNode node) {
971 final int labelNum = int.tryParse(node.label.replaceAll(replaceRegExp, '')) ?? -1;
972 return labelNum > 1;
973 }, describeMatch: (_) => expected);
974 try {
975 expect(finder, findsExactly(5));
976 } on TestFailure catch (e) {
977 failure = e;
978 }
979
980 expect(failure.message, contains(expected));
981 });
982 });
983
984 group('byLabel', () {
985 testWidgets('finds nodes with matching label using String', (WidgetTester tester) async {
986 await tester.pumpWidget(semanticsTree);
987
988 final SemanticsFinder finder = find.semantics.byLabel('label3');
989
990 expect(finder, findsOne);
991 expect(finder.found.first.label, 'label3');
992 });
993
994 testWidgets('finds nodes with matching label using RegEx', (WidgetTester tester) async {
995 await tester.pumpWidget(semanticsTree);
996
997 final SemanticsFinder finder = find.semantics.byLabel(RegExp('^label.*'));
998
999 expect(finder, findsExactly(5));
1000 expect(finder.found.every((SemanticsNode node) => node.label.startsWith('label')), isTrue);
1001 });
1002
1003 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1004 late TestFailure failure;
1005 await tester.pumpWidget(semanticsTree);
1006
1007 final SemanticsFinder finder = find.semantics.byLabel('label3');
1008
1009 try {
1010 expect(finder, findsNothing);
1011 } on TestFailure catch (e) {
1012 failure = e;
1013 }
1014
1015 expect(
1016 failure.message,
1017 contains('Actual: _PredicateSemanticsFinder:<Found 1 SemanticsNode with label "label3"'),
1018 );
1019 });
1020 });
1021
1022 group('byValue', () {
1023 testWidgets('finds nodes with matching value using String', (WidgetTester tester) async {
1024 await tester.pumpWidget(semanticsTree);
1025
1026 final SemanticsFinder finder = find.semantics.byValue('value3');
1027
1028 expect(finder, findsOne);
1029 expect(finder.found.first.value, 'value3');
1030 });
1031
1032 testWidgets('finds nodes with matching value using RegEx', (WidgetTester tester) async {
1033 await tester.pumpWidget(semanticsTree);
1034
1035 final SemanticsFinder finder = find.semantics.byValue(RegExp('^value.*'));
1036
1037 expect(finder, findsExactly(5));
1038 expect(finder.found.every((SemanticsNode node) => node.value.startsWith('value')), isTrue);
1039 });
1040
1041 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1042 late TestFailure failure;
1043 await tester.pumpWidget(semanticsTree);
1044
1045 final SemanticsFinder finder = find.semantics.byValue('value3');
1046
1047 try {
1048 expect(finder, findsNothing);
1049 } on TestFailure catch (e) {
1050 failure = e;
1051 }
1052
1053 expect(
1054 failure.message,
1055 contains('Actual: _PredicateSemanticsFinder:<Found 1 SemanticsNode with value "value3"'),
1056 );
1057 });
1058 });
1059
1060 group('byHint', () {
1061 testWidgets('finds nodes with matching hint using String', (WidgetTester tester) async {
1062 await tester.pumpWidget(semanticsTree);
1063
1064 final SemanticsFinder finder = find.semantics.byHint('hint3');
1065
1066 expect(finder, findsOne);
1067 expect(finder.found.first.hint, 'hint3');
1068 });
1069
1070 testWidgets('finds nodes with matching hint using RegEx', (WidgetTester tester) async {
1071 await tester.pumpWidget(semanticsTree);
1072
1073 final SemanticsFinder finder = find.semantics.byHint(RegExp('^hint.*'));
1074
1075 expect(finder, findsExactly(5));
1076 expect(finder.found.every((SemanticsNode node) => node.hint.startsWith('hint')), isTrue);
1077 });
1078
1079 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1080 late TestFailure failure;
1081 await tester.pumpWidget(semanticsTree);
1082
1083 final SemanticsFinder finder = find.semantics.byHint('hint3');
1084
1085 try {
1086 expect(finder, findsNothing);
1087 } on TestFailure catch (e) {
1088 failure = e;
1089 }
1090
1091 expect(
1092 failure.message,
1093 contains('Actual: _PredicateSemanticsFinder:<Found 1 SemanticsNode with hint "hint3"'),
1094 );
1095 });
1096 });
1097
1098 group('byAction', () {
1099 testWidgets('finds nodes with matching action', (WidgetTester tester) async {
1100 await tester.pumpWidget(semanticsTree);
1101
1102 final SemanticsFinder finder = find.semantics.byAction(SemanticsAction.copy);
1103
1104 expect(finder, findsExactly(3));
1105 });
1106
1107 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1108 late TestFailure failure;
1109 await tester.pumpWidget(semanticsTree);
1110
1111 final SemanticsFinder finder = find.semantics.byAction(SemanticsAction.copy);
1112
1113 try {
1114 expect(finder, findsExactly(4));
1115 } on TestFailure catch (e) {
1116 failure = e;
1117 }
1118
1119 expect(
1120 failure.message,
1121 contains(
1122 'Actual: _PredicateSemanticsFinder:<Found 3 SemanticsNodes with action "SemanticsAction.copy"',
1123 ),
1124 );
1125 });
1126 });
1127
1128 group('byAnyAction', () {
1129 testWidgets('finds nodes with any matching actions', (WidgetTester tester) async {
1130 await tester.pumpWidget(semanticsTree);
1131
1132 final SemanticsFinder finder = find.semantics.byAnyAction(<SemanticsAction>[
1133 SemanticsAction.paste,
1134 SemanticsAction.longPress,
1135 ]);
1136
1137 expect(finder, findsExactly(4));
1138 });
1139
1140 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1141 late TestFailure failure;
1142 await tester.pumpWidget(semanticsTree);
1143
1144 final SemanticsFinder finder = find.semantics.byAnyAction(<SemanticsAction>[
1145 SemanticsAction.paste,
1146 SemanticsAction.longPress,
1147 ]);
1148
1149 try {
1150 expect(finder, findsExactly(5));
1151 } on TestFailure catch (e) {
1152 failure = e;
1153 }
1154
1155 expect(
1156 failure.message,
1157 contains(
1158 'Actual: _PredicateSemanticsFinder:<Found 4 SemanticsNodes with any of the following actions: [SemanticsAction.paste, SemanticsAction.longPress]:',
1159 ),
1160 );
1161 });
1162 });
1163
1164 group('byFlag', () {
1165 testWidgets('finds nodes with matching flag', (WidgetTester tester) async {
1166 await tester.pumpWidget(semanticsTree);
1167
1168 final SemanticsFinder finder = find.semantics.byFlag(SemanticsFlag.isReadOnly);
1169
1170 expect(finder, findsExactly(3));
1171 });
1172
1173 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1174 late TestFailure failure;
1175 await tester.pumpWidget(semanticsTree);
1176
1177 final SemanticsFinder finder = find.semantics.byFlag(SemanticsFlag.isReadOnly);
1178
1179 try {
1180 expect(finder, findsExactly(4));
1181 } on TestFailure catch (e) {
1182 failure = e;
1183 }
1184
1185 expect(
1186 failure.message,
1187 contains(
1188 '_PredicateSemanticsFinder:<Found 3 SemanticsNodes with flag "SemanticsFlag.isReadOnly":',
1189 ),
1190 );
1191 });
1192 });
1193
1194 group('byAnyFlag', () {
1195 testWidgets('finds nodes with any matching flag', (WidgetTester tester) async {
1196 await tester.pumpWidget(semanticsTree);
1197
1198 final SemanticsFinder finder = find.semantics.byAnyFlag(<SemanticsFlag>[
1199 SemanticsFlag.isHeader,
1200 SemanticsFlag.isTextField,
1201 ]);
1202
1203 expect(finder, findsExactly(2));
1204 });
1205
1206 testWidgets('fails with descriptive message', (WidgetTester tester) async {
1207 late TestFailure failure;
1208 await tester.pumpWidget(semanticsTree);
1209
1210 final SemanticsFinder finder = find.semantics.byAnyFlag(<SemanticsFlag>[
1211 SemanticsFlag.isHeader,
1212 SemanticsFlag.isTextField,
1213 ]);
1214
1215 try {
1216 expect(finder, findsExactly(3));
1217 } on TestFailure catch (e) {
1218 failure = e;
1219 }
1220
1221 expect(
1222 failure.message,
1223 contains(
1224 'Actual: _PredicateSemanticsFinder:<Found 2 SemanticsNodes with any of the following flags: [SemanticsFlag.isHeader, SemanticsFlag.isTextField]:',
1225 ),
1226 );
1227 });
1228 });
1229
1230 group('scrollable', () {
1231 testWidgets('can find node that can scroll up', (WidgetTester tester) async {
1232 final ScrollController controller = ScrollController();
1233 await tester.pumpWidget(
1234 MaterialApp(
1235 home: SingleChildScrollView(
1236 controller: controller,
1237 child: const SizedBox(width: 100, height: 1000),
1238 ),
1239 ),
1240 );
1241
1242 expect(
1243 find.semantics.scrollable(),
1244 containsSemantics(hasScrollUpAction: true, hasScrollDownAction: false),
1245 );
1246 });
1247
1248 testWidgets('can find node that can scroll down', (WidgetTester tester) async {
1249 final ScrollController controller = ScrollController(initialScrollOffset: 400);
1250 await tester.pumpWidget(
1251 MaterialApp(
1252 home: SingleChildScrollView(
1253 controller: controller,
1254 child: const SizedBox(width: 100, height: 1000),
1255 ),
1256 ),
1257 );
1258
1259 expect(
1260 find.semantics.scrollable(),
1261 containsSemantics(hasScrollUpAction: false, hasScrollDownAction: true),
1262 );
1263 });
1264
1265 testWidgets('can find node that can scroll left', (WidgetTester tester) async {
1266 final ScrollController controller = ScrollController();
1267 await tester.pumpWidget(
1268 MaterialApp(
1269 home: SingleChildScrollView(
1270 scrollDirection: Axis.horizontal,
1271 controller: controller,
1272 child: const SizedBox(width: 1000, height: 100),
1273 ),
1274 ),
1275 );
1276
1277 expect(
1278 find.semantics.scrollable(),
1279 containsSemantics(hasScrollLeftAction: true, hasScrollRightAction: false),
1280 );
1281 });
1282
1283 testWidgets('can find node that can scroll right', (WidgetTester tester) async {
1284 final ScrollController controller = ScrollController(initialScrollOffset: 200);
1285 await tester.pumpWidget(
1286 MaterialApp(
1287 home: SingleChildScrollView(
1288 scrollDirection: Axis.horizontal,
1289 controller: controller,
1290 child: const SizedBox(width: 1000, height: 100),
1291 ),
1292 ),
1293 );
1294
1295 expect(
1296 find.semantics.scrollable(),
1297 containsSemantics(hasScrollLeftAction: false, hasScrollRightAction: true),
1298 );
1299 });
1300
1301 testWidgets('can exclusively find node that scrolls horizontally', (
1302 WidgetTester tester,
1303 ) async {
1304 await tester.pumpWidget(
1305 const MaterialApp(
1306 home: Column(
1307 children: <Widget>[
1308 SingleChildScrollView(
1309 scrollDirection: Axis.horizontal,
1310 child: SizedBox(width: 1000, height: 100),
1311 ),
1312 Expanded(child: SingleChildScrollView(child: SizedBox(width: 100, height: 1000))),
1313 ],
1314 ),
1315 ),
1316 );
1317
1318 expect(find.semantics.scrollable(axis: Axis.horizontal), findsOne);
1319 });
1320
1321 testWidgets('can exclusively find node that scrolls vertically', (WidgetTester tester) async {
1322 await tester.pumpWidget(
1323 const MaterialApp(
1324 home: Column(
1325 children: <Widget>[
1326 SingleChildScrollView(
1327 scrollDirection: Axis.horizontal,
1328 child: SizedBox(width: 1000, height: 100),
1329 ),
1330 Expanded(child: SingleChildScrollView(child: SizedBox(width: 100, height: 1000))),
1331 ],
1332 ),
1333 ),
1334 );
1335
1336 expect(find.semantics.scrollable(axis: Axis.vertical), findsOne);
1337 });
1338 });
1339 });
1340
1341 group('FinderBase', () {
1342 group('describeMatch', () {
1343 test('is used for Finder and results', () {
1344 const String expected = 'Fake finder describe match';
1345 final _FakeFinder finder = _FakeFinder(
1346 describeMatchCallback: (_) {
1347 return expected;
1348 },
1349 );
1350
1351 expect(finder.evaluate().toString(), contains(expected));
1352 expect(finder.toString(describeSelf: true), contains(expected));
1353 });
1354
1355 for (int i = 0; i < 4; i++) {
1356 test('gets expected plurality for $i when reporting results from find', () {
1357 final Plurality expected = switch (i) {
1358 0 => Plurality.zero,
1359 1 => Plurality.one,
1360 _ => Plurality.many,
1361 };
1362 late final Plurality actual;
1363 final _FakeFinder finder = _FakeFinder(
1364 describeMatchCallback: (Plurality plurality) {
1365 actual = plurality;
1366 return 'Fake description';
1367 },
1368 findInCandidatesCallback:
1369 (_) => Iterable<String>.generate(i, (int index) => index.toString()),
1370 );
1371 finder.evaluate().toString();
1372
1373 expect(actual, expected);
1374 });
1375
1376 test('gets expected plurality for $i when reporting results from toString', () {
1377 final Plurality expected = switch (i) {
1378 0 => Plurality.zero,
1379 1 => Plurality.one,
1380 _ => Plurality.many,
1381 };
1382 late final Plurality actual;
1383 final _FakeFinder finder = _FakeFinder(
1384 describeMatchCallback: (Plurality plurality) {
1385 actual = plurality;
1386 return 'Fake description';
1387 },
1388 findInCandidatesCallback:
1389 (_) => Iterable<String>.generate(i, (int index) => index.toString()),
1390 );
1391 finder.toString();
1392
1393 expect(actual, expected);
1394 });
1395
1396 test('always gets many when describing finder', () {
1397 const Plurality expected = Plurality.many;
1398 late final Plurality actual;
1399 final _FakeFinder finder = _FakeFinder(
1400 describeMatchCallback: (Plurality plurality) {
1401 actual = plurality;
1402 return 'Fake description';
1403 },
1404 findInCandidatesCallback:
1405 (_) => Iterable<String>.generate(i, (int index) => index.toString()),
1406 );
1407 finder.toString(describeSelf: true);
1408
1409 expect(actual, expected);
1410 });
1411 }
1412 });
1413
1414 test('findInCandidates gets allCandidates', () {
1415 final List<String> expected = <String>['Test1', 'Test2', 'Test3', 'Test4'];
1416 late final List<String> actual;
1417 final _FakeFinder finder = _FakeFinder(
1418 allCandidatesCallback: () => expected,
1419 findInCandidatesCallback: (Iterable<String> candidates) {
1420 actual = candidates.toList();
1421 return candidates;
1422 },
1423 );
1424 finder.evaluate();
1425
1426 expect(actual, expected);
1427 });
1428
1429 test('allCandidates calculated for each find', () {
1430 const int expectedCallCount = 3;
1431 int actualCallCount = 0;
1432 final _FakeFinder finder = _FakeFinder(
1433 allCandidatesCallback: () {
1434 actualCallCount++;
1435 return <String>['test'];
1436 },
1437 );
1438 for (int i = 0; i < expectedCallCount; i++) {
1439 finder.evaluate();
1440 }
1441
1442 expect(actualCallCount, expectedCallCount);
1443 });
1444
1445 test('allCandidates only called once while caching', () {
1446 int actualCallCount = 0;
1447 final _FakeFinder finder = _FakeFinder(
1448 allCandidatesCallback: () {
1449 actualCallCount++;
1450 return <String>['test'];
1451 },
1452 );
1453 finder.runCached(() {
1454 for (int i = 0; i < 5; i++) {
1455 finder.evaluate();
1456 finder.tryEvaluate();
1457 final FinderResult<String> _ = finder.found;
1458 }
1459 });
1460
1461 expect(actualCallCount, 1);
1462 });
1463
1464 group('tryFind', () {
1465 test('returns false if no results', () {
1466 final _FakeFinder finder = _FakeFinder(findInCandidatesCallback: (_) => <String>[]);
1467
1468 expect(finder.tryEvaluate(), false);
1469 });
1470
1471 test('returns true if results are available', () {
1472 final _FakeFinder finder = _FakeFinder(
1473 findInCandidatesCallback: (_) => <String>['Results'],
1474 );
1475
1476 expect(finder.tryEvaluate(), true);
1477 });
1478 });
1479
1480 group('found', () {
1481 test('throws before any calls to evaluate or tryEvaluate', () {
1482 final _FakeFinder finder = _FakeFinder();
1483
1484 expect(finder.hasFound, false);
1485 expect(() => finder.found, throwsAssertionError);
1486 });
1487
1488 test('has same results as evaluate after call to evaluate', () {
1489 final _FakeFinder finder = _FakeFinder();
1490 final FinderResult<String> expected = finder.evaluate();
1491
1492 expect(finder.hasFound, true);
1493 expect(finder.found, expected);
1494 });
1495
1496 test('has expected results after call to tryFind', () {
1497 final Iterable<String> expected = Iterable<String>.generate(10, (int i) => i.toString());
1498 final _FakeFinder finder = _FakeFinder(findInCandidatesCallback: (_) => expected);
1499 finder.tryEvaluate();
1500
1501 expect(finder.hasFound, true);
1502 expect(finder.found, orderedEquals(expected));
1503 });
1504 });
1505 });
1506}
1507
1508Widget _boilerplate(Widget child) {
1509 return Directionality(
1510 textDirection: TextDirection.ltr,
1511 child: Navigator(
1512 onGenerateRoute: (RouteSettings settings) {
1513 return MaterialPageRoute<void>(builder: (BuildContext context) => child);
1514 },
1515 ),
1516 );
1517}
1518
1519class SimpleCustomSemanticsWidget extends LeafRenderObjectWidget {
1520 const SimpleCustomSemanticsWidget(this.label, {super.key});
1521
1522 final String label;
1523
1524 @override
1525 RenderObject createRenderObject(BuildContext context) => SimpleCustomSemanticsRenderObject(label);
1526}
1527
1528class SimpleCustomSemanticsRenderObject extends RenderBox {
1529 SimpleCustomSemanticsRenderObject(this.label);
1530
1531 final String label;
1532
1533 @override
1534 bool get sizedByParent => true;
1535
1536 @override
1537 Size computeDryLayout(BoxConstraints constraints) {
1538 return constraints.smallest;
1539 }
1540
1541 @override
1542 void describeSemanticsConfiguration(SemanticsConfiguration config) {
1543 super.describeSemanticsConfiguration(config);
1544 config
1545 ..label = label
1546 ..textDirection = TextDirection.ltr;
1547 }
1548}
1549
1550class SimpleGenericWidget<T> extends StatelessWidget {
1551 const SimpleGenericWidget({required Widget child, super.key}) : _child = child;
1552
1553 final Widget _child;
1554
1555 @override
1556 Widget build(BuildContext context) {
1557 return _child;
1558 }
1559}
1560
1561/// Wraps [child] in [depth] layers of [SizedBox]
1562Widget _deepWidgetTree({required int depth, required Widget child}) {
1563 Widget tree = child;
1564 for (int i = 0; i < depth; i += 1) {
1565 tree = SizedBox(child: tree);
1566 }
1567 return tree;
1568}
1569
1570class _FakeFinder extends FinderBase<String> {
1571 _FakeFinder({
1572 this.allCandidatesCallback,
1573 this.describeMatchCallback,
1574 this.findInCandidatesCallback,
1575 });
1576
1577 final Iterable<String> Function()? allCandidatesCallback;
1578 final DescribeMatchCallback? describeMatchCallback;
1579 final Iterable<String> Function(Iterable<String> candidates)? findInCandidatesCallback;
1580
1581 @override
1582 Iterable<String> get allCandidates {
1583 return allCandidatesCallback?.call() ?? <String>['String 1', 'String 2', 'String 3'];
1584 }
1585
1586 @override
1587 String describeMatch(Plurality plurality) {
1588 return describeMatchCallback?.call(plurality) ??
1589 switch (plurality) {
1590 Plurality.one => 'String',
1591 Plurality.many || Plurality.zero => 'Strings',
1592 };
1593 }
1594
1595 @override
1596 Iterable<String> findInCandidates(Iterable<String> candidates) {
1597 return findInCandidatesCallback?.call(candidates) ?? candidates;
1598 }
1599}
1600

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com