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