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:async';
6import 'dart:io';
7
8import 'package:flutter/cupertino.dart';
9import 'package:flutter/foundation.dart';
10import 'package:flutter/gestures.dart';
11import 'package:flutter/material.dart';
12import 'package:flutter/rendering.dart';
13import 'package:flutter/scheduler.dart';
14import 'package:flutter/services.dart';
15import 'package:flutter_test/flutter_test.dart';
16import 'package:matcher/expect.dart' as matcher;
17import 'package:matcher/src/expect/async_matcher.dart';
18
19import 'multi_view_testing.dart';
20
21void main() {
22 group('expectLater', () {
23 testWidgets('completes when matcher completes', (WidgetTester tester) async {
24 final Completer<void> completer = Completer<void>();
25 final Future<void> future = expectLater(null, FakeMatcher(completer));
26 String? result;
27 future.then<void>((void value) {
28 result = '123';
29 });
30 matcher.expect(result, isNull);
31 completer.complete();
32 matcher.expect(result, isNull);
33 await future;
34 await tester.pump();
35 matcher.expect(result, '123');
36 });
37
38 testWidgets('respects the skip flag', (WidgetTester tester) async {
39 final Completer<void> completer = Completer<void>();
40 final Future<void> future = expectLater(
41 null,
42 FakeMatcher(completer),
43 skip: 'testing skip',
44 ); // [intended] API testing
45 bool completed = false;
46 future.then<void>((_) {
47 completed = true;
48 });
49 matcher.expect(completed, isFalse);
50 await future;
51 matcher.expect(completed, isTrue);
52 });
53 });
54
55 group('group retry flag allows test to run multiple times', () {
56 bool retried = false;
57 group('the group with retry flag', () {
58 testWidgets('the test inside it', (WidgetTester tester) async {
59 addTearDown(() => retried = true);
60 if (!retried) {
61 debugPrint('DISREGARD NEXT FAILURE, IT IS EXPECTED');
62 }
63 expect(retried, isTrue);
64 });
65 }, retry: 1);
66 });
67
68 group('testWidget retry flag allows test to run multiple times', () {
69 bool retried = false;
70 testWidgets('the test with retry flag', (WidgetTester tester) async {
71 addTearDown(() => retried = true);
72 if (!retried) {
73 debugPrint('DISREGARD NEXT FAILURE, IT IS EXPECTED');
74 }
75 expect(retried, isTrue);
76 }, retry: 1);
77 });
78
79 group('respects the group skip flag', () {
80 testWidgets('should be skipped', (WidgetTester tester) async {
81 expect(false, true);
82 });
83 }, skip: true); // [intended] API testing
84
85 group('pumping', () {
86 testWidgets('pumping', (WidgetTester tester) async {
87 await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
88 int count;
89
90 final AnimationController test = AnimationController(
91 duration: const Duration(milliseconds: 5100),
92 vsync: tester,
93 );
94 count = await tester.pumpAndSettle(const Duration(seconds: 1));
95 expect(count, 1); // it always pumps at least one frame
96
97 test.forward(from: 0.0);
98 count = await tester.pumpAndSettle(const Duration(seconds: 1));
99 // 1 frame at t=0, starting the animation
100 // 1 frame at t=1
101 // 1 frame at t=2
102 // 1 frame at t=3
103 // 1 frame at t=4
104 // 1 frame at t=5
105 // 1 frame at t=6, ending the animation
106 expect(count, 7);
107
108 test.forward(from: 0.0);
109 await tester.pump(); // starts the animation
110 count = await tester.pumpAndSettle(const Duration(seconds: 1));
111 expect(count, 6);
112
113 test.forward(from: 0.0);
114 await tester.pump(); // starts the animation
115 await tester.pump(); // has no effect
116 count = await tester.pumpAndSettle(const Duration(seconds: 1));
117 expect(count, 6);
118 });
119
120 testWidgets('pumpFrames', (WidgetTester tester) async {
121 final List<int> logPaints = <int>[];
122 int? initial;
123
124 final Widget target = _AlwaysAnimating(
125 onPaint: () {
126 final int current = SchedulerBinding.instance.currentFrameTimeStamp.inMicroseconds;
127 initial ??= current;
128 logPaints.add(current - initial!);
129 },
130 );
131
132 await tester.pumpFrames(target, const Duration(milliseconds: 55));
133
134 // `pumpframes` defaults to 16 milliseconds and 683 microseconds per pump,
135 // so we expect 4 pumps of 16683 microseconds each in the 55ms duration.
136 expect(logPaints, <int>[0, 16683, 33366, 50049]);
137 logPaints.clear();
138
139 await tester.pumpFrames(
140 target,
141 const Duration(milliseconds: 30),
142 const Duration(milliseconds: 10),
143 );
144
145 // Since `pumpFrames` was given a 10ms interval per pump, we expect the
146 // results to continue from 50049 with 10000 microseconds per pump over
147 // the 30ms duration.
148 expect(logPaints, <int>[60049, 70049, 80049]);
149 });
150 });
151 group('pageBack', () {
152 testWidgets('fails when there are no back buttons', (WidgetTester tester) async {
153 await tester.pumpWidget(Container());
154
155 expect(expectAsync0(tester.pageBack), throwsA(isA<TestFailure>()));
156 });
157
158 testWidgets('successfully taps material back buttons', (WidgetTester tester) async {
159 final TransitionDurationObserver observer = TransitionDurationObserver();
160 await tester.pumpWidget(
161 MaterialApp(
162 navigatorObservers: <NavigatorObserver>[observer],
163 home: Center(
164 child: Builder(
165 builder: (BuildContext context) {
166 return ElevatedButton(
167 child: const Text('Next'),
168 onPressed: () {
169 Navigator.push<void>(
170 context,
171 MaterialPageRoute<void>(
172 builder: (BuildContext context) {
173 return Scaffold(appBar: AppBar(title: const Text('Page 2')));
174 },
175 ),
176 );
177 },
178 );
179 },
180 ),
181 ),
182 ),
183 );
184
185 await tester.tap(find.text('Next'));
186 await tester.pump();
187 await tester.pump(observer.transitionDuration + const Duration(milliseconds: 1));
188
189 expect(find.text('Next'), findsNothing);
190 expect(find.text('Page 2'), findsOneWidget);
191
192 await tester.pageBack();
193 await tester.pump();
194 await tester.pump(observer.transitionDuration + const Duration(milliseconds: 1));
195
196 expect(find.text('Next'), findsOneWidget);
197 expect(find.text('Page 2'), findsNothing);
198 });
199
200 testWidgets('successfully taps cupertino back buttons', (WidgetTester tester) async {
201 final TransitionDurationObserver observer = TransitionDurationObserver();
202 await tester.pumpWidget(
203 MaterialApp(
204 navigatorObservers: <NavigatorObserver>[observer],
205 home: Center(
206 child: Builder(
207 builder: (BuildContext context) {
208 return CupertinoButton(
209 child: const Text('Next'),
210 onPressed: () {
211 Navigator.push<void>(
212 context,
213 CupertinoPageRoute<void>(
214 builder: (BuildContext context) {
215 return CupertinoPageScaffold(
216 navigationBar: const CupertinoNavigationBar(middle: Text('Page 2')),
217 child: Container(),
218 );
219 },
220 ),
221 );
222 },
223 );
224 },
225 ),
226 ),
227 ),
228 );
229
230 await tester.tap(find.text('Next'));
231 await tester.pump();
232 await tester.pump(observer.transitionDuration);
233
234 await tester.pageBack();
235 await tester.pump();
236 await tester.pumpAndSettle();
237
238 expect(find.text('Next'), findsOneWidget);
239 expect(find.text('Page 2'), findsNothing);
240 });
241 });
242
243 testWidgets('hasRunningAnimations control test', (WidgetTester tester) async {
244 final AnimationController controller = AnimationController(
245 duration: const Duration(seconds: 1),
246 vsync: const TestVSync(),
247 );
248 expect(tester.hasRunningAnimations, isFalse);
249 controller.forward();
250 expect(tester.hasRunningAnimations, isTrue);
251 controller.stop();
252 expect(tester.hasRunningAnimations, isFalse);
253 controller.forward();
254 expect(tester.hasRunningAnimations, isTrue);
255 await tester.pumpAndSettle();
256 expect(tester.hasRunningAnimations, isFalse);
257 });
258
259 testWidgets('pumpAndSettle control test', (WidgetTester tester) async {
260 final AnimationController controller = AnimationController(
261 duration: const Duration(minutes: 525600),
262 vsync: const TestVSync(),
263 );
264 expect(await tester.pumpAndSettle(), 1);
265 controller.forward();
266 try {
267 await tester.pumpAndSettle();
268 expect(true, isFalse);
269 } catch (e) {
270 expect(e, isFlutterError);
271 }
272 controller.stop();
273 expect(await tester.pumpAndSettle(), 1);
274 controller.duration = const Duration(seconds: 1);
275 controller.forward();
276 expect(
277 await tester.pumpAndSettle(const Duration(milliseconds: 300)),
278 5,
279 ); // 0, 300, 600, 900, 1200ms
280 });
281
282 testWidgets('Input event array', (WidgetTester tester) async {
283 final List<String> logs = <String>[];
284
285 await tester.pumpWidget(
286 Directionality(
287 textDirection: TextDirection.ltr,
288 child: Listener(
289 onPointerDown: (PointerDownEvent event) => logs.add('down ${event.buttons}'),
290 onPointerMove: (PointerMoveEvent event) => logs.add('move ${event.buttons}'),
291 onPointerUp: (PointerUpEvent event) => logs.add('up ${event.buttons}'),
292 child: const Text('test'),
293 ),
294 ),
295 );
296
297 final Offset location = tester.getCenter(find.text('test'));
298 final List<PointerEventRecord> records = <PointerEventRecord>[
299 PointerEventRecord(Duration.zero, <PointerEvent>[
300 // Typically PointerAddedEvent is not used in testers, but for records
301 // captured on a device it is usually what start a gesture.
302 PointerAddedEvent(position: location),
303 PointerDownEvent(position: location, buttons: kSecondaryMouseButton, pointer: 1),
304 ]),
305 ...<PointerEventRecord>[
306 for (
307 Duration t = const Duration(milliseconds: 5);
308 t < const Duration(milliseconds: 80);
309 t += const Duration(milliseconds: 16)
310 )
311 PointerEventRecord(t, <PointerEvent>[
312 PointerMoveEvent(
313 timeStamp: t - const Duration(milliseconds: 1),
314 position: location,
315 buttons: kSecondaryMouseButton,
316 pointer: 1,
317 ),
318 ]),
319 ],
320 PointerEventRecord(const Duration(milliseconds: 80), <PointerEvent>[
321 PointerUpEvent(
322 timeStamp: const Duration(milliseconds: 79),
323 position: location,
324 buttons: kSecondaryMouseButton,
325 pointer: 1,
326 ),
327 ]),
328 ];
329 final List<Duration> timeDiffs = await tester.handlePointerEventRecord(records);
330 expect(timeDiffs.length, records.length);
331 for (final Duration diff in timeDiffs) {
332 expect(diff, Duration.zero);
333 }
334
335 const String b = '$kSecondaryMouseButton';
336 expect(logs.first, 'down $b');
337 for (int i = 1; i < logs.length - 1; i++) {
338 expect(logs[i], 'move $b');
339 }
340 expect(logs.last, 'up $b');
341 });
342
343 group('runAsync', () {
344 testWidgets('works with no async calls', (WidgetTester tester) async {
345 String? value;
346 await tester.runAsync(() async {
347 value = '123';
348 });
349 expect(value, '123');
350 });
351
352 testWidgets('works with real async calls', (WidgetTester tester) async {
353 final StringBuffer buf = StringBuffer('1');
354 await tester.runAsync(() async {
355 buf.write('2');
356 //ignore: avoid_slow_async_io
357 await Directory.current.stat();
358 buf.write('3');
359 });
360 buf.write('4');
361 expect(buf.toString(), '1234');
362 });
363
364 testWidgets('propagates return values', (WidgetTester tester) async {
365 final String? value = await tester.runAsync<String>(() async {
366 return '123';
367 });
368 expect(value, '123');
369 });
370
371 testWidgets('reports errors via framework', (WidgetTester tester) async {
372 final String? value = await tester.runAsync<String>(() async {
373 throw ArgumentError();
374 });
375 expect(value, isNull);
376 expect(tester.takeException(), isArgumentError);
377 });
378
379 testWidgets('disallows re-entry', (WidgetTester tester) async {
380 final Completer<void> completer = Completer<void>();
381 tester.runAsync<void>(() => completer.future);
382 expect(() => tester.runAsync(() async {}), throwsA(isA<TestFailure>()));
383 completer.complete();
384 });
385
386 testWidgets('maintains existing zone values', (WidgetTester tester) async {
387 final Object key = Object();
388 await runZoned<Future<void>>(() {
389 expect(Zone.current[key], 'abczed');
390 return tester.runAsync<void>(() async {
391 expect(Zone.current[key], 'abczed');
392 });
393 }, zoneValues: <dynamic, dynamic>{key: 'abczed'});
394 });
395
396 testWidgets('control test (return value)', (WidgetTester tester) async {
397 final String? result = await tester.binding.runAsync<String>(() async => 'Judy Turner');
398 expect(result, 'Judy Turner');
399 });
400
401 testWidgets('async throw', (WidgetTester tester) async {
402 final String? result = await tester.binding.runAsync<Never>(
403 () async => throw Exception('Lois Dilettente'),
404 );
405 expect(result, isNull);
406 expect(tester.takeException(), isNotNull);
407 });
408
409 testWidgets('sync throw', (WidgetTester tester) async {
410 final String? result = await tester.binding.runAsync<Never>(
411 () => throw Exception('Butch Barton'),
412 );
413 expect(result, isNull);
414 expect(tester.takeException(), isNotNull);
415 });
416
417 testWidgets('runs in original zone', (WidgetTester tester) async {
418 final Zone testZone = Zone.current;
419 Zone? runAsyncZone;
420 Zone? timerZone;
421 Zone? periodicTimerZone;
422 Zone? microtaskZone;
423
424 Zone? innerZone;
425 Zone? innerTimerZone;
426 Zone? innerPeriodicTimerZone;
427 Zone? innerMicrotaskZone;
428
429 await tester.binding.runAsync<void>(() async {
430 final Zone currentZone = Zone.current;
431 runAsyncZone = currentZone;
432
433 // Complete a future when all callbacks have completed.
434 int pendingCallbacks = 6;
435 final Completer<void> callbacksDone = Completer<void>();
436 void onCallback() {
437 if (--pendingCallbacks == 0) {
438 testZone.run(() {
439 callbacksDone.complete(null);
440 });
441 }
442 }
443
444 // On the runAsync zone itself.
445 currentZone.createTimer(Duration.zero, () {
446 timerZone = Zone.current;
447 onCallback();
448 });
449 currentZone.createPeriodicTimer(Duration.zero, (Timer timer) {
450 timer.cancel();
451 periodicTimerZone = Zone.current;
452 onCallback();
453 });
454 currentZone.scheduleMicrotask(() {
455 microtaskZone = Zone.current;
456 onCallback();
457 });
458
459 // On a nested user-created zone.
460 final Zone inner = runZoned(() => Zone.current);
461 innerZone = inner;
462 inner.createTimer(Duration.zero, () {
463 innerTimerZone = Zone.current;
464 onCallback();
465 });
466 inner.createPeriodicTimer(Duration.zero, (Timer timer) {
467 timer.cancel();
468 innerPeriodicTimerZone = Zone.current;
469 onCallback();
470 });
471 inner.scheduleMicrotask(() {
472 innerMicrotaskZone = Zone.current;
473 onCallback();
474 });
475
476 await callbacksDone.future;
477 });
478 expect(runAsyncZone, isNotNull);
479 expect(timerZone, same(runAsyncZone));
480 expect(periodicTimerZone, same(runAsyncZone));
481 expect(microtaskZone, same(runAsyncZone));
482
483 expect(innerZone, isNotNull);
484 expect(innerTimerZone, same(innerZone));
485 expect(innerPeriodicTimerZone, same(innerZone));
486 expect(innerMicrotaskZone, same(innerZone));
487
488 expect(runAsyncZone, isNot(same(testZone)));
489 expect(runAsyncZone, isNot(same(innerZone)));
490 expect(innerZone, isNot(same(testZone)));
491 });
492 });
493
494 group('showKeyboard', () {
495 testWidgets('can be called twice', (WidgetTester tester) async {
496 await tester.pumpWidget(
497 MaterialApp(
498 home: Material(child: Center(child: TextFormField())),
499 ),
500 );
501 await tester.showKeyboard(find.byType(TextField));
502 await tester.testTextInput.receiveAction(TextInputAction.done);
503 await tester.pump();
504 await tester.showKeyboard(find.byType(TextField));
505 await tester.testTextInput.receiveAction(TextInputAction.done);
506 await tester.pump();
507 await tester.showKeyboard(find.byType(TextField));
508 await tester.showKeyboard(find.byType(TextField));
509 await tester.pump();
510 });
511
512 testWidgets(
513 'can focus on offstage text input field if finder says not to skip offstage nodes',
514 (WidgetTester tester) async {
515 await tester.pumpWidget(
516 MaterialApp(
517 home: Material(child: Offstage(child: TextFormField())),
518 ),
519 );
520 await tester.showKeyboard(find.byType(TextField, skipOffstage: false));
521 },
522 );
523 });
524
525 testWidgets('verifyTickersWereDisposed control test', (WidgetTester tester) async {
526 late FlutterError error;
527 final Ticker ticker = tester.createTicker((Duration duration) {});
528 ticker.start();
529 try {
530 tester.verifyTickersWereDisposed('');
531 } on FlutterError catch (e) {
532 error = e;
533 } finally {
534 expect(error, isNotNull);
535 expect(error.diagnostics.length, 4);
536 expect(error.diagnostics[2].level, DiagnosticLevel.hint);
537 expect(
538 error.diagnostics[2].toStringDeep(),
539 'Tickers used by AnimationControllers should be disposed by\n'
540 'calling dispose() on the AnimationController itself. Otherwise,\n'
541 'the ticker will leak.\n',
542 );
543 expect(error.diagnostics.last, isA<DiagnosticsProperty<Ticker>>());
544 expect(error.diagnostics.last.value, ticker);
545 expect(
546 error.toStringDeep(),
547 startsWith(
548 'FlutterError\n'
549 ' A Ticker was active .\n'
550 ' All Tickers must be disposed.\n'
551 ' Tickers used by AnimationControllers should be disposed by\n'
552 ' calling dispose() on the AnimationController itself. Otherwise,\n'
553 ' the ticker will leak.\n'
554 ' The offending ticker was:\n'
555 ' _TestTicker()\n',
556 ),
557 );
558 }
559 ticker.stop();
560 });
561
562 group('testWidgets variants work', () {
563 int numberOfVariationsRun = 0;
564
565 testWidgets(
566 'variant tests run all values provided',
567 (WidgetTester tester) async {
568 if (debugDefaultTargetPlatformOverride == null) {
569 expect(numberOfVariationsRun, equals(TargetPlatform.values.length));
570 } else {
571 numberOfVariationsRun += 1;
572 }
573 },
574 variant: TargetPlatformVariant(TargetPlatform.values.toSet()),
575 );
576
577 testWidgets(
578 'variant tests have descriptions with details',
579 (WidgetTester tester) async {
580 if (debugDefaultTargetPlatformOverride == null) {
581 expect(tester.testDescription, equals('variant tests have descriptions with details'));
582 } else {
583 expect(
584 tester.testDescription,
585 equals(
586 'variant tests have descriptions with details (variant: $debugDefaultTargetPlatformOverride)',
587 ),
588 );
589 }
590 },
591 variant: TargetPlatformVariant(TargetPlatform.values.toSet()),
592 );
593 });
594
595 group('TargetPlatformVariant', () {
596 int numberOfVariationsRun = 0;
597 TargetPlatform? origTargetPlatform;
598
599 setUpAll(() {
600 origTargetPlatform = debugDefaultTargetPlatformOverride;
601 });
602
603 tearDownAll(() {
604 expect(debugDefaultTargetPlatformOverride, equals(origTargetPlatform));
605 });
606
607 testWidgets(
608 'TargetPlatformVariant.only tests given value',
609 (WidgetTester tester) async {
610 expect(debugDefaultTargetPlatformOverride, equals(TargetPlatform.iOS));
611 expect(defaultTargetPlatform, equals(TargetPlatform.iOS));
612 },
613 variant: TargetPlatformVariant.only(TargetPlatform.iOS),
614 );
615
616 group('all', () {
617 testWidgets('TargetPlatformVariant.all tests run all variants', (WidgetTester tester) async {
618 if (debugDefaultTargetPlatformOverride == null) {
619 expect(numberOfVariationsRun, equals(TargetPlatform.values.length));
620 } else {
621 numberOfVariationsRun += 1;
622 }
623 }, variant: TargetPlatformVariant.all());
624
625 const Set<TargetPlatform> excludePlatforms = <TargetPlatform>{
626 TargetPlatform.android,
627 TargetPlatform.linux,
628 };
629 testWidgets(
630 'TargetPlatformVariant.all, excluding runs an all variants except those provided in excluding',
631 (WidgetTester tester) async {
632 if (debugDefaultTargetPlatformOverride == null) {
633 expect(
634 numberOfVariationsRun,
635 equals(TargetPlatform.values.length - excludePlatforms.length),
636 );
637 expect(
638 excludePlatforms,
639 isNot(contains(debugDefaultTargetPlatformOverride)),
640 reason: 'this test should not run on any platform in excludePlatforms',
641 );
642 } else {
643 numberOfVariationsRun += 1;
644 }
645 },
646 variant: TargetPlatformVariant.all(excluding: excludePlatforms),
647 );
648 });
649
650 testWidgets('TargetPlatformVariant.desktop + mobile contains all TargetPlatform values', (
651 WidgetTester tester,
652 ) async {
653 final TargetPlatformVariant all = TargetPlatformVariant.all();
654 final TargetPlatformVariant desktop = TargetPlatformVariant.all();
655 final TargetPlatformVariant mobile = TargetPlatformVariant.all();
656 expect(desktop.values.union(mobile.values), equals(all.values));
657 });
658 });
659
660 group('Pending timer', () {
661 late TestExceptionReporter currentExceptionReporter;
662 setUp(() {
663 currentExceptionReporter = reportTestException;
664 });
665
666 tearDown(() {
667 reportTestException = currentExceptionReporter;
668 });
669
670 test('Throws assertion message without code', () async {
671 late FlutterErrorDetails flutterErrorDetails;
672 reportTestException = (FlutterErrorDetails details, String testDescription) {
673 flutterErrorDetails = details;
674 };
675
676 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
677 debugPrint('DISREGARD NEXT PENDING TIMER LIST, IT IS EXPECTED');
678 await binding.runTest(() async {
679 final Timer timer = Timer(const Duration(seconds: 1), () {});
680 expect(timer.isActive, true);
681 }, () {});
682
683 expect(flutterErrorDetails.exception, isA<AssertionError>());
684 expect(
685 (flutterErrorDetails.exception as AssertionError).message,
686 'A Timer is still pending even after the widget tree was disposed.',
687 );
688 expect(binding.inTest, true);
689 binding.postTest();
690 });
691 });
692
693 group('Accessibility announcements testing API', () {
694 testWidgets('Returns the list of announcements', (WidgetTester tester) async {
695 // Make sure the handler is properly set
696 expect(
697 TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.checkMockMessageHandler(
698 SystemChannels.accessibility.name,
699 null,
700 ),
701 isFalse,
702 );
703
704 await SemanticsService.announce('announcement 1', TextDirection.ltr);
705 await SemanticsService.announce(
706 'announcement 2',
707 TextDirection.rtl,
708 assertiveness: Assertiveness.assertive,
709 );
710 await SemanticsService.announce('announcement 3', TextDirection.rtl);
711
712 final List<CapturedAccessibilityAnnouncement> list = tester.takeAnnouncements();
713 expect(list, hasLength(3));
714 final CapturedAccessibilityAnnouncement first = list[0];
715 expect(first.message, 'announcement 1');
716 expect(first.textDirection, TextDirection.ltr);
717
718 final CapturedAccessibilityAnnouncement second = list[1];
719 expect(second.message, 'announcement 2');
720 expect(second.textDirection, TextDirection.rtl);
721 expect(second.assertiveness, Assertiveness.assertive);
722
723 final CapturedAccessibilityAnnouncement third = list[2];
724 expect(third.message, 'announcement 3');
725 expect(third.textDirection, TextDirection.rtl);
726 expect(third.assertiveness, Assertiveness.polite);
727
728 final List<CapturedAccessibilityAnnouncement> emptyList = tester.takeAnnouncements();
729 expect(emptyList, <CapturedAccessibilityAnnouncement>[]);
730 });
731
732 test('New test API is not breaking existing tests', () async {
733 final List<Map<dynamic, dynamic>> log = <Map<dynamic, dynamic>>[];
734
735 Future<dynamic> handleMessage(dynamic mockMessage) async {
736 final Map<dynamic, dynamic> message = mockMessage as Map<dynamic, dynamic>;
737 log.add(message);
738 }
739
740 TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
741 .setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, handleMessage);
742
743 await SemanticsService.announce(
744 'announcement 1',
745 TextDirection.rtl,
746 assertiveness: Assertiveness.assertive,
747 );
748 expect(
749 log,
750 equals(<Map<String, dynamic>>[
751 <String, dynamic>{
752 'type': 'announce',
753 'data': <String, dynamic>{
754 'message': 'announcement 1',
755 'textDirection': 0,
756 'assertiveness': 1,
757 },
758 },
759 ]),
760 );
761
762 // Remove the handler
763 TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
764 .setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null);
765 });
766
767 tearDown(() {
768 // Make sure that the handler is removed in [TestWidgetsFlutterBinding.postTest]
769 expect(
770 TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.checkMockMessageHandler(
771 SystemChannels.accessibility.name,
772 null,
773 ),
774 isTrue,
775 );
776 });
777 });
778
779 testWidgets('wrapWithView: false does not include View', (WidgetTester tester) async {
780 FlutterView? flutterView;
781 View? view;
782 int builderCount = 0;
783 await tester.pumpWidget(
784 wrapWithView: false,
785 Builder(
786 builder: (BuildContext context) {
787 builderCount++;
788 flutterView = View.maybeOf(context);
789 view = context.findAncestorWidgetOfExactType<View>();
790 return const ViewCollection(views: <Widget>[]);
791 },
792 ),
793 );
794
795 expect(builderCount, 1);
796 expect(view, isNull);
797 expect(flutterView, isNull);
798 expect(find.byType(View), findsNothing);
799 });
800
801 testWidgets('passing a view to pumpWidget with wrapWithView: true throws', (
802 WidgetTester tester,
803 ) async {
804 await tester.pumpWidget(View(view: FakeView(tester.view), child: const SizedBox.shrink()));
805 expect(
806 tester.takeException(),
807 isFlutterError.having(
808 (FlutterError e) => e.message,
809 'message',
810 contains('consider setting the "wrapWithView" parameter of that method to false'),
811 ),
812 );
813 });
814
815 testWidgets('can pass a View to pumpWidget when wrapWithView: false', (
816 WidgetTester tester,
817 ) async {
818 await tester.pumpWidget(
819 wrapWithView: false,
820 View(view: tester.view, child: const SizedBox.shrink()),
821 );
822 expect(find.byType(View), findsOne);
823 });
824}
825
826class FakeMatcher extends AsyncMatcher {
827 FakeMatcher(this.completer);
828
829 final Completer<void> completer;
830
831 @override
832 Future<String?> matchAsync(dynamic object) {
833 return completer.future.then<String?>((void value) {
834 return object?.toString();
835 });
836 }
837
838 @override
839 Description describe(Description description) => description.add('--fake--');
840}
841
842class _AlwaysAnimating extends StatefulWidget {
843 const _AlwaysAnimating({required this.onPaint});
844
845 final VoidCallback onPaint;
846
847 @override
848 State<StatefulWidget> createState() => _AlwaysAnimatingState();
849}
850
851class _AlwaysAnimatingState extends State<_AlwaysAnimating> with SingleTickerProviderStateMixin {
852 late AnimationController _controller;
853
854 @override
855 void initState() {
856 super.initState();
857 _controller = AnimationController(duration: const Duration(milliseconds: 100), vsync: this);
858 _controller.repeat();
859 }
860
861 @override
862 void dispose() {
863 _controller.dispose();
864 super.dispose();
865 }
866
867 @override
868 Widget build(BuildContext context) {
869 return AnimatedBuilder(
870 animation: _controller.view,
871 builder: (BuildContext context, Widget? child) {
872 return CustomPaint(painter: _AlwaysRepaint(widget.onPaint));
873 },
874 );
875 }
876}
877
878class _AlwaysRepaint extends CustomPainter {
879 _AlwaysRepaint(this.onPaint);
880
881 final VoidCallback onPaint;
882
883 @override
884 bool shouldRepaint(CustomPainter oldDelegate) => true;
885
886 @override
887 void paint(Canvas canvas, Size size) {
888 onPaint();
889 }
890}
891