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

Provided by KDAB

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