|
| 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 | +import 'package:flutter/foundation.dart'; |
| 6 | +import 'package:flutter/material.dart'; |
| 7 | +import 'package:flutter_test/flutter_test.dart'; |
| 8 | + |
| 9 | +// This file is for tests for WidgetsBinding that require a `LiveTestWidgetsFlutterBinding`. |
| 10 | +void main() { |
| 11 | + LiveTestWidgetsFlutterBinding(); |
| 12 | + testWidgets('ReportTiming callback records the sendFramesToEngine when it was scheduled', (WidgetTester tester) async { |
| 13 | + // Addresses https://github.com/flutter/flutter/issues/144261 |
| 14 | + // This test needs LiveTestWidgetsFlutterBinding for multiple reasons. |
| 15 | + // |
| 16 | + // First, this was the environment that this bug was discovered. |
| 17 | + // |
| 18 | + // Second, unlike `AutomatedTestWidgetsFlutterBinding`, which overrides |
| 19 | + // `scheduleWarmUpFrame` to execute the handlers synchronously, |
| 20 | + // `LiveTestWidgetsFlutterBinding` still calls them asynchronously. This |
| 21 | + // allows `runApp`, which also schedules a warm-up frame, to bind a widget |
| 22 | + // without rendering a frame, which is needed to for `deferFirstFrame` to |
| 23 | + // take effect. |
| 24 | + |
| 25 | + // Before `testWidgets` executes the test body, it pumps a frame with a |
| 26 | + // fixed dummy widget, then calls `resetFirstFrameSent`. The pumped frame |
| 27 | + // schedules a reportTiming call that has yet to arrive. |
| 28 | + // |
| 29 | + // This puts the test in an inconsistent state: a reportTiming callback is |
| 30 | + // supposed to happen only after a frame is rendered, but due to |
| 31 | + // `resetFirstFrameSent`, the framework thinks no frames have been rendered. |
| 32 | + |
| 33 | + expect(tester.binding.sendFramesToEngine, true); |
| 34 | + // Push the widget with `runApp` instead of `tester.pump`, avoiding |
| 35 | + // rendering a frame, which is needed for `deferFirstFrame` later to work. |
| 36 | + runApp(const DummyWidget()); |
| 37 | + // Verify that no widget tree is built and nothing is rendered. |
| 38 | + expect(find.text('First frame'), findsNothing); |
| 39 | + // Defer the first frame, making `sendFramesToEngine` false, so that widget |
| 40 | + // tree will be built but not sent to the engine. |
| 41 | + tester.binding.deferFirstFrame(); |
| 42 | + expect(tester.binding.sendFramesToEngine, false); |
| 43 | + // Pump a frame, letting the reportTiming callback to run. If the |
| 44 | + // reportTiming callback were to assume that `sendFramesToEngine` is true, |
| 45 | + // the callback would crash. |
| 46 | + await tester.pump(const Duration(milliseconds: 1)); |
| 47 | + await tester.binding.waitUntilFirstFrameRasterized; |
| 48 | + expect(find.text('First frame'), findsOne); |
| 49 | + }, skip: kIsWeb); // [intended] Web doesn't use LiveTestWidgetsFlutterBinding |
| 50 | +} |
| 51 | + |
| 52 | +class DummyWidget extends StatelessWidget { |
| 53 | + const DummyWidget({super.key}); |
| 54 | + |
| 55 | + @override |
| 56 | + Widget build(BuildContext context) { |
| 57 | + return const Directionality( |
| 58 | + textDirection: TextDirection.ltr, |
| 59 | + child: Center( |
| 60 | + child: Text('First frame'), |
| 61 | + ), |
| 62 | + ); |
| 63 | + } |
| 64 | +} |
0 commit comments