1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:fake_async/fake_async.dart';
6/// @docImport 'package:flutter/material.dart';
7/// @docImport 'package:matcher/matcher.dart';
8/// @docImport 'package:test_api/hooks.dart';
9library;
10
11import 'package:flutter/cupertino.dart';
12import 'package:flutter/foundation.dart';
13import 'package:flutter/gestures.dart';
14import 'package:flutter/material.dart' show Tooltip;
15import 'package:flutter/rendering.dart';
16import 'package:flutter/scheduler.dart';
17import 'package:flutter/services.dart';
18import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
19import 'package:matcher/expect.dart' as matcher_expect;
20import 'package:meta/meta.dart';
21import 'package:test_api/scaffolding.dart' as test_package;
22
23import 'binding.dart';
24import 'controller.dart';
25import 'finders.dart';
26import 'matchers.dart';
27import 'restoration.dart';
28import 'test_async_utils.dart';
29import 'test_compat.dart';
30import 'test_pointer.dart';
31import 'test_text_input.dart';
32import 'tree_traversal.dart';
33
34// Keep users from needing multiple imports to test semantics.
35export 'package:flutter/rendering.dart' show SemanticsHandle;
36// We re-export the matcher package minus some features that we reimplement.
37//
38// - expect is reimplemented below, to catch incorrect async usage.
39//
40// - isInstanceOf is reimplemented in matchers.dart because we don't want to
41// mark it as deprecated (ours is just a method, not a class).
42//
43export 'package:matcher/expect.dart' hide expect, isInstanceOf;
44// We re-export the test package minus some features that we reimplement.
45//
46// Specifically:
47//
48// - test, group, setUpAll, tearDownAll, setUp, tearDown, and expect would
49// conflict with our own implementations in test_compat.dart. This handles
50// setting up a declarer when one is not defined, which can happen when a
51// test is executed via `flutter run`.
52//
53// The test_api package has a deprecation warning to discourage direct use but
54// that doesn't apply here.
55export 'package:test_api/hooks.dart' show TestFailure;
56export 'package:test_api/scaffolding.dart'
57 show
58 OnPlatform,
59 Retry,
60 Skip,
61 Tags,
62 TestOn,
63 Timeout,
64 addTearDown,
65 markTestSkipped,
66 printOnFailure,
67 pumpEventQueue,
68 registerException,
69 spawnHybridCode,
70 spawnHybridUri;
71
72/// Signature for callback to [testWidgets] and [benchmarkWidgets].
73typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);
74
75// Return the last element that satisfies `test`, or return null if not found.
76E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
77 late E result;
78 bool foundMatching = false;
79 for (final E element in list) {
80 if (test(element)) {
81 result = element;
82 foundMatching = true;
83 }
84 }
85 if (foundMatching) {
86 return result;
87 }
88 return null;
89}
90
91// Examples can assume:
92// typedef MyWidget = Placeholder;
93
94/// Runs the [callback] inside the Flutter test environment.
95///
96/// Use this function for testing custom [StatelessWidget]s and
97/// [StatefulWidget]s.
98///
99/// The callback can be asynchronous (using `async`/`await` or
100/// using explicit [Future]s).
101///
102/// The `timeout` argument specifies the backstop timeout implemented by the
103/// `test` package. If set, it should be relatively large (minutes). It defaults
104/// to ten minutes for tests run by `flutter test`, and is unlimited for tests
105/// run by `flutter run`; specifically, it defaults to
106/// [TestWidgetsFlutterBinding.defaultTestTimeout].
107///
108/// If the `semanticsEnabled` parameter is set to `true`,
109/// [WidgetTester.ensureSemantics] will have been called before the tester is
110/// passed to the `callback`, and that handle will automatically be disposed
111/// after the callback is finished. It defaults to true.
112///
113/// This function uses the [test] function in the test package to
114/// register the given callback as a test. The callback, when run,
115/// will be given a new instance of [WidgetTester]. The [find] object
116/// provides convenient widget [Finder]s for use with the
117/// [WidgetTester].
118///
119/// When the [variant] argument is set, [testWidgets] will run the test once for
120/// each value of the [TestVariant.values]. If [variant] is not set, the test
121/// will be run once using the base test environment.
122///
123/// If the [tags] are passed, they declare user-defined tags that are implemented by
124/// the `test` package.
125///
126/// The argument [experimentalLeakTesting] is experimental and is not recommended
127/// for use outside of the Flutter framework.
128/// When [experimentalLeakTesting] is set, it is used to leak track objects created
129/// during test execution.
130/// Otherwise [LeakTesting.settings] is used.
131/// Adjust [LeakTesting.settings] in `flutter_test_config.dart`
132/// (see https://flutter.dev/to/flutter-test-docs)
133/// for the entire package or folder, or in the test's main for a test file
134/// (don't use [setUp] or [setUpAll]).
135/// To turn off leak tracking just for one test, set [experimentalLeakTesting] to
136/// `LeakTrackingForTests.ignore()`.
137///
138/// ## Sample code
139///
140/// ```dart
141/// testWidgets('MyWidget', (WidgetTester tester) async {
142/// await tester.pumpWidget(const MyWidget());
143/// await tester.tap(find.text('Save'));
144/// expect(find.text('Success'), findsOneWidget);
145/// });
146/// ```
147@isTest
148void testWidgets(
149 String description,
150 WidgetTesterCallback callback, {
151 bool? skip,
152 test_package.Timeout? timeout,
153 bool semanticsEnabled = true,
154 TestVariant<Object?> variant = const DefaultTestVariant(),
155 dynamic tags,
156 int? retry,
157 LeakTesting? experimentalLeakTesting,
158}) {
159 assert(
160 variant.values.isNotEmpty,
161 'There must be at least one value to test in the testing variant.',
162 );
163 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
164 final WidgetTester tester = WidgetTester._(binding);
165 for (final dynamic value in variant.values) {
166 final String variationDescription = variant.describeValue(value);
167 // IDEs may make assumptions about the format of this suffix in order to
168 // support running tests directly from the editor (where they may have
169 // access to only the test name, provided by the analysis server).
170 // See https://github.com/flutter/flutter/issues/86659.
171 final String combinedDescription =
172 variationDescription.isNotEmpty
173 ? '$description (variant: $variationDescription)'
174 : description;
175 test(
176 combinedDescription,
177 () {
178 tester._testDescription = combinedDescription;
179 SemanticsHandle? semanticsHandle;
180 tester._recordNumberOfSemanticsHandles();
181 if (semanticsEnabled) {
182 semanticsHandle = tester.ensureSemantics();
183 }
184 test_package.addTearDown(binding.postTest);
185 return binding.runTest(
186 () async {
187 debugResetSemanticsIdCounter();
188 Object? memento;
189 try {
190 memento = await variant.setUp(value);
191 binding.reset(); // TODO(ianh): the binding should just do this itself in _runTest
192 maybeSetupLeakTrackingForTest(experimentalLeakTesting, combinedDescription);
193 await callback(tester);
194 } finally {
195 await variant.tearDown(value, memento);
196 maybeTearDownLeakTrackingForTest();
197 }
198 semanticsHandle?.dispose();
199 },
200 tester._endOfTestVerifications,
201 description: combinedDescription,
202 );
203 },
204 skip: skip,
205 timeout: timeout ?? binding.defaultTestTimeout,
206 tags: tags,
207 retry: retry,
208 );
209 }
210}
211
212/// An abstract base class for describing test environment variants.
213///
214/// These serve as elements of the `variants` argument to [testWidgets].
215///
216/// Use care when adding more testing variants: it multiplies the number of
217/// tests which run. This can drastically increase the time it takes to run all
218/// the tests.
219abstract class TestVariant<T> {
220 /// A const constructor so that subclasses can be const.
221 const TestVariant();
222
223 /// Returns an iterable of the variations that this test dimension represents.
224 ///
225 /// The variations returned should be unique so that the same variation isn't
226 /// needlessly run twice.
227 Iterable<T> get values;
228
229 /// Returns the string that will be used to both add to the test description, and
230 /// be printed when a test fails for this variation.
231 String describeValue(T value);
232
233 /// A function that will be called before each value is tested, with the
234 /// value that will be tested.
235 ///
236 /// This function should preserve any state needed to restore the testing
237 /// environment back to its base state when [tearDown] is called in the
238 /// `Object` that is returned. The returned object will then be passed to
239 /// [tearDown] as a `memento` when the test is complete.
240 Future<Object?> setUp(T value);
241
242 /// A function that is guaranteed to be called after a value is tested, even
243 /// if it throws an exception.
244 ///
245 /// Calling this function must return the testing environment back to the base
246 /// state it was in before [setUp] was called. The [memento] is the object
247 /// returned from [setUp] when it was called.
248 Future<void> tearDown(T value, covariant Object? memento);
249}
250
251/// The [TestVariant] that represents the "default" test that is run if no
252/// `variants` iterable is specified for [testWidgets].
253///
254/// This variant can be added into a list of other test variants to provide
255/// a "control" test where nothing is changed from the base test environment.
256class DefaultTestVariant extends TestVariant<void> {
257 /// A const constructor for a [DefaultTestVariant].
258 const DefaultTestVariant();
259
260 @override
261 Iterable<void> get values => const <void>[null];
262
263 @override
264 String describeValue(void value) => '';
265
266 @override
267 Future<void> setUp(void value) async {}
268
269 @override
270 Future<void> tearDown(void value, void memento) async {}
271}
272
273/// A [TestVariant] that runs tests with [debugDefaultTargetPlatformOverride]
274/// set to different values of [TargetPlatform].
275class TargetPlatformVariant extends TestVariant<TargetPlatform> {
276 /// Creates a [TargetPlatformVariant] that tests the given [values].
277 const TargetPlatformVariant(this.values);
278
279 /// Creates a [TargetPlatformVariant] that tests all values from
280 /// the [TargetPlatform] enum. If [excluding] is provided, will test all platforms
281 /// except those in [excluding].
282 TargetPlatformVariant.all({Set<TargetPlatform> excluding = const <TargetPlatform>{}})
283 : values = TargetPlatform.values.toSet()..removeAll(excluding);
284
285 /// Creates a [TargetPlatformVariant] that includes platforms that are
286 /// considered desktop platforms.
287 TargetPlatformVariant.desktop()
288 : values = <TargetPlatform>{TargetPlatform.linux, TargetPlatform.macOS, TargetPlatform.windows};
289
290 /// Creates a [TargetPlatformVariant] that includes platforms that are
291 /// considered mobile platforms.
292 TargetPlatformVariant.mobile()
293 : values = <TargetPlatform>{TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.fuchsia};
294
295 /// Creates a [TargetPlatformVariant] that tests only the given value of
296 /// [TargetPlatform].
297 TargetPlatformVariant.only(TargetPlatform platform) : values = <TargetPlatform>{platform};
298
299 @override
300 final Set<TargetPlatform> values;
301
302 @override
303 String describeValue(TargetPlatform value) => value.toString();
304
305 @override
306 Future<TargetPlatform?> setUp(TargetPlatform value) async {
307 final TargetPlatform? previousTargetPlatform = debugDefaultTargetPlatformOverride;
308 debugDefaultTargetPlatformOverride = value;
309 return previousTargetPlatform;
310 }
311
312 @override
313 Future<void> tearDown(TargetPlatform value, TargetPlatform? memento) async {
314 debugDefaultTargetPlatformOverride = memento;
315 }
316}
317
318/// A [TestVariant] that runs separate tests with each of the given values.
319///
320/// To use this variant, define it before the test, and then access
321/// [currentValue] inside the test.
322///
323/// The values are typically enums, but they don't have to be. The `toString`
324/// for the given value will be used to describe the variant. Values will have
325/// their type name stripped from their `toString` output, so that enum values
326/// will only print the value, not the type.
327///
328/// {@tool snippet}
329/// This example shows how to set up the test to access the [currentValue]. In
330/// this example, two tests will be run, one with `value1`, and one with
331/// `value2`. The test with `value2` will fail. The names of the tests will be:
332///
333/// - `Test handling of TestScenario (value1)`
334/// - `Test handling of TestScenario (value2)`
335///
336/// ```dart
337/// enum TestScenario {
338/// value1,
339/// value2,
340/// value3,
341/// }
342///
343/// final ValueVariant<TestScenario> variants = ValueVariant<TestScenario>(
344/// <TestScenario>{TestScenario.value1, TestScenario.value2},
345/// );
346/// void main() {
347/// testWidgets('Test handling of TestScenario', (WidgetTester tester) async {
348/// expect(variants.currentValue, equals(TestScenario.value1));
349/// }, variant: variants);
350/// }
351/// ```
352/// {@end-tool}
353class ValueVariant<T> extends TestVariant<T> {
354 /// Creates a [ValueVariant] that tests the given [values].
355 ValueVariant(this.values);
356
357 /// Returns the value currently under test.
358 T? get currentValue => _currentValue;
359 T? _currentValue;
360
361 @override
362 final Set<T> values;
363
364 @override
365 String describeValue(T value) => value.toString().replaceFirst('$T.', '');
366
367 @override
368 Future<T> setUp(T value) async => _currentValue = value;
369
370 @override
371 Future<void> tearDown(T value, T memento) async {}
372}
373
374/// The warning message to show when a benchmark is performed with assert on.
375const String kDebugWarning = '''
376┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
377┇ ⚠ THIS BENCHMARK IS BEING RUN IN DEBUG MODE ⚠ ┇
378┡╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┦
379│ │
380│ Numbers obtained from a benchmark while asserts are │
381│ enabled will not accurately reflect the performance │
382│ that will be experienced by end users using release ╎
383│ builds. Benchmarks should be run using this command ╎
384│ line: "flutter run --profile test.dart" or ┊
385│ or "flutter drive --profile -t test.dart". ┊
386│ ┊
387└─────────────────────────────────────────────────╌┄┈ 🐢
388''';
389
390/// Runs the [callback] inside the Flutter benchmark environment.
391///
392/// Use this function for benchmarking custom [StatelessWidget]s and
393/// [StatefulWidget]s when you want to be able to use features from
394/// [TestWidgetsFlutterBinding]. The callback, when run, will be given
395/// a new instance of [WidgetTester]. The [find] object provides
396/// convenient widget [Finder]s for use with the [WidgetTester].
397///
398/// The callback can be asynchronous (using `async`/`await` or using
399/// explicit [Future]s). If it is, then [benchmarkWidgets] will return
400/// a [Future] that completes when the callback's does. Otherwise, it
401/// will return a Future that is always complete.
402///
403/// If the callback is asynchronous, make sure you `await` the call
404/// to [benchmarkWidgets], otherwise it won't run!
405///
406/// If the `semanticsEnabled` parameter is set to `true`,
407/// [WidgetTester.ensureSemantics] will have been called before the tester is
408/// passed to the `callback`, and that handle will automatically be disposed
409/// after the callback is finished.
410///
411/// Benchmarks must not be run in debug mode, because the performance is not
412/// representative. To avoid this, this function will print a big message if it
413/// is run in debug mode. Unit tests of this method pass `mayRunWithAsserts`,
414/// but it should not be used for actual benchmarking.
415///
416/// Example:
417///
418/// main() async {
419/// assert(false); // fail in debug mode
420/// await benchmarkWidgets((WidgetTester tester) async {
421/// await tester.pumpWidget(MyWidget());
422/// final Stopwatch timer = Stopwatch()..start();
423/// for (int index = 0; index < 10000; index += 1) {
424/// await tester.tap(find.text('Tap me'));
425/// await tester.pump();
426/// }
427/// timer.stop();
428/// debugPrint('Time taken: ${timer.elapsedMilliseconds}ms');
429/// });
430/// exit(0);
431/// }
432Future<void> benchmarkWidgets(
433 WidgetTesterCallback callback, {
434 bool mayRunWithAsserts = false,
435 bool semanticsEnabled = false,
436}) {
437 assert(() {
438 if (mayRunWithAsserts) {
439 return true;
440 }
441 debugPrint(kDebugWarning);
442 return true;
443 }());
444 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
445 assert(binding is! AutomatedTestWidgetsFlutterBinding);
446 final WidgetTester tester = WidgetTester._(binding);
447 SemanticsHandle? semanticsHandle;
448 if (semanticsEnabled) {
449 semanticsHandle = tester.ensureSemantics();
450 }
451 tester._recordNumberOfSemanticsHandles();
452 return binding.runTest(() async {
453 await callback(tester);
454 semanticsHandle?.dispose();
455 }, tester._endOfTestVerifications);
456}
457
458/// Assert that `actual` matches `matcher`.
459///
460/// See [matcher_expect.expect] for details. This is a variant of that function
461/// that additionally verifies that there are no asynchronous APIs
462/// that have not yet resolved.
463///
464/// See also:
465///
466/// * [expectLater] for use with asynchronous matchers.
467void expect(
468 dynamic actual,
469 dynamic matcher, {
470 String? reason,
471 dynamic skip, // true or a String
472}) {
473 TestAsyncUtils.guardSync();
474 matcher_expect.expect(actual, matcher, reason: reason, skip: skip);
475}
476
477/// Assert that `actual` matches `matcher`.
478///
479/// See [matcher_expect.expect] for details. This variant will _not_ check that
480/// there are no outstanding asynchronous API requests. As such, it can be
481/// called from, e.g., callbacks that are run during build or layout, or in the
482/// completion handlers of futures that execute in response to user input.
483///
484/// Generally, it is better to use [expect], which does include checks to ensure
485/// that asynchronous APIs are not being called.
486void expectSync(dynamic actual, dynamic matcher, {String? reason}) {
487 matcher_expect.expect(actual, matcher, reason: reason);
488}
489
490/// Just like [expect], but returns a [Future] that completes when the matcher
491/// has finished matching.
492///
493/// See [matcher_expect.expectLater] for details.
494///
495/// If the matcher fails asynchronously, that failure is piped to the returned
496/// future where it can be handled by user code. If it is not handled by user
497/// code, the test will fail.
498Future<void> expectLater(
499 dynamic actual,
500 dynamic matcher, {
501 String? reason,
502 dynamic skip, // true or a String
503}) {
504 // We can't wrap the delegate in a guard, or we'll hit async barriers in
505 // [TestWidgetsFlutterBinding] while we're waiting for the matcher to complete
506 TestAsyncUtils.guardSync();
507 return matcher_expect
508 .expectLater(actual, matcher, reason: reason, skip: skip)
509 .then<void>((dynamic value) => null);
510}
511
512/// Class that programmatically interacts with widgets and the test environment.
513///
514/// Typically, a test uses [pumpWidget] to load a widget tree (in a manner very
515/// similar to how [runApp] works in a Flutter application). Then, methods such
516/// as [tap], [drag], [enterText], [fling], [longPress], etc, can be used to
517/// interact with the application. The application runs in a [FakeAsync] zone,
518/// which allows time to be stepped forward deliberately; this is done using the
519/// [pump] method.
520///
521/// The [expect] function can then be used to examine the state of the
522/// application, typically using [Finder]s such as those in the [find]
523/// namespace, and [Matcher]s such as [findsOneWidget].
524///
525/// ```dart
526/// testWidgets('MyWidget', (WidgetTester tester) async {
527/// await tester.pumpWidget(const MyWidget());
528/// await tester.tap(find.text('Save'));
529/// await tester.pump(); // allow the application to handle
530/// await tester.pump(const Duration(seconds: 1)); // skip past the animation
531/// expect(find.text('Success'), findsOneWidget);
532/// });
533/// ```
534///
535/// For convenience, instances of this class (such as the one provided by
536/// `testWidgets`) can be used as the `vsync` for `AnimationController` objects.
537///
538/// When the binding is [LiveTestWidgetsFlutterBinding], events from
539/// [LiveTestWidgetsFlutterBinding.deviceEventDispatcher] will be handled in
540/// [dispatchEvent]. Thus, using `flutter run` to run a test lets one tap on
541/// the screen to generate [Finder]s relevant to the test.
542class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
543 WidgetTester._(super.binding) {
544 if (binding is LiveTestWidgetsFlutterBinding) {
545 (binding as LiveTestWidgetsFlutterBinding).deviceEventDispatcher = this;
546 }
547 }
548
549 /// The description string of the test currently being run.
550 String get testDescription => _testDescription;
551 String _testDescription = '';
552
553 /// The binding instance used by the testing framework.
554 @override
555 TestWidgetsFlutterBinding get binding => super.binding as TestWidgetsFlutterBinding;
556
557 /// Renders the UI from the given [widget].
558 ///
559 /// Calls [runApp] with the given widget, then triggers a frame and flushes
560 /// microtasks, by calling [pump] with the same `duration` (if any). The
561 /// supplied [EnginePhase] is the final phase reached during the pump pass; if
562 /// not supplied, the whole pass is executed.
563 ///
564 /// Subsequent calls to this is different from [pump] in that it forces a full
565 /// rebuild of the tree, even if [widget] is the same as the previous call.
566 /// [pump] will only rebuild the widgets that have changed.
567 ///
568 /// This method should not be used as the first parameter to an [expect] or
569 /// [expectLater] call to test that a widget throws an exception. Instead, use
570 /// [TestWidgetsFlutterBinding.takeException].
571 ///
572 /// {@tool snippet}
573 /// ```dart
574 /// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async {
575 /// await tester.pumpWidget(const MyWidget());
576 /// expect(tester.takeException(), isAssertionError); // or isNull, as appropriate.
577 /// });
578 /// ```
579 /// {@end-tool}
580 ///
581 /// By default, the provided `widget` is rendered into [WidgetTester.view],
582 /// whose properties tests can modify to simulate different scenarios (e.g.
583 /// running on a large/small screen). Tests that want to control the
584 /// [FlutterView] into which content is rendered can set `wrapWithView` to
585 /// false and use [View] widgets in the provided `widget` tree to specify the
586 /// desired [FlutterView]s.
587 ///
588 /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
589 /// this method works when the test is run with `flutter run`.
590 Future<void> pumpWidget(
591 Widget widget, {
592 Duration? duration,
593 EnginePhase phase = EnginePhase.sendSemanticsUpdate,
594 bool wrapWithView = true,
595 }) {
596 return TestAsyncUtils.guard<void>(() {
597 binding.attachRootWidget(wrapWithView ? binding.wrapWithDefaultView(widget) : widget);
598 binding.scheduleFrame();
599 return binding.pump(duration, phase);
600 });
601 }
602
603 @override
604 Future<List<Duration>> handlePointerEventRecord(Iterable<PointerEventRecord> records) {
605 assert(records.isNotEmpty);
606 return TestAsyncUtils.guard<List<Duration>>(() async {
607 final List<Duration> handleTimeStampDiff = <Duration>[];
608 DateTime? startTime;
609 for (final PointerEventRecord record in records) {
610 final DateTime now = binding.clock.now();
611 startTime ??= now;
612 // So that the first event is promised to receive a zero timeDiff
613 final Duration timeDiff = record.timeDelay - now.difference(startTime);
614 if (timeDiff.isNegative) {
615 // Flush all past events
616 handleTimeStampDiff.add(-timeDiff);
617 for (final PointerEvent event in record.events) {
618 binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
619 }
620 } else {
621 await binding.pump();
622 await binding.delayed(timeDiff);
623 handleTimeStampDiff.add(binding.clock.now().difference(startTime) - record.timeDelay);
624 for (final PointerEvent event in record.events) {
625 binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
626 }
627 }
628 }
629 await binding.pump();
630 // This makes sure that a gesture is completed, with no more pointers
631 // active.
632 return handleTimeStampDiff;
633 });
634 }
635
636 /// Triggers a frame after `duration` amount of time.
637 ///
638 /// This makes the framework act as if the application had janked (missed
639 /// frames) for `duration` amount of time, and then received a "Vsync" signal
640 /// to paint the application.
641 ///
642 /// For a [FakeAsync] environment (typically in `flutter test`), this advances
643 /// time and timeout counting; for a live environment this delays `duration`
644 /// time.
645 ///
646 /// This is a convenience function that just calls
647 /// [TestWidgetsFlutterBinding.pump].
648 ///
649 /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
650 /// this method works when the test is run with `flutter run`.
651 @override
652 Future<void> pump([Duration? duration, EnginePhase phase = EnginePhase.sendSemanticsUpdate]) {
653 return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
654 }
655
656 /// Triggers a frame after `duration` amount of time, return as soon as the frame is drawn.
657 ///
658 /// This enables driving an artificially high CPU load by rendering frames in
659 /// a tight loop. It must be used with the frame policy set to
660 /// [LiveTestWidgetsFlutterBindingFramePolicy.benchmark].
661 ///
662 /// Similarly to [pump], this doesn't actually wait for `duration`, just
663 /// advances the clock.
664 Future<void> pumpBenchmark(Duration duration) async {
665 assert(() {
666 final TestWidgetsFlutterBinding widgetsBinding = binding;
667 return widgetsBinding is LiveTestWidgetsFlutterBinding &&
668 widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
669 }());
670
671 dynamic caughtException;
672 StackTrace? stackTrace;
673 void handleError(dynamic error, StackTrace trace) {
674 caughtException ??= error;
675 stackTrace ??= trace;
676 }
677
678 await Future<void>.microtask(() {
679 binding.handleBeginFrame(duration);
680 }).catchError(handleError);
681 await idle();
682 await Future<void>.microtask(() {
683 binding.handleDrawFrame();
684 }).catchError(handleError);
685 await idle();
686
687 if (caughtException != null) {
688 Error.throwWithStackTrace(caughtException as Object, stackTrace!);
689 }
690 }
691
692 @override
693 Future<int> pumpAndSettle([
694 Duration duration = const Duration(milliseconds: 100),
695 EnginePhase phase = EnginePhase.sendSemanticsUpdate,
696 Duration timeout = const Duration(minutes: 10),
697 ]) {
698 assert(duration > Duration.zero);
699 assert(timeout > Duration.zero);
700 assert(() {
701 final WidgetsBinding binding = this.binding;
702 if (binding is LiveTestWidgetsFlutterBinding &&
703 binding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
704 matcher_expect.fail(
705 'When using LiveTestWidgetsFlutterBindingFramePolicy.benchmark, '
706 'hasScheduledFrame is never set to true. This means that pumpAndSettle() '
707 'cannot be used, because it has no way to know if the application has '
708 'stopped registering new frames.',
709 );
710 }
711 return true;
712 }());
713 return TestAsyncUtils.guard<int>(() async {
714 final DateTime endTime = binding.clock.fromNowBy(timeout);
715 int count = 0;
716 do {
717 if (binding.clock.now().isAfter(endTime)) {
718 throw FlutterError('pumpAndSettle timed out');
719 }
720 await binding.pump(duration, phase);
721 count += 1;
722 } while (binding.hasScheduledFrame);
723 return count;
724 });
725 }
726
727 /// Repeatedly pump frames that render the `target` widget with a fixed time
728 /// `interval` as many as `maxDuration` allows.
729 ///
730 /// The `maxDuration` argument is required. The `interval` argument defaults to
731 /// 16.683 milliseconds (59.94 FPS).
732 Future<void> pumpFrames(
733 Widget target,
734 Duration maxDuration, [
735 Duration interval = const Duration(milliseconds: 16, microseconds: 683),
736 ]) {
737 // The interval following the last frame doesn't have to be within the fullDuration.
738 Duration elapsed = Duration.zero;
739 return TestAsyncUtils.guard<void>(() async {
740 binding.attachRootWidget(binding.wrapWithDefaultView(target));
741 binding.scheduleFrame();
742 while (elapsed < maxDuration) {
743 await binding.pump(interval);
744 elapsed += interval;
745 }
746 });
747 }
748
749 /// Simulates restoring the state of the widget tree after the application
750 /// is restarted.
751 ///
752 /// The method grabs the current serialized restoration data from the
753 /// [RestorationManager], takes down the widget tree to destroy all in-memory
754 /// state, and then restores the widget tree from the serialized restoration
755 /// data.
756 Future<void> restartAndRestore() async {
757 assert(
758 binding.restorationManager.debugRootBucketAccessed,
759 'The current widget tree did not inject the root bucket of the RestorationManager and '
760 'therefore no restoration data has been collected to restore from. Did you forget to wrap '
761 'your widget tree in a RootRestorationScope?',
762 );
763 return TestAsyncUtils.guard<void>(() async {
764 final RootWidget widget = binding.rootElement!.widget as RootWidget;
765 final TestRestorationData restorationData = binding.restorationManager.restorationData;
766 runApp(Container(key: UniqueKey()));
767 await pump();
768 binding.restorationManager.restoreFrom(restorationData);
769 binding.attachToBuildOwner(widget);
770 binding.scheduleFrame();
771 return binding.pump();
772 });
773 }
774
775 /// Retrieves the current restoration data from the [RestorationManager].
776 ///
777 /// The returned [TestRestorationData] describes the current state of the
778 /// widget tree under test and can be provided to [restoreFrom] to restore
779 /// the widget tree to the state described by this data.
780 Future<TestRestorationData> getRestorationData() async {
781 assert(
782 binding.restorationManager.debugRootBucketAccessed,
783 'The current widget tree did not inject the root bucket of the RestorationManager and '
784 'therefore no restoration data has been collected. Did you forget to wrap your widget tree '
785 'in a RootRestorationScope?',
786 );
787 return binding.restorationManager.restorationData;
788 }
789
790 /// Restores the widget tree under test to the state described by the
791 /// provided [TestRestorationData].
792 ///
793 /// The data provided to this method is usually obtained from
794 /// [getRestorationData].
795 Future<void> restoreFrom(TestRestorationData data) {
796 binding.restorationManager.restoreFrom(data);
797 return pump();
798 }
799
800 /// Runs a [callback] that performs real asynchronous work.
801 ///
802 /// This is intended for callers who need to call asynchronous methods where
803 /// the methods spawn isolates or OS threads and thus cannot be executed
804 /// synchronously by calling [pump].
805 ///
806 /// If callers were to run these types of asynchronous tasks directly in
807 /// their test methods, they run the possibility of encountering deadlocks.
808 ///
809 /// If [callback] completes successfully, this will return the future
810 /// returned by [callback].
811 ///
812 /// If [callback] completes with an error, the error will be caught by the
813 /// Flutter framework and made available via [takeException], and this method
814 /// will return a future that completes with `null`.
815 ///
816 /// Re-entrant calls to this method are not allowed; callers of this method
817 /// are required to wait for the returned future to complete before calling
818 /// this method again. Attempts to do otherwise will result in a
819 /// [TestFailure] error being thrown.
820 ///
821 /// If your widget test hangs and you are using [runAsync], chances are your
822 /// code depends on the result of a task that did not complete. Fake async
823 /// environment is unable to resolve a future that was created in [runAsync].
824 /// If you observe such behavior or flakiness, you have a number of options:
825 ///
826 /// * Consider restructuring your code so you do not need [runAsync]. This is
827 /// the optimal solution as widget tests are designed to run in fake async
828 /// environment.
829 ///
830 /// * Expose a [Future] in your application code that signals the readiness of
831 /// your widget tree, then await that future inside [callback].
832 Future<T?> runAsync<T>(
833 Future<T> Function() callback, {
834 @Deprecated(
835 'This is no longer supported and has no effect. '
836 'This feature was deprecated after v3.12.0-1.1.pre.',
837 )
838 Duration additionalTime = const Duration(milliseconds: 1000),
839 }) => binding.runAsync<T?>(callback);
840
841 /// Whether there are any transient callbacks scheduled.
842 ///
843 /// This essentially checks whether all animations have completed.
844 ///
845 /// See also:
846 ///
847 /// * [pumpAndSettle], which essentially calls [pump] until there are no
848 /// scheduled frames.
849 /// * [SchedulerBinding.transientCallbackCount], which is the value on which
850 /// this is based.
851 /// * [SchedulerBinding.hasScheduledFrame], which is true whenever a frame is
852 /// pending. [SchedulerBinding.hasScheduledFrame] is made true when a
853 /// widget calls [State.setState], even if there are no transient callbacks
854 /// scheduled. This is what [pumpAndSettle] uses.
855 bool get hasRunningAnimations => binding.transientCallbackCount > 0;
856
857 @override
858 HitTestResult hitTestOnBinding(Offset location, {int? viewId}) {
859 viewId ??= view.viewId;
860 final RenderView renderView = binding.renderViews.firstWhere(
861 (RenderView r) => r.flutterView.viewId == viewId,
862 );
863 location = binding.localToGlobal(location, renderView);
864 return super.hitTestOnBinding(location, viewId: viewId);
865 }
866
867 @override
868 Future<void> sendEventToBinding(PointerEvent event) {
869 return TestAsyncUtils.guard<void>(() async {
870 binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
871 });
872 }
873
874 /// Handler for device events caught by the binding in live test mode.
875 ///
876 /// [PointerDownEvent]s received here will only print a diagnostic message
877 /// showing possible [Finder]s that can be used to interact with the widget at
878 /// the location of [result].
879 @override
880 void dispatchEvent(PointerEvent event, HitTestResult result) {
881 if (event is PointerDownEvent) {
882 final RenderObject innerTarget =
883 result.path
884 .map((HitTestEntry candidate) => candidate.target)
885 .whereType<RenderObject>()
886 .first;
887 final Element? innerTargetElement =
888 binding.renderViews.contains(innerTarget)
889 ? null
890 : _lastWhereOrNull(
891 collectAllElementsFrom(binding.rootElement!, skipOffstage: true),
892 (Element element) => element.renderObject == innerTarget,
893 );
894 if (innerTargetElement == null) {
895 printToConsole('No widgets found at ${event.position}.');
896 return;
897 }
898 final List<Element> candidates = <Element>[];
899 innerTargetElement.visitAncestorElements((Element element) {
900 candidates.add(element);
901 return true;
902 });
903 assert(candidates.isNotEmpty);
904 String? descendantText;
905 int numberOfWithTexts = 0;
906 int numberOfTypes = 0;
907 int totalNumber = 0;
908 printToConsole('Some possible finders for the widgets at ${event.position}:');
909 for (final Element element in candidates) {
910 if (totalNumber > 13) {
911 break;
912 }
913 totalNumber += 1; // optimistically assume we'll be able to describe it
914
915 final Widget widget = element.widget;
916 if (widget is Tooltip) {
917 final String message = widget.message ?? widget.richMessage!.toPlainText();
918 final Iterable<Element> matches = find.byTooltip(message).evaluate();
919 if (matches.length == 1) {
920 printToConsole(" find.byTooltip('$message')");
921 continue;
922 }
923 }
924
925 if (widget is Text) {
926 assert(descendantText == null);
927 assert(widget.data != null || widget.textSpan != null);
928 final String text = widget.data ?? widget.textSpan!.toPlainText();
929 final Iterable<Element> matches = find.text(text).evaluate();
930 descendantText = widget.data;
931 if (matches.length == 1) {
932 printToConsole(" find.text('$text')");
933 continue;
934 }
935 }
936
937 final Key? key = widget.key;
938 if (key is ValueKey<dynamic>) {
939 final String? keyLabel = switch (key.value) {
940 int() || double() || bool() => 'const ${key.runtimeType}(${key.value})',
941 final String value => "const Key('$value')",
942 _ => null,
943 };
944 if (keyLabel != null) {
945 final Iterable<Element> matches = find.byKey(key).evaluate();
946 if (matches.length == 1) {
947 printToConsole(' find.byKey($keyLabel)');
948 continue;
949 }
950 }
951 }
952
953 if (!_isPrivate(widget.runtimeType)) {
954 if (numberOfTypes < 5) {
955 final Iterable<Element> matches = find.byType(widget.runtimeType).evaluate();
956 if (matches.length == 1) {
957 printToConsole(' find.byType(${widget.runtimeType})');
958 numberOfTypes += 1;
959 continue;
960 }
961 }
962
963 if (descendantText != null && numberOfWithTexts < 5) {
964 final Iterable<Element> matches =
965 find.widgetWithText(widget.runtimeType, descendantText).evaluate();
966 if (matches.length == 1) {
967 printToConsole(" find.widgetWithText(${widget.runtimeType}, '$descendantText')");
968 numberOfWithTexts += 1;
969 continue;
970 }
971 }
972 }
973
974 if (!_isPrivate(element.runtimeType)) {
975 final Iterable<Element> matches = find.byElementType(element.runtimeType).evaluate();
976 if (matches.length == 1) {
977 printToConsole(' find.byElementType(${element.runtimeType})');
978 continue;
979 }
980 }
981
982 totalNumber -= 1; // if we got here, we didn't actually find something to say about it
983 }
984 if (totalNumber == 0) {
985 printToConsole(' <could not come up with any unique finders>');
986 }
987 }
988 }
989
990 bool _isPrivate(Type type) {
991 // used above so that we don't suggest matchers for private types
992 return '_'.matchAsPrefix(type.toString()) != null;
993 }
994
995 /// Returns the exception most recently caught by the Flutter framework.
996 ///
997 /// See [TestWidgetsFlutterBinding.takeException] for details.
998 dynamic takeException() {
999 return binding.takeException();
1000 }
1001
1002 /// {@macro flutter.flutter_test.TakeAccessibilityAnnouncements}
1003 ///
1004 /// See [TestWidgetsFlutterBinding.takeAnnouncements] for details.
1005 List<CapturedAccessibilityAnnouncement> takeAnnouncements() {
1006 return binding.takeAnnouncements();
1007 }
1008
1009 /// Acts as if the application went idle.
1010 ///
1011 /// Runs all remaining microtasks, including those scheduled as a result of
1012 /// running them, until there are no more microtasks scheduled. Then, runs any
1013 /// previously scheduled timers with zero time, and completes the returned future.
1014 ///
1015 /// May result in an infinite loop or run out of memory if microtasks continue
1016 /// to recursively schedule new microtasks. Will not run any timers scheduled
1017 /// after this method was invoked, even if they are zero-time timers.
1018 Future<void> idle() {
1019 return TestAsyncUtils.guard<void>(() => binding.idle());
1020 }
1021
1022 Set<Ticker>? _tickers;
1023
1024 @override
1025 Ticker createTicker(TickerCallback onTick) {
1026 _tickers ??= <_TestTicker>{};
1027 final _TestTicker result = _TestTicker(onTick, _removeTicker);
1028 _tickers!.add(result);
1029 return result;
1030 }
1031
1032 void _removeTicker(_TestTicker ticker) {
1033 assert(_tickers != null);
1034 assert(_tickers!.contains(ticker));
1035 _tickers!.remove(ticker);
1036 }
1037
1038 /// Throws an exception if any tickers created by the [WidgetTester] are still
1039 /// active when the method is called.
1040 ///
1041 /// An argument can be specified to provide a string that will be used in the
1042 /// error message. It should be an adverbial phrase describing the current
1043 /// situation, such as "at the end of the test".
1044 void verifyTickersWereDisposed([String when = 'when none should have been']) {
1045 if (_tickers != null) {
1046 for (final Ticker ticker in _tickers!) {
1047 if (ticker.isActive) {
1048 throw FlutterError.fromParts(<DiagnosticsNode>[
1049 ErrorSummary('A Ticker was active $when.'),
1050 ErrorDescription('All Tickers must be disposed.'),
1051 ErrorHint(
1052 'Tickers used by AnimationControllers '
1053 'should be disposed by calling dispose() on the AnimationController itself. '
1054 'Otherwise, the ticker will leak.',
1055 ),
1056 ticker.describeForError('The offending ticker was'),
1057 ]);
1058 }
1059 }
1060 }
1061 }
1062
1063 void _endOfTestVerifications() {
1064 verifyTickersWereDisposed('at the end of the test');
1065 _verifySemanticsHandlesWereDisposed();
1066 }
1067
1068 void _verifySemanticsHandlesWereDisposed() {
1069 assert(_lastRecordedSemanticsHandles != null);
1070 // TODO(goderbauer): Fix known leak in web engine when running integration tests and remove this "correction", https://github.com/flutter/flutter/issues/121640.
1071 final int knownWebEngineLeakForLiveTestsCorrection =
1072 kIsWeb && binding is LiveTestWidgetsFlutterBinding ? 1 : 0;
1073
1074 if (_currentSemanticsHandles - knownWebEngineLeakForLiveTestsCorrection >
1075 _lastRecordedSemanticsHandles!) {
1076 throw FlutterError.fromParts(<DiagnosticsNode>[
1077 ErrorSummary('A SemanticsHandle was active at the end of the test.'),
1078 ErrorDescription(
1079 'All SemanticsHandle instances must be disposed by calling dispose() on '
1080 'the SemanticsHandle.',
1081 ),
1082 ]);
1083 }
1084 _lastRecordedSemanticsHandles = null;
1085 }
1086
1087 int? _lastRecordedSemanticsHandles;
1088
1089 // TODO(goderbauer): Only use binding.debugOutstandingSemanticsHandles when deprecated binding.pipelineOwner is removed.
1090 int get _currentSemanticsHandles =>
1091 binding.debugOutstandingSemanticsHandles +
1092 binding.pipelineOwner.debugOutstandingSemanticsHandles;
1093
1094 void _recordNumberOfSemanticsHandles() {
1095 _lastRecordedSemanticsHandles = _currentSemanticsHandles;
1096 }
1097
1098 /// Returns the TestTextInput singleton.
1099 ///
1100 /// Typical app tests will not need to use this value. To add text to widgets
1101 /// like [TextField] or [TextFormField], call [enterText].
1102 ///
1103 /// Some of the properties and methods on this value are only valid if the
1104 /// binding's [TestWidgetsFlutterBinding.registerTestTextInput] flag is set to
1105 /// true as a test is starting (meaning that the keyboard is to be simulated
1106 /// by the test framework). If those members are accessed when using a binding
1107 /// that sets this flag to false, they will throw.
1108 TestTextInput get testTextInput => binding.testTextInput;
1109
1110 /// Give the text input widget specified by [finder] the focus, as if the
1111 /// onscreen keyboard had appeared.
1112 ///
1113 /// Implies a call to [pump].
1114 ///
1115 /// The widget specified by [finder] must be an [EditableText] or have
1116 /// an [EditableText] descendant. For example `find.byType(TextField)`
1117 /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
1118 ///
1119 /// Tests that just need to add text to widgets like [TextField]
1120 /// or [TextFormField] only need to call [enterText].
1121 Future<void> showKeyboard(FinderBase<Element> finder) async {
1122 bool skipOffstage = true;
1123 if (finder is Finder) {
1124 skipOffstage = finder.skipOffstage;
1125 }
1126 return TestAsyncUtils.guard<void>(() async {
1127 final EditableTextState editable = state<EditableTextState>(
1128 find.descendant(
1129 of: finder,
1130 matching: find.byType(EditableText, skipOffstage: skipOffstage),
1131 matchRoot: true,
1132 ),
1133 );
1134 // Setting focusedEditable causes the binding to call requestKeyboard()
1135 // on the EditableTextState, which itself eventually calls TextInput.attach
1136 // to establish the connection.
1137 binding.focusedEditable = editable;
1138 await pump();
1139 });
1140 }
1141
1142 /// Give the text input widget specified by [finder] the focus and replace its
1143 /// content with [text], as if it had been provided by the onscreen keyboard.
1144 ///
1145 /// The widget specified by [finder] must be an [EditableText] or have
1146 /// an [EditableText] descendant. For example `find.byType(TextField)`
1147 /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
1148 ///
1149 /// When the returned future completes, the text input widget's text will be
1150 /// exactly `text`, and the caret will be placed at the end of `text`.
1151 ///
1152 /// To just give [finder] the focus without entering any text,
1153 /// see [showKeyboard].
1154 ///
1155 /// To enter text into other widgets (e.g. a custom widget that maintains a
1156 /// TextInputConnection the way that a [EditableText] does), first ensure that
1157 /// that widget has an open connection (e.g. by using [tap] to focus it),
1158 /// then call `testTextInput.enterText` directly (see
1159 /// [TestTextInput.enterText]).
1160 Future<void> enterText(FinderBase<Element> finder, String text) async {
1161 return TestAsyncUtils.guard<void>(() async {
1162 await showKeyboard(finder);
1163 testTextInput.enterText(text);
1164 await idle();
1165 });
1166 }
1167
1168 /// Makes an effort to dismiss the current page with a Material [Scaffold] or
1169 /// a [CupertinoPageScaffold].
1170 ///
1171 /// Will throw an error if there is no back button in the page.
1172 Future<void> pageBack() async {
1173 return TestAsyncUtils.guard<void>(() async {
1174 Finder backButton = find.byTooltip('Back');
1175 if (backButton.evaluate().isEmpty) {
1176 backButton = find.byType(CupertinoNavigationBarBackButton);
1177 }
1178
1179 expectSync(backButton, findsOneWidget, reason: 'One back button expected on screen');
1180
1181 await tap(backButton);
1182 });
1183 }
1184
1185 @override
1186 void printToConsole(String message) {
1187 binding.debugPrintOverride(message);
1188 }
1189}
1190
1191typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
1192
1193class _TestTicker extends Ticker {
1194 _TestTicker(super.onTick, this._onDispose);
1195
1196 final _TickerDisposeCallback _onDispose;
1197
1198 @override
1199 void dispose() {
1200 _onDispose(this);
1201 super.dispose();
1202 }
1203}
1204

Provided by KDAB

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