• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium 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';
6
7import 'package:flutter/cupertino.dart';
8import 'package:flutter/gestures.dart';
9import 'package:flutter/material.dart';
10import 'package:flutter/rendering.dart';
11import 'package:flutter/scheduler.dart';
12import 'package:flutter/widgets.dart';
13import 'package:meta/meta.dart';
14import 'package:test_api/test_api.dart' as test_package;
15
16import 'all_elements.dart';
17import 'binding.dart';
18import 'controller.dart';
19import 'finders.dart';
20import 'matchers.dart';
21import 'test_async_utils.dart';
22import 'test_compat.dart';
23import 'test_text_input.dart';
24
25/// Keep users from needing multiple imports to test semantics.
26export 'package:flutter/rendering.dart' show SemanticsHandle;
27
28/// Hide these imports so that they do not conflict with our own implementations in
29/// test_compat.dart. This handles setting up a declarer when one is not defined, which
30/// can happen when a test is executed via flutter_run.
31export 'package:test_api/test_api.dart' hide
32  test,
33  group,
34  setUpAll,
35  tearDownAll,
36  setUp,
37  tearDown,
38  expect, // we have our own wrapper below
39  TypeMatcher, // matcher's TypeMatcher conflicts with the one in the Flutter framework
40  isInstanceOf; // we have our own wrapper in matchers.dart
41
42/// Signature for callback to [testWidgets] and [benchmarkWidgets].
43typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);
44
45/// Runs the [callback] inside the Flutter test environment.
46///
47/// Use this function for testing custom [StatelessWidget]s and
48/// [StatefulWidget]s.
49///
50/// The callback can be asynchronous (using `async`/`await` or
51/// using explicit [Future]s).
52///
53/// There are two kinds of timeouts that can be specified. The `timeout`
54/// argument specifies the backstop timeout implemented by the `test` package.
55/// If set, it should be relatively large (minutes). It defaults to ten minutes
56/// for tests run by `flutter test`, and is unlimited for tests run by `flutter
57/// run`; specifically, it defaults to
58/// [TestWidgetsFlutterBinding.defaultTestTimeout].
59///
60/// The `initialTimeout` argument specifies the timeout implemented by the
61/// `flutter_test` package itself. If set, it may be relatively small (seconds),
62/// as it is automatically increased for some expensive operations, and can also
63/// be manually increased by calling
64/// [AutomatedTestWidgetsFlutterBinding.addTime]. The effective maximum value of
65/// this timeout (even after calling `addTime`) is the one specified by the
66/// `timeout` argument.
67///
68/// In general, timeouts are race conditions and cause flakes, so best practice
69/// is to avoid the use of timeouts in tests.
70///
71/// If the `semanticsEnabled` parameter is set to `true`,
72/// [WidgetTester.ensureSemantics] will have been called before the tester is
73/// passed to the `callback`, and that handle will automatically be disposed
74/// after the callback is finished. It defaults to true.
75///
76/// This function uses the [test] function in the test package to
77/// register the given callback as a test. The callback, when run,
78/// will be given a new instance of [WidgetTester]. The [find] object
79/// provides convenient widget [Finder]s for use with the
80/// [WidgetTester].
81///
82/// See also:
83///
84///  * [AutomatedTestWidgetsFlutterBinding.addTime] to learn more about
85///    timeout and how to manually increase timeouts.
86///
87/// ## Sample code
88///
89/// ```dart
90/// testWidgets('MyWidget', (WidgetTester tester) async {
91///   await tester.pumpWidget(new MyWidget());
92///   await tester.tap(find.text('Save'));
93///   expect(find.text('Success'), findsOneWidget);
94/// });
95/// ```
96@isTest
97void testWidgets(
98  String description,
99  WidgetTesterCallback callback, {
100  bool skip = false,
101  test_package.Timeout timeout,
102  Duration initialTimeout,
103  bool semanticsEnabled = true,
104}) {
105  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
106  final WidgetTester tester = WidgetTester._(binding);
107  test(
108    description,
109    () {
110      SemanticsHandle semanticsHandle;
111      if (semanticsEnabled == true) {
112        semanticsHandle = tester.ensureSemantics();
113      }
114      tester._recordNumberOfSemanticsHandles();
115      test_package.addTearDown(binding.postTest);
116      return binding.runTest(
117        () async {
118          debugResetSemanticsIdCounter();
119          await callback(tester);
120          semanticsHandle?.dispose();
121        },
122        tester._endOfTestVerifications,
123        description: description ?? '',
124        timeout: initialTimeout,
125      );
126    },
127    skip: skip,
128    timeout: timeout ?? binding.defaultTestTimeout,
129  );
130}
131
132/// Runs the [callback] inside the Flutter benchmark environment.
133///
134/// Use this function for benchmarking custom [StatelessWidget]s and
135/// [StatefulWidget]s when you want to be able to use features from
136/// [TestWidgetsFlutterBinding]. The callback, when run, will be given
137/// a new instance of [WidgetTester]. The [find] object provides
138/// convenient widget [Finder]s for use with the [WidgetTester].
139///
140/// The callback can be asynchronous (using `async`/`await` or using
141/// explicit [Future]s). If it is, then [benchmarkWidgets] will return
142/// a [Future] that completes when the callback's does. Otherwise, it
143/// will return a Future that is always complete.
144///
145/// If the callback is asynchronous, make sure you `await` the call
146/// to [benchmarkWidgets], otherwise it won't run!
147///
148/// If the `semanticsEnabled` parameter is set to `true`,
149/// [WidgetTester.ensureSemantics] will have been called before the tester is
150/// passed to the `callback`, and that handle will automatically be disposed
151/// after the callback is finished.
152///
153/// Benchmarks must not be run in checked mode, because the performance is not
154/// representative. To avoid this, this function will print a big message if it
155/// is run in checked mode. Unit tests of this method pass `mayRunWithAsserts`,
156/// but it should not be used for actual benchmarking.
157///
158/// Example:
159///
160///     main() async {
161///       assert(false); // fail in checked mode
162///       await benchmarkWidgets((WidgetTester tester) async {
163///         await tester.pumpWidget(new MyWidget());
164///         final Stopwatch timer = new Stopwatch()..start();
165///         for (int index = 0; index < 10000; index += 1) {
166///           await tester.tap(find.text('Tap me'));
167///           await tester.pump();
168///         }
169///         timer.stop();
170///         debugPrint('Time taken: ${timer.elapsedMilliseconds}ms');
171///       });
172///       exit(0);
173///     }
174Future<void> benchmarkWidgets(
175  WidgetTesterCallback callback, {
176  bool mayRunWithAsserts = false,
177  bool semanticsEnabled = false,
178}) {
179  assert(() {
180    if (mayRunWithAsserts)
181      return true;
182
183    print('┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓');
184    print('┇ ⚠ THIS BENCHMARK IS BEING RUN WITH ASSERTS ENABLED ⚠  ┇');
185    print('┡╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┦');
186    print('│                                                       │');
187    print('│  Numbers obtained from a benchmark while asserts are  │');
188    print('│  enabled will not accurately reflect the performance  │');
189    print('│  that will be experienced by end users using release  ╎');
190    print('│  builds. Benchmarks should be run using this command  ┆');
191    print('│  line:  flutter run --release benchmark.dart          ┊');
192    print('│                                                        ');
193    print('└─────────────────────────────────────────────────╌┄┈  ��');
194    return true;
195  }());
196  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
197  assert(binding is! AutomatedTestWidgetsFlutterBinding);
198  final WidgetTester tester = WidgetTester._(binding);
199  SemanticsHandle semanticsHandle;
200  if (semanticsEnabled == true) {
201    semanticsHandle = tester.ensureSemantics();
202  }
203  tester._recordNumberOfSemanticsHandles();
204  return binding.runTest(
205    () async {
206      await callback(tester);
207      semanticsHandle?.dispose();
208    },
209    tester._endOfTestVerifications,
210  ) ?? Future<void>.value();
211}
212
213/// Assert that `actual` matches `matcher`.
214///
215/// See [test_package.expect] for details. This is a variant of that function
216/// that additionally verifies that there are no asynchronous APIs
217/// that have not yet resolved.
218///
219/// See also:
220///
221///  * [expectLater] for use with asynchronous matchers.
222void expect(
223  dynamic actual,
224  dynamic matcher, {
225  String reason,
226  dynamic skip, // true or a String
227}) {
228  TestAsyncUtils.guardSync();
229  test_package.expect(actual, matcher, reason: reason, skip: skip);
230}
231
232/// Assert that `actual` matches `matcher`.
233///
234/// See [test_package.expect] for details. This variant will _not_ check that
235/// there are no outstanding asynchronous API requests. As such, it can be
236/// called from, e.g., callbacks that are run during build or layout, or in the
237/// completion handlers of futures that execute in response to user input.
238///
239/// Generally, it is better to use [expect], which does include checks to ensure
240/// that asynchronous APIs are not being called.
241void expectSync(
242  dynamic actual,
243  dynamic matcher, {
244  String reason,
245}) {
246  test_package.expect(actual, matcher, reason: reason);
247}
248
249/// Just like [expect], but returns a [Future] that completes when the matcher
250/// has finished matching.
251///
252/// See [test_package.expectLater] for details.
253///
254/// If the matcher fails asynchronously, that failure is piped to the returned
255/// future where it can be handled by user code. If it is not handled by user
256/// code, the test will fail.
257Future<void> expectLater(
258  dynamic actual,
259  dynamic matcher, {
260  String reason,
261  dynamic skip, // true or a String
262}) {
263  // We can't wrap the delegate in a guard, or we'll hit async barriers in
264  // [TestWidgetsFlutterBinding] while we're waiting for the matcher to complete
265  TestAsyncUtils.guardSync();
266  return test_package.expectLater(actual, matcher, reason: reason, skip: skip)
267           .then<void>((dynamic value) => null);
268}
269
270/// Class that programmatically interacts with widgets and the test environment.
271///
272/// For convenience, instances of this class (such as the one provided by
273/// `testWidget`) can be used as the `vsync` for `AnimationController` objects.
274class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
275  WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
276    if (binding is LiveTestWidgetsFlutterBinding)
277      binding.deviceEventDispatcher = this;
278  }
279
280  /// The binding instance used by the testing framework.
281  @override
282  TestWidgetsFlutterBinding get binding => super.binding;
283
284  /// Renders the UI from the given [widget].
285  ///
286  /// Calls [runApp] with the given widget, then triggers a frame and flushes
287  /// microtasks, by calling [pump] with the same `duration` (if any). The
288  /// supplied [EnginePhase] is the final phase reached during the pump pass; if
289  /// not supplied, the whole pass is executed.
290  ///
291  /// Subsequent calls to this is different from [pump] in that it forces a full
292  /// rebuild of the tree, even if [widget] is the same as the previous call.
293  /// [pump] will only rebuild the widgets that have changed.
294  ///
295  /// This method should not be used as the first parameter to an [expect] or
296  /// [expectLater] call to test that a widget throws an exception. Instead, use
297  /// [TestWidgetsFlutterBinding.takeException].
298  ///
299  /// {@tool sample}
300  /// ```dart
301  /// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async {
302  ///   await tester.pumpWidget(MyWidget(-1));
303  ///   expect(tester.takeException(), isAssertionError); // or isNull, as appropriate.
304  /// });
305  /// ```
306  /// {@end-tool}
307  ///
308  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
309  /// this method works when the test is run with `flutter run`.
310  Future<void> pumpWidget(
311    Widget widget, [
312    Duration duration,
313    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
314  ]) {
315    return TestAsyncUtils.guard<void>(() {
316      binding.attachRootWidget(widget);
317      binding.scheduleFrame();
318      return binding.pump(duration, phase);
319    });
320  }
321
322  /// Triggers a frame after `duration` amount of time.
323  ///
324  /// This makes the framework act as if the application had janked (missed
325  /// frames) for `duration` amount of time, and then received a v-sync signal
326  /// to paint the application.
327  ///
328  /// This is a convenience function that just calls
329  /// [TestWidgetsFlutterBinding.pump].
330  ///
331  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
332  /// this method works when the test is run with `flutter run`.
333  @override
334  Future<void> pump([
335    Duration duration,
336    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
337  ]) {
338    return TestAsyncUtils.guard<void>(() => binding.pump(duration, phase));
339  }
340
341  /// Triggers a frame after `duration` amount of time, return as soon as the frame is drawn.
342  ///
343  /// This enables driving an artificially high CPU load by rendering frames in
344  /// a tight loop. It must be used with the frame policy set to
345  /// [LiveTestWidgetsFlutterBindingFramePolicy.benchmark].
346  ///
347  /// Similarly to [pump], this doesn't actually wait for `duration`, just
348  /// advances the clock.
349  Future<void> pumpBenchmark(Duration duration) async {
350    assert(() {
351      final TestWidgetsFlutterBinding widgetsBinding = binding;
352      return widgetsBinding is LiveTestWidgetsFlutterBinding &&
353              widgetsBinding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
354    }());
355
356    dynamic caughtException;
357    void handleError(dynamic error, StackTrace stackTrace) => caughtException ??= error;
358
359    await Future<void>.microtask(() { binding.handleBeginFrame(duration); }).catchError(handleError);
360    await idle();
361    await Future<void>.microtask(() { binding.handleDrawFrame(); }).catchError(handleError);
362    await idle();
363
364    if (caughtException != null) {
365      throw caughtException;
366    }
367  }
368
369  /// Repeatedly calls [pump] with the given `duration` until there are no
370  /// longer any frames scheduled. This will call [pump] at least once, even if
371  /// no frames are scheduled when the function is called, to flush any pending
372  /// microtasks which may themselves schedule a frame.
373  ///
374  /// This essentially waits for all animations to have completed.
375  ///
376  /// If it takes longer that the given `timeout` to settle, then the test will
377  /// fail (this method will throw an exception). In particular, this means that
378  /// if there is an infinite animation in progress (for example, if there is an
379  /// indeterminate progress indicator spinning), this method will throw.
380  ///
381  /// The default timeout is ten minutes, which is longer than most reasonable
382  /// finite animations would last.
383  ///
384  /// If the function returns, it returns the number of pumps that it performed.
385  ///
386  /// In general, it is better practice to figure out exactly why each frame is
387  /// needed, and then to [pump] exactly as many frames as necessary. This will
388  /// help catch regressions where, for instance, an animation is being started
389  /// one frame later than it should.
390  ///
391  /// Alternatively, one can check that the return value from this function
392  /// matches the expected number of pumps.
393  Future<int> pumpAndSettle([
394    Duration duration = const Duration(milliseconds: 100),
395    EnginePhase phase = EnginePhase.sendSemanticsUpdate,
396    Duration timeout = const Duration(minutes: 10),
397  ]) {
398    assert(duration != null);
399    assert(duration > Duration.zero);
400    assert(timeout != null);
401    assert(timeout > Duration.zero);
402    assert(() {
403      final WidgetsBinding binding = this.binding;
404      if (binding is LiveTestWidgetsFlutterBinding &&
405          binding.framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
406        throw 'When using LiveTestWidgetsFlutterBindingFramePolicy.benchmark, '
407              'hasScheduledFrame is never set to true. This means that pumpAndSettle() '
408              'cannot be used, because it has no way to know if the application has '
409              'stopped registering new frames.';
410      }
411      return true;
412    }());
413    int count = 0;
414    return TestAsyncUtils.guard<void>(() async {
415      final DateTime endTime = binding.clock.fromNowBy(timeout);
416      do {
417        if (binding.clock.now().isAfter(endTime))
418          throw FlutterError('pumpAndSettle timed out');
419        await binding.pump(duration, phase);
420        count += 1;
421      } while (binding.hasScheduledFrame);
422    }).then<int>((_) => count);
423  }
424
425  /// Runs a [callback] that performs real asynchronous work.
426  ///
427  /// This is intended for callers who need to call asynchronous methods where
428  /// the methods spawn isolates or OS threads and thus cannot be executed
429  /// synchronously by calling [pump].
430  ///
431  /// If callers were to run these types of asynchronous tasks directly in
432  /// their test methods, they run the possibility of encountering deadlocks.
433  ///
434  /// If [callback] completes successfully, this will return the future
435  /// returned by [callback].
436  ///
437  /// If [callback] completes with an error, the error will be caught by the
438  /// Flutter framework and made available via [takeException], and this method
439  /// will return a future that completes will `null`.
440  ///
441  /// Re-entrant calls to this method are not allowed; callers of this method
442  /// are required to wait for the returned future to complete before calling
443  /// this method again. Attempts to do otherwise will result in a
444  /// [TestFailure] error being thrown.
445  Future<T> runAsync<T>(
446    Future<T> callback(), {
447    Duration additionalTime = const Duration(milliseconds: 1000),
448  }) => binding.runAsync<T>(callback, additionalTime: additionalTime);
449
450  /// Whether there are any any transient callbacks scheduled.
451  ///
452  /// This essentially checks whether all animations have completed.
453  ///
454  /// See also:
455  ///
456  ///  * [pumpAndSettle], which essentially calls [pump] until there are no
457  ///    scheduled frames.
458  ///  * [SchedulerBinding.transientCallbackCount], which is the value on which
459  ///    this is based.
460  ///  * [SchedulerBinding.hasScheduledFrame], which is true whenever a frame is
461  ///    pending. [SchedulerBinding.hasScheduledFrame] is made true when a
462  ///    widget calls [State.setState], even if there are no transient callbacks
463  ///    scheduled. This is what [pumpAndSettle] uses.
464  bool get hasRunningAnimations => binding.transientCallbackCount > 0;
465
466  @override
467  HitTestResult hitTestOnBinding(Offset location) {
468    location = binding.localToGlobal(location);
469    return super.hitTestOnBinding(location);
470  }
471
472  @override
473  Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
474    return TestAsyncUtils.guard<void>(() async {
475      binding.dispatchEvent(event, result, source: TestBindingEventSource.test);
476    });
477  }
478
479  /// Handler for device events caught by the binding in live test mode.
480  @override
481  void dispatchEvent(PointerEvent event, HitTestResult result) {
482    if (event is PointerDownEvent) {
483      final RenderObject innerTarget = result.path.firstWhere(
484        (HitTestEntry candidate) => candidate.target is RenderObject,
485      ).target;
486      final Element innerTargetElement = collectAllElementsFrom(
487        binding.renderViewElement,
488        skipOffstage: true,
489      ).lastWhere(
490        (Element element) => element.renderObject == innerTarget,
491        orElse: () => null,
492      );
493      if (innerTargetElement == null) {
494        debugPrint('No widgets found at ${binding.globalToLocal(event.position)}.');
495        return;
496      }
497      final List<Element> candidates = <Element>[];
498      innerTargetElement.visitAncestorElements((Element element) {
499        candidates.add(element);
500        return true;
501      });
502      assert(candidates.isNotEmpty);
503      String descendantText;
504      int numberOfWithTexts = 0;
505      int numberOfTypes = 0;
506      int totalNumber = 0;
507      debugPrint('Some possible finders for the widgets at ${binding.globalToLocal(event.position)}:');
508      for (Element element in candidates) {
509        if (totalNumber > 13) // an arbitrary number of finders that feels useful without being overwhelming
510          break;
511        totalNumber += 1; // optimistically assume we'll be able to describe it
512
513        if (element.widget is Tooltip) {
514          final Tooltip widget = element.widget;
515          final Iterable<Element> matches = find.byTooltip(widget.message).evaluate();
516          if (matches.length == 1) {
517            debugPrint('  find.byTooltip(\'${widget.message}\')');
518            continue;
519          }
520        }
521
522        if (element.widget is Text) {
523          assert(descendantText == null);
524          final Text widget = element.widget;
525          final Iterable<Element> matches = find.text(widget.data).evaluate();
526          descendantText = widget.data;
527          if (matches.length == 1) {
528            debugPrint('  find.text(\'${widget.data}\')');
529            continue;
530          }
531        }
532
533        if (element.widget.key is ValueKey<dynamic>) {
534          final ValueKey<dynamic> key = element.widget.key;
535          String keyLabel;
536          if (key is ValueKey<int> ||
537              key is ValueKey<double> ||
538              key is ValueKey<bool>) {
539            keyLabel = 'const ${element.widget.key.runtimeType}(${key.value})';
540          } else if (key is ValueKey<String>) {
541            keyLabel = 'const Key(\'${key.value}\')';
542          }
543          if (keyLabel != null) {
544            final Iterable<Element> matches = find.byKey(key).evaluate();
545            if (matches.length == 1) {
546              debugPrint('  find.byKey($keyLabel)');
547              continue;
548            }
549          }
550        }
551
552        if (!_isPrivate(element.widget.runtimeType)) {
553          if (numberOfTypes < 5) {
554            final Iterable<Element> matches = find.byType(element.widget.runtimeType).evaluate();
555            if (matches.length == 1) {
556              debugPrint('  find.byType(${element.widget.runtimeType})');
557              numberOfTypes += 1;
558              continue;
559            }
560          }
561
562          if (descendantText != null && numberOfWithTexts < 5) {
563            final Iterable<Element> matches = find.widgetWithText(element.widget.runtimeType, descendantText).evaluate();
564            if (matches.length == 1) {
565              debugPrint('  find.widgetWithText(${element.widget.runtimeType}, \'$descendantText\')');
566              numberOfWithTexts += 1;
567              continue;
568            }
569          }
570        }
571
572        if (!_isPrivate(element.runtimeType)) {
573          final Iterable<Element> matches = find.byElementType(element.runtimeType).evaluate();
574          if (matches.length == 1) {
575            debugPrint('  find.byElementType(${element.runtimeType})');
576            continue;
577          }
578        }
579
580        totalNumber -= 1; // if we got here, we didn't actually find something to say about it
581      }
582      if (totalNumber == 0)
583        debugPrint('  <could not come up with any unique finders>');
584    }
585  }
586
587  bool _isPrivate(Type type) {
588    // used above so that we don't suggest matchers for private types
589    return '_'.matchAsPrefix(type.toString()) != null;
590  }
591
592  /// Returns the exception most recently caught by the Flutter framework.
593  ///
594  /// See [TestWidgetsFlutterBinding.takeException] for details.
595  dynamic takeException() {
596    return binding.takeException();
597  }
598
599  /// Acts as if the application went idle.
600  ///
601  /// Runs all remaining microtasks, including those scheduled as a result of
602  /// running them, until there are no more microtasks scheduled.
603  ///
604  /// Does not run timers. May result in an infinite loop or run out of memory
605  /// if microtasks continue to recursively schedule new microtasks.
606  Future<void> idle() {
607    return TestAsyncUtils.guard<void>(() => binding.idle());
608  }
609
610  Set<Ticker> _tickers;
611
612  @override
613  Ticker createTicker(TickerCallback onTick) {
614    _tickers ??= <_TestTicker>{};
615    final _TestTicker result = _TestTicker(onTick, _removeTicker);
616    _tickers.add(result);
617    return result;
618  }
619
620  void _removeTicker(_TestTicker ticker) {
621    assert(_tickers != null);
622    assert(_tickers.contains(ticker));
623    _tickers.remove(ticker);
624  }
625
626  /// Throws an exception if any tickers created by the [WidgetTester] are still
627  /// active when the method is called.
628  ///
629  /// An argument can be specified to provide a string that will be used in the
630  /// error message. It should be an adverbial phrase describing the current
631  /// situation, such as "at the end of the test".
632  void verifyTickersWereDisposed([ String when = 'when none should have been' ]) {
633    assert(when != null);
634    if (_tickers != null) {
635      for (Ticker ticker in _tickers) {
636        if (ticker.isActive) {
637          throw FlutterError(
638            'A Ticker was active $when.\n'
639            'All Tickers must be disposed. Tickers used by AnimationControllers '
640            'should be disposed by calling dispose() on the AnimationController itself. '
641            'Otherwise, the ticker will leak.\n'
642            'The offending ticker was: ${ticker.toString(debugIncludeStack: true)}'
643          );
644        }
645      }
646    }
647  }
648
649  void _endOfTestVerifications() {
650    verifyTickersWereDisposed('at the end of the test');
651    _verifySemanticsHandlesWereDisposed();
652  }
653
654  void _verifySemanticsHandlesWereDisposed() {
655    assert(_lastRecordedSemanticsHandles != null);
656    if (binding.pipelineOwner.debugOutstandingSemanticsHandles > _lastRecordedSemanticsHandles) {
657      throw FlutterError(
658        'A SemanticsHandle was active at the end of the test.\n'
659        'All SemanticsHandle instances must be disposed by calling dispose() on '
660        'the SemanticsHandle. If your test uses SemanticsTester, it is '
661        'sufficient to call dispose() on SemanticsTester. Otherwise, the '
662        'existing handle will leak into another test and alter its behavior.'
663      );
664    }
665    _lastRecordedSemanticsHandles = null;
666  }
667
668  int _lastRecordedSemanticsHandles;
669
670  void _recordNumberOfSemanticsHandles() {
671    _lastRecordedSemanticsHandles = binding.pipelineOwner.debugOutstandingSemanticsHandles;
672  }
673
674  /// Returns the TestTextInput singleton.
675  ///
676  /// Typical app tests will not need to use this value. To add text to widgets
677  /// like [TextField] or [TextFormField], call [enterText].
678  TestTextInput get testTextInput => binding.testTextInput;
679
680  /// Give the text input widget specified by [finder] the focus, as if the
681  /// onscreen keyboard had appeared.
682  ///
683  /// Implies a call to [pump].
684  ///
685  /// The widget specified by [finder] must be an [EditableText] or have
686  /// an [EditableText] descendant. For example `find.byType(TextField)`
687  /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
688  ///
689  /// Tests that just need to add text to widgets like [TextField]
690  /// or [TextFormField] only need to call [enterText].
691  Future<void> showKeyboard(Finder finder) async {
692    return TestAsyncUtils.guard<void>(() async {
693      final EditableTextState editable = state<EditableTextState>(
694        find.descendant(
695          of: finder,
696          matching: find.byType(EditableText),
697          matchRoot: true,
698        ),
699      );
700      binding.focusedEditable = editable;
701      await pump();
702    });
703  }
704
705  /// Give the text input widget specified by [finder] the focus and
706  /// enter [text] as if it been provided by the onscreen keyboard.
707  ///
708  /// The widget specified by [finder] must be an [EditableText] or have
709  /// an [EditableText] descendant. For example `find.byType(TextField)`
710  /// or `find.byType(TextFormField)`, or `find.byType(EditableText)`.
711  ///
712  /// To just give [finder] the focus without entering any text,
713  /// see [showKeyboard].
714  Future<void> enterText(Finder finder, String text) async {
715    return TestAsyncUtils.guard<void>(() async {
716      await showKeyboard(finder);
717      testTextInput.enterText(text);
718      await idle();
719    });
720  }
721
722  /// Makes an effort to dismiss the current page with a Material [Scaffold] or
723  /// a [CupertinoPageScaffold].
724  ///
725  /// Will throw an error if there is no back button in the page.
726  Future<void> pageBack() async {
727    return TestAsyncUtils.guard<void>(() async {
728      Finder backButton = find.byTooltip('Back');
729      if (backButton.evaluate().isEmpty) {
730        backButton = find.byType(CupertinoNavigationBarBackButton);
731      }
732
733      expectSync(backButton, findsOneWidget, reason: 'One back button expected on screen');
734
735      await tap(backButton);
736    });
737  }
738
739  /// Attempts to find the [SemanticsNode] of first result from `finder`.
740  ///
741  /// If the object identified by the finder doesn't own it's semantic node,
742  /// this will return the semantics data of the first ancestor with semantics.
743  /// The ancestor's semantic data will include the child's as well as
744  /// other nodes that have been merged together.
745  ///
746  /// Will throw a [StateError] if the finder returns more than one element or
747  /// if no semantics are found or are not enabled.
748  SemanticsNode getSemantics(Finder finder) {
749    if (binding.pipelineOwner.semanticsOwner == null)
750      throw StateError('Semantics are not enabled.');
751    final Iterable<Element> candidates = finder.evaluate();
752    if (candidates.isEmpty) {
753      throw StateError('Finder returned no matching elements.');
754    }
755    if (candidates.length > 1) {
756      throw StateError('Finder returned more than one element.');
757    }
758    final Element element = candidates.single;
759    RenderObject renderObject = element.findRenderObject();
760    SemanticsNode result = renderObject.debugSemantics;
761    while (renderObject != null && result == null) {
762      renderObject = renderObject?.parent;
763      result = renderObject?.debugSemantics;
764    }
765    if (result == null)
766      throw StateError('No Semantics data found.');
767    return result;
768  }
769
770  /// Enable semantics in a test by creating a [SemanticsHandle].
771  ///
772  /// The handle must be disposed at the end of the test.
773  SemanticsHandle ensureSemantics() {
774    return binding.pipelineOwner.ensureSemantics();
775  }
776
777  /// Given a widget `W` specified by [finder] and a [Scrollable] widget `S` in
778  /// its ancestry tree, this scrolls `S` so as to make `W` visible.
779  ///
780  /// Shorthand for `Scrollable.ensureVisible(tester.element(finder))`
781  Future<void> ensureVisible(Finder finder) => Scrollable.ensureVisible(element(finder));
782}
783
784typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
785
786class _TestTicker extends Ticker {
787  _TestTicker(TickerCallback onTick, this._onDispose) : super(onTick);
788
789  final _TickerDisposeCallback _onDispose;
790
791  @override
792  void dispose() {
793    if (_onDispose != null)
794      _onDispose(this);
795    super.dispose();
796  }
797}
798