binding.dart 69.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:ui' as ui;
7

8 9
import 'package:clock/clock.dart';
import 'package:fake_async/fake_async.dart';
10
import 'package:flutter/foundation.dart';
11
import 'package:flutter/gestures.dart';
12 13
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
14
import 'package:flutter/services.dart';
15
import 'package:flutter/widgets.dart';
16
import 'package:flutter_test/flutter_test.dart' show TestWindow;
17
// ignore: deprecated_member_use
18
import 'package:test_api/test_api.dart' as test_package;
19
import 'package:stack_trace/stack_trace.dart' as stack_trace;
20 21
import 'package:vector_math/vector_math_64.dart';

22
import '_binding_io.dart' if (dart.library.html) '_binding_web.dart' as binding;
23
import 'goldens.dart';
24
import 'platform.dart';
25
import 'restoration.dart';
26
import 'stack_manipulation.dart';
27
import 'test_async_utils.dart';
28
import 'test_exception_reporter.dart';
29
import 'test_text_input.dart';
30

31 32
/// Phases that can be reached by [WidgetTester.pumpWidget] and
/// [TestWidgetsFlutterBinding.pump].
33
///
34
/// See [WidgetsBinding.drawFrame] for a more detailed description of some of
35
/// these phases.
36
enum EnginePhase {
37
  /// The build phase in the widgets library. See [BuildOwner.buildScope].
38 39 40
  build,

  /// The layout phase in the rendering library. See [PipelineOwner.flushLayout].
41
  layout,
42 43 44

  /// The compositing bits update phase in the rendering library. See
  /// [PipelineOwner.flushCompositingBits].
45
  compositingBits,
46 47

  /// The paint phase in the rendering library. See [PipelineOwner.flushPaint].
48
  paint,
49 50 51 52

  /// The compositing phase in the rendering library. See
  /// [RenderView.compositeFrame]. This is the phase in which data is sent to
  /// the GPU. If semantics are not enabled, then this is the last phase.
53
  composite,
54 55 56

  /// The semantics building phase in the rendering library. See
  /// [PipelineOwner.flushSemantics].
57
  flushSemantics,
58 59

  /// The final phase in the rendering library, wherein semantics information is
Ian Hickson's avatar
Ian Hickson committed
60
  /// sent to the embedder. See [SemanticsOwner.sendSemanticsUpdate].
61
  sendSemanticsUpdate,
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
}

/// Parts of the system that can generate pointer events that reach the test
/// binding.
///
/// This is used to identify how to handle events in the
/// [LiveTestWidgetsFlutterBinding]. See
/// [TestWidgetsFlutterBinding.dispatchEvent].
enum TestBindingEventSource {
  /// The pointer event came from the test framework itself, e.g. from a
  /// [TestGesture] created by [WidgetTester.startGesture].
  test,

  /// The pointer event came from the system, presumably as a result of the user
  /// interactive directly with the device while the test was running.
  device,
78 79
}

80
const Size _kDefaultTestViewportSize = Size(800.0, 600.0);
81

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
/// A [BinaryMessenger] subclass that is used as the default binary messenger
/// under testing environment.
///
/// It tracks status of data sent across the Flutter platform barrier, which is
/// useful for testing frameworks to monitor and synchronize against the
/// platform messages.
class TestDefaultBinaryMessenger extends BinaryMessenger {
  /// Creates a [TestDefaultBinaryMessenger] instance.
  ///
  /// The [delegate] instance must not be null.
  TestDefaultBinaryMessenger(this.delegate): assert(delegate != null);

  /// The delegate [BinaryMessenger].
  final BinaryMessenger delegate;

97
  final List<Future<ByteData?>> _pendingMessages = <Future<ByteData?>>[];
98 99 100 101 102

  /// The number of incomplete/pending calls sent to the platform channels.
  int get pendingMessageCount => _pendingMessages.length;

  @override
103 104
  Future<ByteData?>? send(String channel, ByteData? message) {
    final Future<ByteData?>? resultFuture = delegate.send(channel, message);
105 106
    if (resultFuture != null) {
      _pendingMessages.add(resultFuture);
107 108 109
      resultFuture
        .catchError((Object error) { /* errors are the responsibility of the caller */ })
        .whenComplete(() => _pendingMessages.remove(resultFuture));
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
    }
    return resultFuture;
  }

  /// Returns a Future that completes after all the platform calls are finished.
  ///
  /// If a new platform message is sent after this method is called, this new
  /// message is not tracked. Use with [pendingMessageCount] to guarantee no
  /// pending message calls.
  Future<void> get platformMessagesFinished {
    return Future.wait<void>(_pendingMessages);
  }

  @override
  Future<void> handlePlatformMessage(
      String channel,
126 127
      ByteData? data,
      ui.PlatformMessageResponseCallback? callback,
128 129 130 131 132
  ) {
    return delegate.handlePlatformMessage(channel, data, callback);
  }

  @override
133
  void setMessageHandler(String channel, MessageHandler? handler) {
134 135 136
    delegate.setMessageHandler(channel, handler);
  }

137
  @override
138
  bool checkMessageHandler(String channel, MessageHandler? handler) {
139 140 141
    return delegate.checkMessageHandler(channel, handler);
  }

142
  @override
143
  void setMockMessageHandler(String channel, MessageHandler? handler) {
144 145
    delegate.setMockMessageHandler(channel, handler);
  }
146 147

  @override
148
  bool checkMockMessageHandler(String channel, MessageHandler? handler) {
149 150
    return delegate.checkMockMessageHandler(channel, handler);
  }
151 152
}

153 154 155 156 157 158 159
/// Base class for bindings used by widgets library tests.
///
/// The [ensureInitialized] method creates (if necessary) and returns
/// an instance of the appropriate subclass.
///
/// When using these bindings, certain features are disabled. For
/// example, [timeDilation] is reset to 1.0 on initialization.
160 161 162 163 164 165 166
///
/// In non-browser tests, the binding overrides `HttpClient` creation with a
/// fake client that always returns a status code of 400. This is to prevent
/// tests from making network calls, which could introduce flakiness. A test
/// that actually needs to make a network call should provide its own
/// `HttpClient` to the code making the call, so that it can appropriately mock
/// or fake responses.
167
abstract class TestWidgetsFlutterBinding extends BindingBase
168 169
  with SchedulerBinding,
       ServicesBinding,
170
       GestureBinding,
171
       SemanticsBinding,
172
       RendererBinding,
173
       PaintingBinding,
174
       WidgetsBinding {
175

176 177 178 179
  /// Constructor for [TestWidgetsFlutterBinding].
  ///
  /// This constructor overrides the [debugPrint] global hook to point to
  /// [debugPrintOverride], which can be overridden by subclasses.
180
  TestWidgetsFlutterBinding() : _window = TestWindow(window: ui.window) {
181
    debugPrint = debugPrintOverride;
182
    debugDisableShadows = disableShadows;
183 184
  }

185 186 187 188
  @override
  TestWindow get window => _window;
  final TestWindow _window;

189
  @override
190 191 192 193 194
  TestRestorationManager get restorationManager {
    _restorationManager ??= createRestorationManager();
    return _restorationManager!;
  }
  TestRestorationManager? _restorationManager;
195 196 197

  /// Called by the test framework at the beginning of a widget test to
  /// prepare the binding for the next test.
198 199 200
  ///
  /// If [registerTestTextInput] returns true when this method is called,
  /// the [testTextInput] is configured to simulate the keyboard.
201
  void reset() {
202
    _restorationManager = null;
203
    resetGestureBinding();
204 205 206
    testTextInput.reset();
    if (registerTestTextInput)
      _testTextInput.register();
207 208 209 210 211 212 213
  }

  @override
  TestRestorationManager createRestorationManager() {
    return TestRestorationManager();
  }

214 215 216 217 218
  /// The value to set [debugPrint] to while tests are running.
  ///
  /// This can be used to redirect console output from the framework, or to
  /// change the behavior of [debugPrint]. For example,
  /// [AutomatedTestWidgetsFlutterBinding] uses it to make [debugPrint]
219
  /// synchronous, disabling its normal throttling behavior.
220 221 222 223 224
  ///
  /// It is also used by some other parts of the test framework (e.g.
  /// [WidgetTester.printToConsole]) to ensure that messages from the
  /// test framework are displayed to the developer rather than logged
  /// by whatever code is overriding [debugPrint].
225 226
  DebugPrintCallback get debugPrintOverride => debugPrint;

227 228 229 230 231 232 233 234 235
  /// The value to set [debugDisableShadows] to while tests are running.
  ///
  /// This can be used to reduce the likelihood of golden file tests being
  /// flaky, because shadow rendering is not always deterministic. The
  /// [AutomatedTestWidgetsFlutterBinding] sets this to true, so that all tests
  /// always run with shadows disabled.
  @protected
  bool get disableShadows => false;

236
  /// Determines whether the Dart [HttpClient] class should be overridden to
237 238 239
  /// always return a failure response.
  ///
  /// By default, this value is true, so that unit tests will not become flaky
240 241 242
  /// due to intermittent network errors. The value may be overridden by a
  /// binding intended for use in integration tests that do end to end
  /// application testing, including working with real network responses.
243 244 245
  @protected
  bool get overrideHttpClient => true;

246 247
  /// Determines whether the binding automatically registers [testTextInput] as
  /// a fake keyboard implementation.
248 249 250 251 252 253 254
  ///
  /// Unit tests make use of this to mock out text input communication for
  /// widgets. An integration test would set this to false, to test real IME
  /// or keyboard input.
  ///
  /// [TestTextInput.isRegistered] reports whether the text input mock is
  /// registered or not.
255 256 257 258 259 260 261 262 263 264 265 266 267
  ///
  /// Some of the properties and methods on [testTextInput] are only valid if
  /// [registerTestTextInput] returns true when a test starts. If those
  /// members are accessed when using a binding that sets this flag to false,
  /// they will throw.
  ///
  /// If this property returns true when a test ends, the [testTextInput] is
  /// unregistered.
  ///
  /// This property should not change the value it returns during the lifetime
  /// of the binding. Changing the value of this property risks very confusing
  /// behavior as the [TestTextInput] may be inconsistently registered or
  /// unregistered.
268 269 270
  @protected
  bool get registerTestTextInput => true;

271
  /// Increase the timeout for the current test by the given duration.
272 273 274 275 276 277 278 279 280
  ///
  /// This only matters if the test has an `initialTimeout` set on
  /// [testWidgets], and the test is running via `flutter test`. By default,
  /// tests do not have such a timeout. Tests run using `flutter run` never time
  /// out even if one is specified.
  ///
  /// This method has no effect on the timeout specified via `timeout` on
  /// [testWidgets]. That timeout is implemented by the `test` package.
  ///
281 282 283 284
  /// By default, each [pump] and [WidgetTester.pumpWidget] call increases the
  /// timeout by a hundred milliseconds, and each [matchesGoldenFile]
  /// expectation increases it by a minute. If there is no timeout in the first
  /// place, this has no effect.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
  ///
  /// The granularity of timeouts is coarse: the time is checked once per
  /// second, and only when the test is not executing. It is therefore possible
  /// for a timeout to be exceeded by hundreds of milliseconds and for the test
  /// to still succeed. If precise timing is required, it should be implemented
  /// as a part of the test rather than relying on this mechanism.
  ///
  /// See also:
  ///
  ///  * [testWidgets], on which a timeout can be set using the `timeout`
  ///    argument.
  ///  * [defaultTestTimeout], the maximum that the timeout can reach.
  ///    (That timeout is implemented by the `test` package.)
  // See AutomatedTestWidgetsFlutterBinding.addTime for an actual implementation.
  void addTime(Duration duration);
300

301 302 303 304 305 306
  /// Delay for `duration` of time.
  ///
  /// In the automated test environment ([AutomatedTestWidgetsFlutterBinding],
  /// typically used in `flutter test`), this advances the fake [clock] for the
  /// period and also increases timeout (see [addTime]).
  ///
307
  /// In the live test environment ([LiveTestWidgetsFlutterBinding], typically
308 309 310 311
  /// used for `flutter run` and for [e2e](https://pub.dev/packages/e2e)), it is
  /// equivalent as [Future.delayed].
  Future<void> delayed(Duration duration);

312
  /// Creates and initializes the binding. This function is
313 314
  /// idempotent; calling it a second time will just return the
  /// previously-created instance.
315 316 317 318
  ///
  /// This function will use [AutomatedTestWidgetsFlutterBinding] if
  /// the test was run using `flutter test`, and
  /// [LiveTestWidgetsFlutterBinding] otherwise (e.g. if it was run
319 320 321 322 323 324 325 326 327 328 329 330 331
  /// using `flutter run`). This is determined by looking at the
  /// environment variables for a variable called `FLUTTER_TEST`.
  ///
  /// If `FLUTTER_TEST` is set with a value of 'true', then this test was
  /// invoked by `flutter test`. If `FLUTTER_TEST` is not set, or if it is set
  /// to 'false', then this test was invoked by `flutter run`.
  ///
  /// Browser environments do not currently support the
  /// [LiveTestWidgetsFlutterBinding], so this function will always set up an
  /// [AutomatedTestWidgetsFlutterBinding] when run in a web browser.
  ///
  /// The parameter `environment` is exposed to test different environment
  /// variable values, and should not be used.
332
  static WidgetsBinding ensureInitialized([@visibleForTesting Map<String, String>? environment]) => binding.ensureInitialized(environment);
333

334 335
  @override
  void initInstances() {
336
    super.initInstances();
337
    timeDilation = 1.0; // just in case the developer has artificially changed it for development
338 339 340 341
    if (overrideHttpClient) {
      binding.setupHttpOverrides();
    }
    _testTextInput = TestTextInput(onCleared: _resetFocusedEditable);
342 343
  }

344
  @override
345
  // ignore: MUST_CALL_SUPER
346 347 348 349 350
  void initLicenses() {
    // Do not include any licenses, because we're a test, and the LICENSE file
    // doesn't get generated for tests.
  }

351 352 353 354 355
  @override
  BinaryMessenger createBinaryMessenger() {
    return TestDefaultBinaryMessenger(super.createBinaryMessenger());
  }

356
  /// Whether there is currently a test executing.
357
  bool get inTest;
358

359 360 361
  /// The number of outstanding microtasks in the queue.
  int get microtaskCount;

362 363 364 365 366 367
  /// The default maximum test timeout for tests when using this binding.
  ///
  /// This controls the default for the `timeout` argument on `testWidgets`. It
  /// is 10 minutes for [AutomatedTestWidgetsFlutterBinding] (tests running
  /// using `flutter test`), and unlimited for tests using
  /// [LiveTestWidgetsFlutterBinding] (tests running using `flutter run`).
368
  ///
369 370
  /// This is the maximum that the timeout controlled by `initialTimeout` on
  /// [testWidgets] can reach when augmented using [addTime].
371
  test_package.Timeout get defaultTestTimeout;
372

373 374 375 376 377 378 379 380 381 382
  /// The current time.
  ///
  /// In the automated test environment (`flutter test`), this is a fake clock
  /// that begins in January 2015 at the start of the test and advances each
  /// time [pump] is called with a non-zero duration.
  ///
  /// In the live testing environment (`flutter run`), this object shows the
  /// actual current wall-clock time.
  Clock get clock;

383 384 385 386 387 388 389 390
  /// Triggers a frame sequence (build/layout/paint/etc),
  /// then flushes microtasks.
  ///
  /// If duration is set, then advances the clock by that much first.
  /// Doing this flushes microtasks.
  ///
  /// The supplied EnginePhase is the final phase reached during the pump pass;
  /// if not supplied, the whole pass is executed.
391 392 393
  ///
  /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
  /// this method works when the test is run with `flutter run`.
394
  Future<void> pump([ Duration? duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]);
395

396
  /// Runs a `callback` that performs real asynchronous work.
397 398 399 400 401
  ///
  /// This is intended for callers who need to call asynchronous methods where
  /// the methods spawn isolates or OS threads and thus cannot be executed
  /// synchronously by calling [pump].
  ///
402 403 404
  /// The `callback` must return a [Future] that completes to a value of type
  /// `T`.
  ///
405 406
  /// If `callback` completes successfully, this will return the future
  /// returned by `callback`.
407
  ///
408
  /// If `callback` completes with an error, the error will be caught by the
409
  /// Flutter framework and made available via [takeException], and this method
410
  /// will return a future that completes with `null`.
411 412 413 414 415
  ///
  /// Re-entrant calls to this method are not allowed; callers of this method
  /// are required to wait for the returned future to complete before calling
  /// this method again. Attempts to do otherwise will result in a
  /// [TestFailure] error being thrown.
416 417 418
  ///
  /// The `additionalTime` argument is used by the
  /// [AutomatedTestWidgetsFlutterBinding] implementation to increase the
419 420
  /// current timeout, if any. See [AutomatedTestWidgetsFlutterBinding.addTime]
  /// for details.
421
  Future<T?> runAsync<T>(
422
    Future<T> Function() callback, {
423
    Duration additionalTime = const Duration(milliseconds: 1000),
424
  });
425

426
  /// Artificially calls dispatchLocalesChanged on the Widget binding,
427
  /// then flushes microtasks.
428 429 430
  ///
  /// Passes only one single Locale. Use [setLocales] to pass a full preferred
  /// locales list.
431 432
  Future<void> setLocale(String languageCode, String countryCode) {
    return TestAsyncUtils.guard<void>(() async {
433
      assert(inTest);
434 435 436 437 438 439 440 441 442 443 444
      final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode);
      dispatchLocalesChanged(<Locale>[locale]);
    });
  }

  /// Artificially calls dispatchLocalesChanged on the Widget binding,
  /// then flushes microtasks.
  Future<void> setLocales(List<Locale> locales) {
    return TestAsyncUtils.guard<void>(() async {
      assert(inTest);
      dispatchLocalesChanged(locales);
445 446 447
    });
  }

448 449 450 451 452 453
  /// Re-attempts the initialization of the lifecycle state after providing
  /// test values in [TestWindow.initialLifecycleStateTestValue].
  void readTestInitialLifecycleStateFromNativeWindow() {
    readInitialLifecycleStateFromNativeWindow();
  }

454
  Size? _surfaceSize;
455 456 457 458 459

  /// Artificially changes the surface size to `size` on the Widget binding,
  /// then flushes microtasks.
  ///
  /// Set to null to use the default surface size.
460
  Future<void> setSurfaceSize(Size? size) {
461
    return TestAsyncUtils.guard<void>(() async {
462 463
      assert(inTest);
      if (_surfaceSize == size)
464
        return;
465 466 467 468 469 470 471
      _surfaceSize = size;
      handleMetricsChanged();
    });
  }

  @override
  ViewConfiguration createViewConfiguration() {
472 473
    final double devicePixelRatio = window.devicePixelRatio;
    final Size size = _surfaceSize ?? window.physicalSize / devicePixelRatio;
474
    return ViewConfiguration(
475 476 477 478 479
      size: size,
      devicePixelRatio: devicePixelRatio,
    );
  }

480 481 482
  /// Acts as if the application went idle.
  ///
  /// Runs all remaining microtasks, including those scheduled as a result of
483 484
  /// running them, until there are no more microtasks scheduled. Then, runs any
  /// previously scheduled timers with zero time, and completes the returned future.
485
  ///
486 487 488
  /// May result in an infinite loop or run out of memory if microtasks continue
  /// to recursively schedule new microtasks. Will not run any timers scheduled
  /// after this method was invoked, even if they are zero-time timers.
489 490 491
  Future<void> idle() {
    return TestAsyncUtils.guard<void>(() {
      final Completer<void> completer = Completer<void>();
492
      Timer.run(() {
493
        completer.complete();
494 495 496
      });
      return completer.future;
    });
497 498
  }

499
  /// Convert the given point from the global coordinate system (as used by
500 501
  /// pointer events from the device) to the coordinate system used by the
  /// tests (an 800 by 600 window).
502
  Offset globalToLocal(Offset point) => point;
503 504

  /// Convert the given point from the coordinate system used by the tests (an
505
  /// 800 by 600 window) to the global coordinate system (as used by pointer
506
  /// events from the device).
507
  Offset localToGlobal(Offset point) => point;
508

509 510 511 512 513 514 515
  // The source of the current pointer event.
  //
  // The [pointerEventSource] is set as the `source` parameter of
  // [handlePointerEvent] and can be used in the immediate enclosing
  // [dispatchEvent].
  TestBindingEventSource _pointerEventSource = TestBindingEventSource.device;

516
  @override
517 518
  void handlePointerEvent(
    PointerEvent event, {
519
    TestBindingEventSource source = TestBindingEventSource.device,
520
  }) {
521 522 523 524 525 526 527
    final TestBindingEventSource previousSource = source;
    _pointerEventSource = source;
    try {
      super.handlePointerEvent(event);
    } finally {
      _pointerEventSource = previousSource;
    }
528 529
  }

530 531 532
  /// A stub for the system's onscreen keyboard. Callers must set the
  /// [focusedEditable] before using this value.
  TestTextInput get testTextInput => _testTextInput;
533
  late TestTextInput _testTextInput;
534

535 536 537 538 539 540 541 542
  /// The [State] of the current [EditableText] client of the onscreen keyboard.
  ///
  /// Setting this property to a new value causes the given [EditableTextState]
  /// to focus itself and request the keyboard to establish a
  /// [TextInputConnection].
  ///
  /// Callers must pump an additional frame after setting this property to
  /// complete the focus change.
543 544 545
  ///
  /// Instead of setting this directly, consider using
  /// [WidgetTester.showKeyboard].
546 547 548
  //
  // TODO(ianh): We should just remove this property and move the call to
  // requestKeyboard to the WidgetTester.showKeyboard method.
549 550 551
  EditableTextState? get focusedEditable => _focusedEditable;
  EditableTextState? _focusedEditable;
  set focusedEditable(EditableTextState? value) {
552 553 554 555 556 557 558 559
    if (_focusedEditable != value) {
      _focusedEditable = value;
      value?.requestKeyboard();
    }
  }

  void _resetFocusedEditable() {
    _focusedEditable = null;
560 561
  }

562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
  /// Returns the exception most recently caught by the Flutter framework.
  ///
  /// Call this if you expect an exception during a test. If an exception is
  /// thrown and this is not called, then the exception is rethrown when
  /// the [testWidgets] call completes.
  ///
  /// If two exceptions are thrown in a row without the first one being
  /// acknowledged with a call to this method, then when the second exception is
  /// thrown, they are both dumped to the console and then the second is
  /// rethrown from the exception handler. This will likely result in the
  /// framework entering a highly unstable state and everything collapsing.
  ///
  /// It's safe to call this when there's no pending exception; it will return
  /// null in that case.
  dynamic takeException() {
577
    assert(inTest);
578
    final dynamic result = _pendingExceptionDetails?.exception;
579
    _pendingExceptionDetails = null;
580 581
    return result;
  }
582 583 584
  FlutterExceptionHandler? _oldExceptionHandler;
  late StackTraceDemangler _oldStackTraceDemangler;
  FlutterErrorDetails? _pendingExceptionDetails;
585

586 587
  static const TextStyle _messageStyle = TextStyle(
    color: Color(0xFF917FFF),
Ian Hickson's avatar
Ian Hickson committed
588
    fontSize: 40.0,
589 590
  );

591 592
  static const Widget _preTestMessage = Center(
    child: Text(
593
      'Test starting...',
594
      style: _messageStyle,
Ian Hickson's avatar
Ian Hickson committed
595
      textDirection: TextDirection.ltr,
596
    ),
597 598
  );

599 600
  static const Widget _postTestMessage = Center(
    child: Text(
601
      'Test finished.',
602
      style: _messageStyle,
Ian Hickson's avatar
Ian Hickson committed
603
      textDirection: TextDirection.ltr,
604
    ),
605 606 607 608 609 610
  );

  /// Whether to include the output of debugDumpApp() when reporting
  /// test failures.
  bool showAppDumpInErrors = false;

611
  /// Call the testBody inside a [FakeAsync] scope on which [pump] can
612 613 614 615 616 617
  /// advance time.
  ///
  /// Returns a future which completes when the test has run.
  ///
  /// Called by the [testWidgets] and [benchmarkWidgets] functions to
  /// run a test.
618 619 620
  ///
  /// The `invariantTester` argument is called after the `testBody`'s [Future]
  /// completes. If it throws, then the test is marked as failed.
621 622 623 624
  ///
  /// The `description` is used by the [LiveTestWidgetsFlutterBinding] to
  /// show a label on the screen during the test. The description comes from
  /// the value passed to [testWidgets]. It must not be null.
625 626 627
  ///
  /// The `timeout` argument sets the initial timeout, if any. It can
  /// be increased with [addTime]. By default there is no timeout.
628
  Future<void> runTest(Future<void> Function() testBody, VoidCallback invariantTester, { String description = '', Duration? timeout });
629 630 631 632 633 634 635 636 637 638

  /// This is called during test execution before and after the body has been
  /// executed.
  ///
  /// It's used by [AutomatedTestWidgetsFlutterBinding] to drain the microtasks
  /// before the final [pump] that happens during test cleanup.
  void asyncBarrier() {
    TestAsyncUtils.verifyAllScopesClosed();
  }

639
  Zone? _parentZone;
640

641
  VoidCallback _createTestCompletionHandler(String testDescription, Completer<void> completer) {
642 643 644 645 646 647
    return () {
      // This can get called twice, in the case of a Future without listeners failing, and then
      // our main future completing.
      assert(Zone.current == _parentZone);
      if (_pendingExceptionDetails != null) {
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
648
        reportTestException(_pendingExceptionDetails!, testDescription);
649 650 651
        _pendingExceptionDetails = null;
      }
      if (!completer.isCompleted)
652
        completer.complete();
653
    };
654 655
  }

656 657 658 659 660 661 662 663 664 665 666 667
  /// Called when the framework catches an exception, even if that exception is
  /// being handled by [takeException].
  ///
  /// This is called when there is no pending exception; if multiple exceptions
  /// are thrown and [takeException] isn't used, then subsequent exceptions are
  /// logged to the console regardless (and the test will fail).
  @protected
  void reportExceptionNoticed(FlutterErrorDetails exception) {
    // By default we do nothing.
    // The LiveTestWidgetsFlutterBinding overrides this to report the exception to the console.
  }

668
  Future<void> _runTest(
669
    Future<void> Function() testBody,
670 671
    VoidCallback invariantTester,
    String description, {
672
    Future<void>? timeout,
673
  }) {
674
    assert(description != null);
675
    assert(inTest);
676
    _oldExceptionHandler = FlutterError.onError;
677
    _oldStackTraceDemangler = FlutterError.demangleStackTrace;
678
    int _exceptionCount = 0; // number of un-taken exceptions
679
    FlutterError.onError = (FlutterErrorDetails details) {
680
      if (_pendingExceptionDetails != null) {
681
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
682 683
        if (_exceptionCount == 0) {
          _exceptionCount = 2;
684
          FlutterError.dumpErrorToConsole(_pendingExceptionDetails!, forceReport: true);
685 686
        } else {
          _exceptionCount += 1;
687
        }
688
        FlutterError.dumpErrorToConsole(details, forceReport: true);
689
        _pendingExceptionDetails = FlutterErrorDetails(
690
          exception: 'Multiple exceptions ($_exceptionCount) were detected during the running of the current test, and at least one was unexpected.',
691
          library: 'Flutter test framework',
692 693
        );
      } else {
694
        reportExceptionNoticed(details); // mostly this is just a hook for the LiveTestWidgetsFlutterBinding
695
        _pendingExceptionDetails = details;
696
      }
697
    };
698 699 700 701 702 703 704 705 706 707 708
    FlutterError.demangleStackTrace = (StackTrace stack) {
      // package:stack_trace uses ZoneSpecification.errorCallback to add useful
      // information to stack traces, in this case the Trace and Chain classes
      // can be present. Because these StackTrace implementations do not follow
      // the format the framework expects, we covert them to a vm trace here.
      if (stack is stack_trace.Trace)
        return stack.vmTrace;
      if (stack is stack_trace.Chain)
        return stack.toTrace().vmTrace;
      return stack;
    };
709
    final Completer<void> testCompleter = Completer<void>();
710
    final VoidCallback testCompletionHandler = _createTestCompletionHandler(description, testCompleter);
711
    void handleUncaughtError(Object exception, StackTrace stack) {
712 713 714 715 716 717 718 719
      if (testCompleter.isCompleted) {
        // Well this is not a good sign.
        // Ideally, once the test has failed we would stop getting errors from the test.
        // However, if someone tries hard enough they could get in a state where this happens.
        // If we silently dropped these errors on the ground, nobody would ever know. So instead
        // we report them to the console. They don't cause test failures, but hopefully someone
        // will see them in the logs at some point.
        debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
720
        FlutterError.dumpErrorToConsole(FlutterErrorDetails(
721
          exception: exception,
722
          stack: stack,
723
          context: ErrorDescription('running a test (but after the test had completed)'),
724
          library: 'Flutter test framework',
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
        ), forceReport: true);
        return;
      }
      // This is where test failures, e.g. those in expect(), will end up.
      // Specifically, runUnaryGuarded() will call this synchronously and
      // return our return value if _runTestBody fails synchronously (which it
      // won't, so this never happens), and Future will call this when the
      // Future completes with an error and it would otherwise call listeners
      // if the listener is in a different zone (which it would be for the
      // `whenComplete` handler below), or if the Future completes with an
      // error and the future has no listeners at all.
      //
      // This handler further calls the onError handler above, which sets
      // _pendingExceptionDetails. Nothing gets printed as a result of that
      // call unless we already had an exception pending, because in general
      // we want people to be able to cause the framework to report exceptions
      // and then use takeException to verify that they were really caught.
      // Now, if we actually get here, this isn't going to be one of those
      // cases. We only get here if the test has actually failed. So, once
      // we've carefully reported it, we then immediately end the test by
      // calling the testCompletionHandler in the _parentZone.
      //
      // We have to manually call testCompletionHandler because if the Future
      // library calls us, it is maybe _instead_ of calling a registered
      // listener from a different zone. In our case, that would be instead of
      // calling the whenComplete() listener below.
      //
      // We have to call it in the parent zone because if we called it in
      // _this_ zone, the test framework would find this zone was the current
      // zone and helpfully throw the error in this zone, causing us to be
      // directly called again.
756
      DiagnosticsNode treeDump;
757
      try {
758 759 760 761
        treeDump = renderViewElement?.toDiagnosticsNode() ?? DiagnosticsNode.message('<no tree>');
        // TODO(jacobr): this is a hack to make sure the tree can safely be fully dumped.
        // Potentially everything is good enough without this case.
        treeDump.toStringDeep();
762
      } catch (exception) {
763
        treeDump = DiagnosticsNode.message('<additional error caught while dumping tree: $exception>', level: DiagnosticLevel.error);
764
      }
765 766
      final List<DiagnosticsNode> omittedFrames = <DiagnosticsNode>[];
      final int stackLinesToOmit = reportExpectCall(stack, omittedFrames);
767
      FlutterError.reportError(FlutterErrorDetails(
768
        exception: exception,
769
        stack: stack,
770
        context: ErrorDescription('running a test'),
771 772 773 774
        library: 'Flutter test framework',
        stackFilter: (Iterable<String> frames) {
          return FlutterError.defaultStackFilter(frames.skip(stackLinesToOmit));
        },
775
        informationCollector: () sync* {
776
          if (stackLinesToOmit > 0)
777
            yield* omittedFrames;
778
          if (showAppDumpInErrors) {
779
            yield DiagnosticsProperty<DiagnosticsNode>('At the time of the failure, the widget tree looked as follows', treeDump, linePrefix: '# ', style: DiagnosticsTreeStyle.flat);
780
          }
781
          if (description.isNotEmpty)
782
            yield DiagnosticsProperty<String>('The test description was', description, style: DiagnosticsTreeStyle.errorProperty);
783
        },
784 785 786
      ));
      assert(_parentZone != null);
      assert(_pendingExceptionDetails != null, 'A test overrode FlutterError.onError but either failed to return it to its original state, or had unexpected additional errors that it could not handle. Typically, this is caused by using expect() before restoring FlutterError.onError.');
787
      _parentZone!.run<void>(testCompletionHandler);
788
    }
789
    final ZoneSpecification errorHandlingZoneSpecification = ZoneSpecification(
790
      handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object exception, StackTrace stack) {
791
        handleUncaughtError(exception, stack);
792 793 794
      }
    );
    _parentZone = Zone.current;
795
    final Zone testZone = _parentZone!.fork(specification: errorHandlingZoneSpecification);
796
    testZone.runBinary<Future<void>, Future<void> Function(), VoidCallback>(_runTestBody, testBody, invariantTester)
797
      .whenComplete(testCompletionHandler);
798
    timeout?.catchError(handleUncaughtError);
799
    return testCompleter.future;
800 801
  }

802
  Future<void> _runTestBody(Future<void> Function() testBody, VoidCallback invariantTester) async {
803
    assert(inTest);
804 805
    // So that we can assert that it remains the same after the test finishes.
    _beforeTestCheckIntrinsicSizes = debugCheckIntrinsicSizes;
806

807
    runApp(Container(key: UniqueKey(), child: _preTestMessage)); // Reset the tree to a known state.
808
    await pump();
809 810 811
    // Pretend that the first frame produced in the test body is the first frame
    // sent to the engine.
    resetFirstFrameSent();
812

813
    final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
814
    final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
815
    final ErrorWidgetBuilder errorWidgetBuilderBeforeTest = ErrorWidget.builder;
816

817
    // run the test
818
    await testBody();
819 820 821 822 823 824
    asyncBarrier(); // drains the microtasks in `flutter test` mode (when using AutomatedTestWidgetsFlutterBinding)

    if (_pendingExceptionDetails == null) {
      // We only try to clean up and verify invariants if we didn't already
      // fail. If we got an exception already, then we instead leave everything
      // alone so that we don't cause more spurious errors.
825
      runApp(Container(key: UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets.
826
      await pump();
827 828
      if (registerTestTextInput)
        _testTextInput.unregister();
829
      invariantTester();
830
      _verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
831
      _verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
832
      _verifyErrorWidgetBuilderUnset(errorWidgetBuilderBeforeTest);
833 834 835 836
      _verifyInvariants();
    }

    assert(inTest);
837
    asyncBarrier(); // When using AutomatedTestWidgetsFlutterBinding, this flushes the microtasks.
838 839
  }

840 841
  late bool _beforeTestCheckIntrinsicSizes;

842 843 844 845
  void _verifyInvariants() {
    assert(debugAssertNoTransientCallbacks(
      'An animation is still running even after the widget tree was disposed.'
    ));
846 847 848 849
    assert(debugAssertAllFoundationVarsUnset(
      'The value of a foundation debug variable was changed by the test.',
      debugPrintOverride: debugPrintOverride,
    ));
850 851 852
    assert(debugAssertAllGesturesVarsUnset(
      'The value of a gestures debug variable was changed by the test.',
    ));
853 854 855 856
    assert(debugAssertAllPaintingVarsUnset(
      'The value of a painting debug variable was changed by the test.',
      debugDisableShadowsOverride: disableShadows,
    ));
857
    assert(debugAssertAllRenderVarsUnset(
858
      'The value of a rendering debug variable was changed by the test.',
859
      debugCheckIntrinsicSizesOverride: _beforeTestCheckIntrinsicSizes,
860 861
    ));
    assert(debugAssertAllWidgetVarsUnset(
862
      'The value of a widget debug variable was changed by the test.',
863 864
    ));
    assert(debugAssertAllSchedulerVarsUnset(
865
      'The value of a scheduler debug variable was changed by the test.',
866
    ));
867 868
  }

869 870 871
  void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) {
    assert(() {
      if (autoUpdateGoldenFiles != valueBeforeTest) {
872 873
        FlutterError.reportError(FlutterErrorDetails(
          exception: FlutterError(
874 875 876 877 878 879 880 881 882 883
              'The value of autoUpdateGoldenFiles was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

884 885 886 887 888 889 890 891
  void _verifyReportTestExceptionUnset(TestExceptionReporter valueBeforeTest) {
    assert(() {
      if (reportTestException != valueBeforeTest) {
        // We can't report this error to their modified reporter because we
        // can't be guaranteed that their reporter will cause the test to fail.
        // So we reset the error reporter to its initial value and then report
        // this error.
        reportTestException = valueBeforeTest;
892 893
        FlutterError.reportError(FlutterErrorDetails(
          exception: FlutterError(
894 895 896 897 898 899 900 901 902 903
            'The value of reportTestException was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

904 905 906 907 908 909 910 911 912 913 914 915 916 917 918
  void _verifyErrorWidgetBuilderUnset(ErrorWidgetBuilder valueBeforeTest) {
    assert(() {
      if (ErrorWidget.builder != valueBeforeTest) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: FlutterError(
              'The value of ErrorWidget.builder was changed by the test.',
          ),
          stack: StackTrace.current,
          library: 'Flutter test framework',
        ));
      }
      return true;
    }());
  }

919 920 921
  /// Called by the [testWidgets] function after a test is executed.
  void postTest() {
    assert(inTest);
922
    FlutterError.onError = _oldExceptionHandler;
923
    FlutterError.demangleStackTrace = _oldStackTraceDemangler;
924 925
    _pendingExceptionDetails = null;
    _parentZone = null;
926 927
    buildOwner!.focusManager.dispose();
    buildOwner!.focusManager = FocusManager()..registerGlobalHandlers();
928 929 930 931 932
    // Disabling the warning because @visibleForTesting doesn't take the testing
    // framework itself into account, but we don't want it visible outside of
    // tests.
    // ignore: invalid_use_of_visible_for_testing_member
    RawKeyboard.instance.clearKeysPressed();
933
    assert(!RendererBinding.instance!.mouseTracker.mouseIsConnected,
934 935 936
        'The MouseTracker thinks that there is still a mouse connected, which indicates that a '
        'test has not removed the mouse pointer which it added. Call removePointer on the '
        'active mouse gesture to remove the mouse pointer.');
937
    // ignore: invalid_use_of_visible_for_testing_member
938
    RendererBinding.instance!.initMouseTracker();
939 940 941 942 943 944 945 946
  }
}

/// A variant of [TestWidgetsFlutterBinding] for executing tests in
/// the `flutter test` environment.
///
/// This binding controls time, allowing tests to verify long
/// animation sequences without having to execute them in real time.
947 948 949
///
/// This class assumes it is always run in checked mode (since tests are always
/// run in checked mode).
950 951 952 953
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  void initInstances() {
    super.initInstances();
954
    binding.mockFlutterAssets();
955 956
  }

957 958
  FakeAsync? _currentFakeAsync; // set in runTest; cleared in postTest
  Completer<void>? _pendingAsyncTasks;
959 960

  @override
961 962 963 964 965
  Clock get clock {
    assert(inTest);
    return _clock!;
  }
  Clock? _clock;
966

967 968 969
  @override
  DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;

970 971 972
  @override
  bool get disableShadows => true;

973 974 975
  /// The value of [defaultTestTimeout] can be set to `None` to enable debugging flutter tests where
  /// we would not want to timeout the test. This is expected to be used by test tooling which
  /// can detect debug mode.
976
  @override
977
  test_package.Timeout defaultTestTimeout = const test_package.Timeout(Duration(minutes: 10));
978 979

  @override
980
  bool get inTest => _currentFakeAsync != null;
981

982
  @override
983
  int get microtaskCount => _currentFakeAsync!.microtaskCount;
984

985
  @override
986
  Future<void> pump([ Duration? duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
987
    return TestAsyncUtils.guard<void>(() {
988 989 990
      assert(inTest);
      assert(_clock != null);
      if (duration != null)
991
        _currentFakeAsync!.elapse(duration);
992 993
      _phase = newPhase;
      if (hasScheduledFrame) {
994
        addTime(const Duration(milliseconds: 500));
995
        _currentFakeAsync!.flushMicrotasks();
996
        handleBeginFrame(Duration(
997
          milliseconds: _clock!.now().millisecondsSinceEpoch,
998
        ));
999
        _currentFakeAsync!.flushMicrotasks();
1000
        handleDrawFrame();
1001
      }
1002
      _currentFakeAsync!.flushMicrotasks();
1003
      return Future<void>.value();
1004 1005 1006
    });
  }

1007
  @override
1008
  Future<T?> runAsync<T>(
1009
    Future<T> Function() callback, {
1010
    Duration additionalTime = const Duration(milliseconds: 1000),
1011 1012
  }) {
    assert(additionalTime != null);
1013 1014 1015
    assert(() {
      if (_pendingAsyncTasks == null)
        return true;
1016
      throw test_package.TestFailure(
1017 1018 1019 1020 1021 1022 1023
          'Reentrant call to runAsync() denied.\n'
          'runAsync() was called, then before its future completed, it '
          'was called again. You must wait for the first returned future '
          'to complete before calling runAsync() again.'
      );
    }());

1024
    final Zone realAsyncZone = Zone.current.fork(
1025
      specification: ZoneSpecification(
1026
        scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void Function() f) {
1027 1028
          Zone.root.scheduleMicrotask(f);
        },
1029
        createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() f) {
1030 1031
          return Zone.root.createTimer(duration, f);
        },
1032
        createPeriodicTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration period, void Function(Timer timer) f) {
1033 1034 1035 1036 1037
          return Zone.root.createPeriodicTimer(period, f);
        },
      ),
    );

1038 1039
    addTime(additionalTime);

1040
    return realAsyncZone.run<Future<T?>>(() async {
1041
      _pendingAsyncTasks = Completer<void>();
1042 1043 1044 1045
      T? result;
      try {
        result = await callback();
      } catch (exception, stack) {
1046
        FlutterError.reportError(FlutterErrorDetails(
1047 1048 1049
          exception: exception,
          stack: stack,
          library: 'Flutter test framework',
1050
          context: ErrorDescription('while running async test code'),
1051
        ));
1052
      } finally {
1053 1054 1055 1056 1057
        // We complete the _pendingAsyncTasks future successfully regardless of
        // whether an exception occurred because in the case of an exception,
        // we already reported the exception to FlutterError. Moreover,
        // completing the future with an error would trigger an unhandled
        // exception due to zone error boundaries.
1058
        _pendingAsyncTasks!.complete();
1059
        _pendingAsyncTasks = null;
1060 1061
      }
      return result;
1062 1063 1064
    });
  }

1065 1066
  @override
  void ensureFrameCallbacksRegistered() {
1067
    // Leave PlatformDispatcher alone, do nothing.
1068 1069
    assert(window.onDrawFrame == null);
    assert(window.onBeginFrame == null);
1070 1071
  }

1072 1073 1074 1075
  @override
  void scheduleWarmUpFrame() {
    // We override the default version of this so that the application-startup warm-up frame
    // does not schedule timers which we might never get around to running.
1076
    assert(inTest);
1077
    handleBeginFrame(null);
1078
    _currentFakeAsync!.flushMicrotasks();
1079
    handleDrawFrame();
1080
    _currentFakeAsync!.flushMicrotasks();
1081 1082
  }

1083 1084 1085 1086
  @override
  void scheduleAttachRootWidget(Widget rootWidget) {
    // We override the default version of this so that the application-startup widget tree
    // build does not schedule timers which we might never get around to running.
1087
    assert(inTest);
1088
    attachRootWidget(rootWidget);
1089
    _currentFakeAsync!.flushMicrotasks();
1090 1091
  }

1092
  @override
1093
  Future<void> idle() {
1094
    assert(inTest);
1095
    final Future<void> result = super.idle();
1096
    _currentFakeAsync!.elapse(Duration.zero);
1097 1098 1099
    return result;
  }

1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
  int _firstFrameDeferredCount = 0;
  bool _firstFrameSent = false;

  @override
  bool get sendFramesToEngine => _firstFrameSent || _firstFrameDeferredCount == 0;

  @override
  void deferFirstFrame() {
    assert(_firstFrameDeferredCount >= 0);
    _firstFrameDeferredCount += 1;
  }

  @override
  void allowFirstFrame() {
    assert(_firstFrameDeferredCount > 0);
    _firstFrameDeferredCount -= 1;
    // Unlike in RendererBinding.allowFirstFrame we do not force a frame her
    // to give the test full control over frame scheduling.
  }

  @override
  void resetFirstFrameSent() {
    _firstFrameSent = false;
  }

1125
  EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
1126

1127
  // Cloned from RendererBinding.drawFrame() but with early-exit semantics.
1128
  @override
1129
  void drawFrame() {
1130
    assert(inTest);
1131 1132
    try {
      debugBuildingDirtyElements = true;
1133
      buildOwner!.buildScope(renderViewElement!);
1134 1135 1136 1137 1138 1139 1140
      if (_phase != EnginePhase.build) {
        assert(renderView != null);
        pipelineOwner.flushLayout();
        if (_phase != EnginePhase.layout) {
          pipelineOwner.flushCompositingBits();
          if (_phase != EnginePhase.compositingBits) {
            pipelineOwner.flushPaint();
1141 1142
            if (_phase != EnginePhase.paint && sendFramesToEngine) {
              _firstFrameSent = true;
1143 1144 1145
              renderView.compositeFrame(); // this sends the bits to the GPU
              if (_phase != EnginePhase.composite) {
                pipelineOwner.flushSemantics();
1146 1147
                assert(_phase == EnginePhase.flushSemantics ||
                       _phase == EnginePhase.sendSemanticsUpdate);
1148 1149 1150 1151 1152
              }
            }
          }
        }
      }
1153
      buildOwner!.finalizeTree();
1154
    } finally {
1155 1156
      debugBuildingDirtyElements = false;
    }
1157 1158
  }

1159 1160 1161 1162
  Duration? _timeout;
  Stopwatch? _timeoutStopwatch;
  Timer? _timeoutTimer;
  Completer<void>? _timeoutCompleter;
1163 1164 1165

  void _checkTimeout(Timer timer) {
    assert(_timeoutTimer == timer);
1166
    assert(_timeout != null);
1167 1168 1169 1170
    assert(_timeoutCompleter != null);
    assert(_timeoutStopwatch != null);
    if (_timeoutStopwatch!.elapsed > _timeout!) {
      _timeoutCompleter!.completeError(
1171
        TimeoutException(
1172
          'The test exceeded the timeout. It may have hung.\n'
1173
          'Consider using "tester.binding.addTime" to increase the timeout before expensive operations.',
1174 1175 1176 1177 1178 1179
          _timeout,
        ),
      );
    }
  }

1180
  @override
1181
  void addTime(Duration duration) {
1182
    if (_timeout != null)
1183
      _timeout = _timeout! + duration;
1184 1185
  }

1186 1187 1188 1189
  @override
  Future<void> delayed(Duration duration) {
    assert(_currentFakeAsync != null);
    addTime(duration);
1190
    _currentFakeAsync!.elapse(duration);
1191 1192 1193
    return Future<void>.value();
  }

1194
  @override
1195
  Future<void> runTest(
1196
    Future<void> Function() testBody,
1197
    VoidCallback invariantTester, {
1198
    String description = '',
1199
    Duration? timeout,
1200
  }) {
1201
    assert(description != null);
1202
    assert(!inTest);
1203
    assert(_currentFakeAsync == null);
1204
    assert(_clock == null);
1205 1206

    _timeout = timeout;
1207 1208 1209 1210 1211
    if (_timeout != null) {
      _timeoutStopwatch = Stopwatch()..start();
      _timeoutTimer = Timer.periodic(const Duration(seconds: 1), _checkTimeout);
      _timeoutCompleter = Completer<void>();
    }
1212

1213
    final FakeAsync fakeAsync = FakeAsync();
1214
    _currentFakeAsync = fakeAsync; // reset in postTest
1215
    _clock = fakeAsync.getClock(DateTime.utc(2015, 1, 1));
1216
    late Future<void> testBodyResult;
1217 1218 1219
    fakeAsync.run((FakeAsync localFakeAsync) {
      assert(fakeAsync == _currentFakeAsync);
      assert(fakeAsync == localFakeAsync);
1220
      testBodyResult = _runTest(testBody, invariantTester, description, timeout: _timeoutCompleter?.future);
1221 1222
      assert(inTest);
    });
1223

1224
    return Future<void>.microtask(() async {
1225 1226 1227 1228 1229 1230
      // testBodyResult is a Future that was created in the Zone of the
      // fakeAsync. This means that if we await it here, it will register a
      // microtask to handle the future _in the fake async zone_. We avoid this
      // by calling '.then' in the current zone. While flushing the microtasks
      // of the fake-zone below, the new future will be completed and can then
      // be used without fakeAsync.
1231

1232
      final Future<void> resultFuture = testBodyResult.then<void>((_) {
1233 1234 1235
        // Do nothing.
      });

1236
      // Resolve interplay between fake async and real async calls.
1237
      fakeAsync.flushMicrotasks();
1238
      while (_pendingAsyncTasks != null) {
1239
        await _pendingAsyncTasks!.future;
1240
        fakeAsync.flushMicrotasks();
1241
      }
1242
      return resultFuture;
1243
    });
1244 1245
  }

1246 1247
  @override
  void asyncBarrier() {
1248
    assert(_currentFakeAsync != null);
1249
    _currentFakeAsync!.flushMicrotasks();
1250 1251
    super.asyncBarrier();
  }
1252

1253 1254 1255
  @override
  void _verifyInvariants() {
    super._verifyInvariants();
1256

1257 1258
    assert(inTest);

1259
    bool timersPending = false;
1260 1261
    if (_currentFakeAsync!.periodicTimerCount != 0 ||
        _currentFakeAsync!.nonPeriodicTimerCount != 0) {
1262
        debugPrint('Pending timers:');
1263
        for (final FakeTimer timer in _currentFakeAsync!.pendingTimers) {
1264 1265 1266 1267 1268 1269 1270 1271 1272
          debugPrint(
            'Timer (duration: ${timer.duration}, '
            'periodic: ${timer.isPeriodic}), created:');
          debugPrintStack(stackTrace: timer.creationStackTrace);
          debugPrint('');
        }
        timersPending = true;
    }
    assert(!timersPending, 'A Timer is still pending even after the widget tree was disposed.');
1273
    assert(_currentFakeAsync!.microtaskCount == 0); // Shouldn't be possible.
1274 1275
  }

1276
  @override
1277
  void postTest() {
1278
    super.postTest();
1279
    assert(_currentFakeAsync != null);
1280 1281
    assert(_clock != null);
    _clock = null;
1282
    _currentFakeAsync = null;
1283
    _timeoutCompleter = null;
1284
    _timeoutTimer?.cancel();
1285 1286 1287
    _timeoutTimer = null;
    _timeoutStopwatch = null;
    _timeout = null;
1288
  }
1289
}
1290

1291 1292 1293 1294
/// Available policies for how a [LiveTestWidgetsFlutterBinding] should paint
/// frames.
///
/// These values are set on the binding's
1295 1296
/// [LiveTestWidgetsFlutterBinding.framePolicy] property.
///
1297
/// {@template flutter.flutter_test.LiveTestWidgetsFlutterBindingFramePolicy}
1298 1299 1300 1301 1302 1303 1304 1305 1306
/// The default is [LiveTestWidgetsFlutterBindingFramePolicy.fadePointers].
/// Setting this to anything other than
/// [LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps] results in pumping
/// extra frames, which might involve calling builders more, or calling paint
/// callbacks more, etc, and might interfere with the test. If you know that
/// your test won't be affected by this, you can set the policy to
/// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive] or
/// [LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive] in that particular
/// file.
1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318
///
/// To set a value while still allowing the test file to work as a normal test,
/// add the following code to your test file at the top of your
/// `void main() { }` function, before calls to [testWidgets]:
///
/// ```dart
/// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
/// if (binding is LiveTestWidgetsFlutterBinding) {
///   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.[thePolicy];
/// }
/// ```
/// {@endtemplate}
1319
enum LiveTestWidgetsFlutterBindingFramePolicy {
1320 1321 1322
  /// Strictly show only frames that are explicitly pumped.
  ///
  /// This most closely matches the [AutomatedTestWidgetsFlutterBinding]
1323
  /// (the default binding for `flutter test`) behavior.
1324 1325 1326 1327 1328 1329
  onlyPumps,

  /// Show pumped frames, and additionally schedule and run frames to fade
  /// out the pointer crosshairs and other debugging information shown by
  /// the binding.
  ///
1330 1331 1332
  /// This will schedule frames when pumped or when there has been some
  /// activity with [TestPointer]s.
  ///
1333 1334 1335 1336 1337 1338 1339
  /// This can result in additional frames being pumped beyond those that
  /// the test itself requests, which can cause differences in behavior.
  fadePointers,

  /// Show every frame that the framework requests, even if the frames are not
  /// explicitly pumped.
  ///
1340
  /// The major difference between [fullyLive] and [benchmarkLive] is the latter
1341
  /// ignores frame requests by [WidgetTester.pump].
1342
  ///
1343 1344 1345 1346 1347
  /// This can help with orienting the developer when looking at
  /// heavily-animated situations, and will almost certainly result in
  /// additional frames being pumped beyond those that the test itself requests,
  /// which can cause differences in behavior.
  fullyLive,
1348 1349 1350 1351 1352 1353

  /// Ignore any request to schedule a frame.
  ///
  /// This is intended to be used by benchmarks (hence the name) that drive the
  /// pipeline directly. It tells the binding to entirely ignore requests for a
  /// frame to be scheduled, while still allowing frames that are pumped
1354
  /// directly to run (either by using [WidgetTester.pumpBenchmark] or invoking
1355
  /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame]).
1356
  ///
1357 1358
  /// This allows all frame requests from the engine to be serviced, and allows
  /// all frame requests that are artificially triggered to be serviced, but
1359
  /// ignores [SchedulerBinding.scheduleFrame] requests from the framework.
1360 1361 1362
  /// Therefore animation won't run for this mode because the framework
  /// generates an animation by requesting new frames.
  ///
1363 1364 1365 1366 1367 1368
  /// The [SchedulerBinding.hasScheduledFrame] property will never be true in
  /// this mode. This can cause unexpected effects. For instance,
  /// [WidgetTester.pumpAndSettle] does not function in this mode, as it relies
  /// on the [SchedulerBinding.hasScheduledFrame] property to determine when the
  /// application has "settled".
  benchmark,
1369 1370 1371 1372 1373 1374 1375

  /// Ignore any request from pump but respect other requests to schedule a
  /// frame.
  ///
  /// This is used for running the test on a device, where scheduling of new
  /// frames respects what the engine and the device needed.
  ///
1376 1377 1378 1379 1380
  /// Compared to [fullyLive] this policy ignores the frame requests from
  /// [WidgetTester.pump] so that frame scheduling mimics that of the real
  /// environment, and avoids waiting for an artificially pumped frame. (For
  /// example, when driving the test in methods like
  /// [WidgetTester.handlePointerEventRecord] or [WidgetTester.fling].)
1381
  ///
1382
  /// This policy differs from [benchmark] in that it can be used for capturing
1383 1384
  /// animation frames requested by the framework.
  benchmarkLive,
1385 1386
}

1387 1388 1389 1390 1391 1392 1393 1394
/// A variant of [TestWidgetsFlutterBinding] for executing tests in
/// the `flutter run` environment, on a device. This is intended to
/// allow interactive test development.
///
/// This is not the way to run a remote-control test. To run a test on
/// a device from a development computer, see the [flutter_driver]
/// package and the `flutter drive` command.
///
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409
/// When running tests using `flutter run`, consider adding the
/// `--use-test-fonts` argument so that the fonts used match those used under
/// `flutter test`. (This forces all text to use the "Ahem" font, which is a
/// font that covers ASCII characters and gives them all the appearance of a
/// square whose size equals the font size.)
///
/// This binding overrides the default [SchedulerBinding] behavior to ensure
/// that tests work in the same way in this environment as they would under the
/// [AutomatedTestWidgetsFlutterBinding]. To override this (and see intermediate
/// frames that the test does not explicitly trigger), set [framePolicy] to
/// [LiveTestWidgetsFlutterBindingFramePolicy.fullyLive]. (This is likely to
/// make tests fail, though, especially if e.g. they test how many times a
/// particular widget was built.) The default behavior is to show pumped frames
/// and a few additional frames when pointers are triggered (to animate the
/// pointer crosshairs).
1410 1411 1412 1413 1414 1415 1416 1417 1418 1419
///
/// This binding does not support the [EnginePhase] argument to
/// [pump]. (There would be no point setting it to a value that
/// doesn't trigger a paint, since then you could not see anything
/// anyway.)
class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
  @override
  bool get inTest => _inTest;
  bool _inTest = false;

1420 1421 1422
  @override
  Clock get clock => const Clock();

1423 1424
  @override
  int get microtaskCount {
1425 1426
    // The Dart SDK doesn't report this number.
    assert(false, 'microtaskCount cannot be reported when running in real time');
1427 1428 1429
    return -1;
  }

1430 1431 1432
  @override
  test_package.Timeout get defaultTestTimeout => test_package.Timeout.none;

1433
  Completer<void>? _pendingFrame;
1434
  bool _expectingFrame = false;
1435
  bool _viewNeedsPaint = false;
1436
  bool _runningAsyncTasks = false;
1437

1438
  /// The strategy for [pump]ing and requesting new frames.
1439
  ///
1440 1441 1442 1443 1444
  /// The policy decides whether [pump] (with a duration) pumps a single frame
  /// (as would happen in a normal test environment using
  /// [AutomatedTestWidgetsFlutterBinding]), or pumps every frame that the
  /// system requests during an asynchronous pause (as would normally happen
  /// when running an application with [WidgetsFlutterBinding]).
1445
  ///
1446
  /// {@macro flutter.flutter_test.LiveTestWidgetsFlutterBindingFramePolicy}
1447
  ///
1448
  /// See [LiveTestWidgetsFlutterBindingFramePolicy].
1449
  LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
1450

1451 1452 1453 1454 1455 1456
  @override
  void addTime(Duration duration) {
    // We don't support timeouts on the LiveTestWidgetsFlutterBinding.
    // See runTest().
  }

1457 1458 1459 1460 1461
  @override
  Future<void> delayed(Duration duration) {
    return Future<void>.delayed(duration);
  }

1462 1463 1464 1465 1466 1467 1468
  @override
  void scheduleFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleFrame();
  }

1469 1470 1471 1472 1473 1474 1475
  @override
  void scheduleForcedFrame() {
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
      return; // In benchmark mode, don't actually schedule any engine frames.
    super.scheduleForcedFrame();
  }

1476
  bool? _doDrawThisFrame;
1477

1478
  @override
1479
  void handleBeginFrame(Duration? rawTimeStamp) {
1480
    assert(_doDrawThisFrame == null);
1481 1482
    if (_expectingFrame ||
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
1483
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive) ||
1484
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) ||
1485 1486
        (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint)) {
      _doDrawThisFrame = true;
1487
      super.handleBeginFrame(rawTimeStamp);
1488 1489 1490
    } else {
      _doDrawThisFrame = false;
    }
1491 1492 1493 1494 1495
  }

  @override
  void handleDrawFrame() {
    assert(_doDrawThisFrame != null);
1496
    if (_doDrawThisFrame!)
1497 1498
      super.handleDrawFrame();
    _doDrawThisFrame = null;
1499 1500
    _viewNeedsPaint = false;
    if (_expectingFrame) { // set during pump
1501
      assert(_pendingFrame != null);
1502
      _pendingFrame!.complete(); // unlocks the test API
1503 1504
      _pendingFrame = null;
      _expectingFrame = false;
1505
    } else if (framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
1506
      window.scheduleFrame();
1507 1508 1509
    }
  }

1510 1511
  @override
  void initRenderView() {
1512
    renderView = _LiveTestRenderView(
1513 1514
      configuration: createViewConfiguration(),
      onNeedPaint: _handleViewNeedsPaint,
1515
      window: window,
1516
    );
1517
    renderView.prepareInitialFrame();
1518 1519 1520
  }

  @override
1521
  _LiveTestRenderView get renderView => super.renderView as _LiveTestRenderView;
1522

1523 1524 1525 1526 1527
  void _handleViewNeedsPaint() {
    _viewNeedsPaint = true;
    renderView.markNeedsPaint();
  }

1528 1529 1530 1531 1532 1533 1534
  /// An object to which real device events should be routed.
  ///
  /// Normally, device events are silently dropped. However, if this property is
  /// set to a non-null value, then the events will be routed to its
  /// [HitTestDispatcher.dispatchEvent] method instead.
  ///
  /// Events dispatched by [TestGesture] are not affected by this.
1535
  HitTestDispatcher? deviceEventDispatcher;
1536

1537

1538
  /// Dispatch an event to the targets found by a hit test on its position.
1539 1540 1541
  ///
  /// Apart from forwarding the event to [GestureBinding.dispatchEvent],
  /// This also paint all events that's down on the screen.
1542
  @override
1543 1544
  void handlePointerEvent(
    PointerEvent event, {
1545
    TestBindingEventSource source = TestBindingEventSource.device,
1546
  }) {
1547 1548
    switch (source) {
      case TestBindingEventSource.test:
1549 1550 1551
        final _LiveTestPointerRecord? record = renderView._pointers[event.pointer];
        if (record != null) {
          record.position = event.position;
1552
          if (!event.down)
1553
            record.decay = _kPointerDecay;
1554 1555 1556 1557 1558 1559 1560
          _handleViewNeedsPaint();
        } else if (event.down) {
          renderView._pointers[event.pointer] = _LiveTestPointerRecord(
            event.pointer,
            event.position,
          );
          _handleViewNeedsPaint();
1561
        }
1562
        super.handlePointerEvent(event, source: TestBindingEventSource.test);
1563 1564 1565
        break;
      case TestBindingEventSource.device:
        if (deviceEventDispatcher != null)
1566 1567 1568 1569 1570 1571
          super.handlePointerEvent(event, source: TestBindingEventSource.device);
        break;
    }
  }

  @override
1572
  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
1573 1574 1575 1576 1577
    switch (_pointerEventSource) {
      case TestBindingEventSource.test:
        super.dispatchEvent(event, hitTestResult);
        break;
      case TestBindingEventSource.device:
1578 1579 1580
        assert(hitTestResult != null);
        assert(deviceEventDispatcher != null);
        deviceEventDispatcher!.dispatchEvent(event, hitTestResult!);
1581
        break;
1582 1583 1584
    }
  }

1585
  @override
1586
  Future<void> pump([ Duration? duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
1587
    assert(newPhase == EnginePhase.sendSemanticsUpdate);
1588 1589 1590
    assert(inTest);
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
1591 1592 1593 1594
    if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive) {
      // Ignore all pumps and just wait.
      return delayed(duration ?? Duration.zero);
    }
1595
    return TestAsyncUtils.guard<void>(() {
1596
      if (duration != null) {
1597
        Timer(duration, () {
1598 1599 1600 1601 1602 1603 1604
          _expectingFrame = true;
          scheduleFrame();
        });
      } else {
        _expectingFrame = true;
        scheduleFrame();
      }
1605
      _pendingFrame = Completer<void>();
1606
      return _pendingFrame!.future;
1607 1608 1609
    });
  }

1610
  @override
1611
  Future<T?> runAsync<T>(
1612
    Future<T> Function() callback, {
1613
    Duration additionalTime = const Duration(milliseconds: 1000),
1614
  }) async {
1615 1616 1617
    assert(() {
      if (!_runningAsyncTasks)
        return true;
1618
      throw test_package.TestFailure(
1619 1620 1621 1622 1623 1624 1625
          'Reentrant call to runAsync() denied.\n'
          'runAsync() was called, then before its future completed, it '
          'was called again. You must wait for the first returned future '
          'to complete before calling runAsync() again.'
      );
    }());

1626 1627
    addTime(additionalTime); // doesn't do anything since we don't actually track the timeout, but just for correctness...

1628 1629 1630 1631
    _runningAsyncTasks = true;
    try {
      return await callback();
    } catch (error, stack) {
1632
      FlutterError.reportError(FlutterErrorDetails(
1633 1634 1635
        exception: error,
        stack: stack,
        library: 'Flutter test framework',
1636
        context: ErrorSummary('while running async test code'),
1637 1638 1639 1640 1641 1642 1643
      ));
      return null;
    } finally {
      _runningAsyncTasks = false;
    }
  }

1644
  @override
1645
  Future<void> runTest(Future<void> Function() testBody, VoidCallback invariantTester, { String description = '', Duration? timeout }) async {
1646
    assert(description != null);
1647 1648
    assert(!inTest);
    _inTest = true;
1649
    renderView._setDescription(description);
1650 1651 1652 1653
    // We drop the timeout on the floor in `flutter run` mode.
    // We could support it, but we'd have to automatically add the entire duration of pumps
    // and timers and so on, since those operate in real time when using this binding, but
    // the timeouts expect them to happen near-instantaneously.
1654
    return _runTest(testBody, invariantTester, description);
1655 1656
  }

1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671
  @override
  void reportExceptionNoticed(FlutterErrorDetails exception) {
    final DebugPrintCallback testPrint = debugPrint;
    debugPrint = debugPrintOverride;
    debugPrint('(The following exception is now available via WidgetTester.takeException:)');
    FlutterError.dumpErrorToConsole(exception, forceReport: true);
    debugPrint(
      '(If WidgetTester.takeException is called, the above exception will be ignored. '
      'If it is not, then the above exception will be dumped when another exception is '
      'caught by the framework or when the test ends, whichever happens first, and then '
      'the test will fail due to having not caught or expected the exception.)'
    );
    debugPrint = testPrint;
  }

1672 1673 1674 1675 1676 1677 1678 1679 1680 1681
  @override
  void postTest() {
    super.postTest();
    assert(!_expectingFrame);
    assert(_pendingFrame == null);
    _inTest = false;
  }

  @override
  ViewConfiguration createViewConfiguration() {
1682 1683 1684 1685
    return TestViewConfiguration(
      size: _surfaceSize ?? _kDefaultTestViewportSize,
      window: window,
    );
1686 1687 1688
  }

  @override
1689
  Offset globalToLocal(Offset point) {
1690 1691
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
    final double det = transform.invert();
1692
    assert(det != 0.0);
1693
    final Offset result = MatrixUtils.transformPoint(transform, point);
1694 1695 1696 1697
    return result;
  }

  @override
1698
  Offset localToGlobal(Offset point) {
1699
    final Matrix4 transform = renderView.configuration.toHitTestMatrix();
1700 1701
    return MatrixUtils.transformPoint(transform, point);
  }
1702
}
1703

1704 1705
/// A [ViewConfiguration] that pretends the display is of a particular size. The
/// size is in logical pixels. The resulting ViewConfiguration maps the given
1706
/// size onto the actual display using the [BoxFit.contain] algorithm.
1707 1708
class TestViewConfiguration extends ViewConfiguration {
  /// Creates a [TestViewConfiguration] with the given size. Defaults to 800x600.
1709 1710 1711
  ///
  /// If a [window] instance is not provided it defaults to [ui.window].
  factory TestViewConfiguration({
1712
    Size size = _kDefaultTestViewportSize,
1713
    ui.FlutterView? window,
1714 1715 1716 1717
  }) {
    return TestViewConfiguration._(size, window ?? ui.window);
  }

1718
  TestViewConfiguration._(Size size, ui.FlutterView window)
1719 1720
    : _paintMatrix = _getMatrix(size, window.devicePixelRatio, window),
      _hitTestMatrix = _getMatrix(size, 1.0, window),
1721 1722
      super(size: size);

1723
  static Matrix4 _getMatrix(Size size, double devicePixelRatio, ui.FlutterView window) {
1724 1725 1726
    final double inverseRatio = devicePixelRatio / window.devicePixelRatio;
    final double actualWidth = window.physicalSize.width * inverseRatio;
    final double actualHeight = window.physicalSize.height * inverseRatio;
1727 1728
    final double desiredWidth = size.width;
    final double desiredHeight = size.height;
1729 1730 1731 1732 1733 1734 1735 1736 1737 1738
    double scale, shiftX, shiftY;
    if ((actualWidth / actualHeight) > (desiredWidth / desiredHeight)) {
      scale = actualHeight / desiredHeight;
      shiftX = (actualWidth - desiredWidth * scale) / 2.0;
      shiftY = 0.0;
    } else {
      scale = actualWidth / desiredWidth;
      shiftX = 0.0;
      shiftY = (actualHeight - desiredHeight * scale) / 2.0;
    }
1739 1740 1741
    final Matrix4 matrix = Matrix4.compose(
      Vector3(shiftX, shiftY, 0.0), // translation
      Quaternion.identity(), // rotation
1742
      Vector3(scale, scale, 1.0), // scale
1743
    );
1744
    return matrix;
1745 1746
  }

1747 1748
  final Matrix4 _paintMatrix;
  final Matrix4 _hitTestMatrix;
1749 1750

  @override
1751
  Matrix4 toMatrix() => _paintMatrix.clone();
1752

1753 1754 1755
  /// Provides the transformation matrix that converts coordinates in the test
  /// coordinate space to coordinates in logical pixels on the real display.
  ///
1756
  /// This is essentially the same as [toMatrix] but ignoring the device pixel
1757 1758 1759 1760 1761
  /// ratio.
  ///
  /// This is useful because pointers are described in logical pixels, as
  /// opposed to graphics which are expressed in physical pixels.
  Matrix4 toHitTestMatrix() => _hitTestMatrix.clone();
1762 1763 1764 1765 1766

  @override
  String toString() => 'TestViewConfiguration';
}

1767 1768 1769 1770
const int _kPointerDecay = -2;

class _LiveTestPointerRecord {
  _LiveTestPointerRecord(
1771
    this.pointer,
1772
    this.position,
1773
  ) : color = HSVColor.fromAHSV(0.8, (35.0 * pointer) % 360.0, 1.0, 1.0).toColor(),
1774 1775 1776
      decay = 1;
  final int pointer;
  final Color color;
1777
  Offset position;
1778 1779 1780 1781 1782
  int decay; // >0 means down, <0 means up, increases by one each time, removed at 0
}

class _LiveTestRenderView extends RenderView {
  _LiveTestRenderView({
1783 1784
    required ViewConfiguration configuration,
    required this.onNeedPaint,
1785
    required ui.FlutterView window,
1786
  }) : super(configuration: configuration, window: window);
1787

1788
  @override
1789
  TestViewConfiguration get configuration => super.configuration as TestViewConfiguration;
1790
  @override
1791
  set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
1792

1793 1794
  final VoidCallback onNeedPaint;

1795 1796
  final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};

1797
  TextPainter? _label;
1798
  static const TextStyle _labelStyle = TextStyle(
1799 1800 1801 1802 1803 1804 1805 1806 1807
    fontFamily: 'sans-serif',
    fontSize: 10.0,
  );
  void _setDescription(String value) {
    assert(value != null);
    if (value.isEmpty) {
      _label = null;
      return;
    }
Ian Hickson's avatar
Ian Hickson committed
1808
    // TODO(ianh): Figure out if the test name is actually RTL.
1809
    _label ??= TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr);
1810 1811
    _label!.text = TextSpan(text: value, style: _labelStyle);
    _label!.layout();
1812
    onNeedPaint();
1813 1814
  }

1815
  @override
1816
  bool hitTest(HitTestResult result, { required Offset position }) {
1817 1818
    final Matrix4 transform = configuration.toHitTestMatrix();
    final double det = transform.invert();
1819 1820 1821 1822 1823
    assert(det != 0.0);
    position = MatrixUtils.transformPoint(transform, position);
    return super.hitTest(result, position: position);
  }

1824 1825 1826 1827 1828 1829
  @override
  void paint(PaintingContext context, Offset offset) {
    assert(offset == Offset.zero);
    super.paint(context, offset);
    if (_pointers.isNotEmpty) {
      final double radius = configuration.size.shortestSide * 0.05;
1830 1831
      final Path path = Path()
        ..addOval(Rect.fromCircle(center: Offset.zero, radius: radius))
1832 1833 1834 1835 1836
        ..moveTo(0.0, -radius * 2.0)
        ..lineTo(0.0, radius * 2.0)
        ..moveTo(-radius * 2.0, 0.0)
        ..lineTo(radius * 2.0, 0.0);
      final Canvas canvas = context.canvas;
1837
      final Paint paint = Paint()
1838 1839 1840
        ..strokeWidth = radius / 10.0
        ..style = PaintingStyle.stroke;
      bool dirty = false;
1841
      for (final int pointer in _pointers.keys) {
1842
        final _LiveTestPointerRecord record = _pointers[pointer]!;
1843
        paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0);
1844
        canvas.drawPath(path.shift(record.position), paint);
1845 1846 1847 1848 1849 1850
        if (record.decay < 0)
          dirty = true;
        record.decay += 1;
      }
      _pointers
        .keys
1851
        .where((int pointer) => _pointers[pointer]!.decay == 0)
1852
        .toList()
1853
        .forEach(_pointers.remove);
1854 1855
      if (dirty && onNeedPaint != null)
        scheduleMicrotask(onNeedPaint);
1856
    }
1857
    _label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
1858 1859
  }
}