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