diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index f58b3ad069..44a6b3189e 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -24,10 +24,6 @@ import 'widget_inspector.dart'; export 'dart:ui' show AppLifecycleState, Locale; -// Examples can assume: -// late FlutterView myFlutterView; -// class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) => const Placeholder(); } - /// Interface for classes that register with the Widgets layer binding. /// /// This can be used by any class, not just widgets. It provides an interface @@ -1156,20 +1152,14 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB } } -/// Inflate the given widget and attach it to the view. -/// -// TODO(goderbauer): Update the paragraph below to include the Window widget once that exists. -/// The [runApp] method renders the provided `app` widget into the -/// [PlatformDispatcher.implicitView] by wrapping it in a [View] widget, which -/// will bootstrap the render tree for the app. Apps that want to control which -/// [FlutterView] they render into can use [runWidget] instead. +/// Inflate the given widget and attach it to the screen. /// /// The widget is given constraints during layout that force it to fill the -/// entire view. If you wish to align your widget to one side of the view +/// entire screen. If you wish to align your widget to one side of the screen /// (e.g., the top), consider using the [Align] widget. If you wish to center /// your widget, you can also use the [Center] widget. /// -/// Calling [runApp] again will detach the previous root widget from the view +/// Calling [runApp] again will detach the previous root widget from the screen /// and attach the given widget in its place. The new widget tree is compared /// against the previous widget tree and any differences are applied to the /// underlying render tree, similar to what happens when a [StatefulWidget] @@ -1177,7 +1167,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB /// /// Initializes the binding using [WidgetsFlutterBinding] if necessary. /// -/// {@template flutter.widgets.runApp.shutdown} /// ## Application shutdown /// /// This widget tree is not torn down when the application shuts down, because @@ -1189,32 +1178,29 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB /// Applications are responsible for ensuring that they are well-behaved /// even in the face of a rapid unscheduled termination. /// -/// To listen for platform shutdown messages (and other lifecycle changes), -/// consider the [AppLifecycleListener] API. -/// {@endtemplate} -/// /// To artificially cause the entire widget tree to be disposed, consider /// calling [runApp] with a widget such as [SizedBox.shrink]. /// -/// {@template flutter.widgets.runApp.dismissal} +/// To listen for platform shutdown messages (and other lifecycle changes), +/// consider the [AppLifecycleListener] API. +/// /// ## Dismissing Flutter UI via platform native methods /// +/// {@template flutter.widgets.runApp.dismissal} /// An application may have both Flutter and non-Flutter UI in it. If the /// application calls non-Flutter methods to remove Flutter based UI such as /// platform native API to manipulate the platform native navigation stack, /// the framework does not know if the developer intends to eagerly free /// resources or not. The widget tree remains mounted and ready to render /// as soon as it is displayed again. -/// {@endtemplate} /// /// To release resources more eagerly, establish a [platform channel](https://flutter.dev/platform-channels/) /// and use it to call [runApp] with a widget such as [SizedBox.shrink] when /// the framework should dispose of the active widget tree. +/// {@endtemplate} /// /// See also: /// -/// * [runWidget], which bootstraps a widget tree without assuming the -/// [FlutterView] into which it will be rendered. /// * [WidgetsBinding.attachRootWidget], which creates the root widget for the /// widget hierarchy. /// * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root @@ -1223,75 +1209,9 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB /// ensure the widget, element, and render trees are all built. void runApp(Widget app) { final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); - _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp'); -} - -/// Inflate the given widget and bootstrap the widget tree. -/// -// TODO(goderbauer): Update the paragraph below to include the Window widget once that exists. -/// Unlike [runApp], this method does not define a [FlutterView] into which the -/// provided `app` widget is rendered into. It is up to the caller to include at -/// least one [View] widget in the provided `app` widget that will bootstrap a -/// render tree and define the [FlutterView] into which content is rendered. -/// [RenderObjectWidget]s without an ancestor [View] widget will result in an -/// exception. Apps that want to render into the default view without dealing -/// with view management should consider calling [runApp] instead. -/// -/// {@tool snippet} -/// The sample shows how to utilize [runWidget] to specify the [FlutterView] -/// into which the `MyApp` widget will be drawn: -/// -/// ```dart -/// runWidget( -/// View( -/// view: myFlutterView, -/// child: const MyApp(), -/// ), -/// ); -/// ``` -/// {@end-tool} -/// -/// Calling [runWidget] again will detach the previous root widget and attach -/// the given widget in its place. The new widget tree is compared against the -/// previous widget tree and any differences are applied to the underlying -/// render tree, similar to what happens when a [StatefulWidget] rebuilds after -/// calling [State.setState]. -/// -/// Initializes the binding using [WidgetsFlutterBinding] if necessary. -/// -/// {@macro flutter.widgets.runApp.shutdown} -/// -/// To artificially cause the entire widget tree to be disposed, consider -/// calling [runWidget] with a [ViewCollection] that does not specify any -/// [ViewCollection.views]. -/// -/// ## Dismissing Flutter UI via platform native methods -/// -/// {@macro flutter.widgets.runApp.dismissal} -/// -/// To release resources more eagerly, establish a [platform channel](https://flutter.dev/platform-channels/) -/// and use it to remove the [View] whose widget resources should be released -/// from the `app` widget tree provided to [runWidget]. -/// -/// See also: -/// -/// * [runApp], which bootstraps a widget tree and renders it into a default -/// [FlutterView]. -/// * [WidgetsBinding.attachRootWidget], which creates the root widget for the -/// widget hierarchy. -/// * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root -/// element for the element hierarchy. -/// * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to -/// ensure the widget, element, and render trees are all built. -void runWidget(Widget app) { - final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); - _runWidget(app, binding, 'runWidget'); -} - -void _runWidget(Widget app, WidgetsBinding binding, String debugEntryPoint) { - assert(binding.debugCheckZone(debugEntryPoint)); + assert(binding.debugCheckZone('runApp')); binding - ..scheduleAttachRootWidget(app) + ..scheduleAttachRootWidget(binding.wrapWithDefaultView(app)) ..scheduleWarmUpFrame(); } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 8b706a638f..2e2c711310 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1325,10 +1325,9 @@ abstract class State with Diagnosticable { /// To listen for platform shutdown messages (and other lifecycle changes), /// consider the [AppLifecycleListener] API. /// - /// {@macro flutter.widgets.runApp.dismissal} + /// ### Dismissing Flutter UI via platform native methods /// - /// See the method used to bootstrap the app (e.g. [runApp] or [runWidget]) - /// for suggestions on how to release resources more eagerly. + /// {@macro flutter.widgets.runApp.dismissal} /// /// See also: /// @@ -6971,16 +6970,9 @@ abstract class RenderTreeRootElement extends RenderObjectElement { 'however, expects that a child will be attached.', ), ErrorHint( - 'Try moving the subtree that contains the ${toStringShort()} widget ' - 'to a location where it is not expected to attach its RenderObject ' - 'to a parent. This could mean moving the subtree into the view ' - 'property of a "ViewAnchor" widget or - if the subtree is the root of ' - 'your widget tree - passing it to "runWidget" instead of "runApp".', - ), - ErrorHint( - 'If you are seeing this error in a test and the subtree containing ' - 'the ${toStringShort()} widget is passed to "WidgetTester.pumpWidget", ' - 'consider setting the "wrapWithView" parameter of that method to false.' + 'Try moving the subtree that contains the ${toStringShort()} widget into the ' + 'view property of a ViewAnchor widget or to the root of the widget tree, where ' + 'it is not expected to attach its RenderObject to a parent.', ), ], ); diff --git a/packages/flutter/test/rendering/view_constraints_test.dart b/packages/flutter/test/rendering/view_constraints_test.dart index ff858de894..b5812819a7 100644 --- a/packages/flutter/test/rendering/view_constraints_test.dart +++ b/packages/flutter/test/rendering/view_constraints_test.dart @@ -13,9 +13,9 @@ void main() { ..physicalConstraints = ViewConstraints.tight(const Size(1008.0, 2198.0)) ..devicePixelRatio = 1.912500023841858; - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: view, child: const SizedBox(), ), @@ -35,3 +35,9 @@ class FlutterViewSpy extends TestFlutterView { sizes.add(size); } } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/widgets/debug_test.dart b/packages/flutter/test/widgets/debug_test.dart index 7df9c7a3b5..8ae59d0aa0 100644 --- a/packages/flutter/test/widgets/debug_test.dart +++ b/packages/flutter/test/widgets/debug_test.dart @@ -98,9 +98,9 @@ void main() { testWidgets('debugCheckHasMediaQuery control test', (WidgetTester tester) async { // Cannot use tester.pumpWidget here because it wraps the widget in a View, // which introduces a MediaQuery ancestor. - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { late FlutterError error; try { @@ -343,3 +343,9 @@ void main() { expect(renderObject.debugLayer?.debugCreator, isNotNull); }); } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index e33975262b..1c6315378f 100644 --- a/packages/flutter/test/widgets/media_query_test.dart +++ b/packages/flutter/test/widgets/media_query_test.dart @@ -47,9 +47,9 @@ void main() { late final FlutterError error; // Cannot use tester.pumpWidget here because it wraps the widget in a View, // which introduces a MediaQuery ancestor. - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { try { MediaQuery.of(context); @@ -111,9 +111,9 @@ void main() { bool tested = false; // Cannot use tester.pumpWidget here because it wraps the widget in a View, // which introduces a MediaQuery ancestor. - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { final MediaQueryData? data = MediaQuery.maybeOf(context); expect(data, isNull); @@ -287,9 +287,9 @@ void main() { late MediaQueryData data; MediaQueryData? outerData; - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { outerData = MediaQuery.maybeOf(context); return MediaQuery.fromView( @@ -342,9 +342,9 @@ void main() { late MediaQueryData data; MediaQueryData? outerData; int rebuildCount = 0; - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { outerData = MediaQuery.maybeOf(context); return MediaQuery.fromView( @@ -1531,3 +1531,9 @@ void main() { ] )); } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/widgets/multi_view_binding_test.dart b/packages/flutter/test/widgets/multi_view_binding_test.dart index 25acc05e08..6c7c1d09de 100644 --- a/packages/flutter/test/widgets/multi_view_binding_test.dart +++ b/packages/flutter/test/widgets/multi_view_binding_test.dart @@ -26,7 +26,7 @@ void main() { final RootWidget rootWidget = RootWidget( child: View( - view: FakeFlutterView(tester.view), + view: tester.view, child: const ColoredBox(color: Colors.orange), ), ); @@ -36,52 +36,4 @@ void main() { expect(tester.binding.rootElement!.widget, equals(rootWidget)); expect(tester.element(find.byType(ColoredBox)).owner, equals(tester.binding.buildOwner)); }); - - testWidgets('runApp throws if given a View', (WidgetTester tester) async { - runApp( - View( - view: FakeFlutterView(tester.view), - child: const SizedBox.shrink(), - ), - ); - expect( - tester.takeException(), - isFlutterError.having( - (FlutterError e) => e.message, - 'message', - contains('passing it to "runWidget" instead of "runApp"'), - ), - ); - }); - - testWidgets('runWidget throws if not given a View', (WidgetTester tester) async { - runWidget(const SizedBox.shrink()); - expect( - tester.takeException(), - isFlutterError.having( - (FlutterError e) => e.message, - 'message', - contains('Try wrapping your widget in a View widget'), - ), - ); - }); - - testWidgets('runWidget does not throw if given a View', (WidgetTester tester) async { - runWidget( - View( - view: FakeFlutterView(tester.view), - child: const SizedBox.shrink(), - ), - ); - expect(find.byType(View), findsOne); - }); - - testWidgets('can call runWidget with an empty ViewCollection', (WidgetTester tester) async { - runWidget(const ViewCollection(views: [])); - expect(find.byType(ViewCollection), findsOne); - }); -} - -class FakeFlutterView extends TestFlutterView { - FakeFlutterView(TestFlutterView view) : super(view: view, display: view.display, platformDispatcher: view.platformDispatcher); } diff --git a/packages/flutter/test/widgets/multi_view_tree_updates_test.dart b/packages/flutter/test/widgets/multi_view_tree_updates_test.dart index 210346ec48..a30b350213 100644 --- a/packages/flutter/test/widgets/multi_view_tree_updates_test.dart +++ b/packages/flutter/test/widgets/multi_view_tree_updates_test.dart @@ -15,9 +15,9 @@ void main() { child: const TestWidget(), ); - await tester.pumpWidget( - wrapWithView: false, - widget, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: widget, ); expect(find.text('Hello'), findsOneWidget); @@ -29,9 +29,9 @@ void main() { expect(find.text('World'), findsOneWidget); expect(tester.renderObject(find.byType(Text)).text.toPlainText(), 'World'); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [widget], ), ); @@ -40,9 +40,9 @@ void main() { expect(tester.renderObject(find.byType(Text)).text.toPlainText(), 'World'); tester.state(find.byType(TestWidget)).text = 'FooBar'; - await tester.pumpWidget( - wrapWithView: false, - widget, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: widget, ); expect(find.text('World'), findsNothing); expect(find.text('FooBar'), findsOneWidget); @@ -65,9 +65,9 @@ void main() { child: TestWidget(key: key2), ); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [view1, view2], ), ); @@ -91,9 +91,9 @@ void main() { expect(renderParagraphTexts(), ['Guten', 'Abend']); tester.state(find.byKey(key2)).text = 'Morgen'; - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [view1, ViewCollection(views: [view2])], ), ); @@ -202,3 +202,9 @@ class TestWidgetState extends State { return Text(text, textDirection: TextDirection.ltr); } } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/widgets/tree_shape_test.dart b/packages/flutter/test/widgets/tree_shape_test.dart index 86d977ddcb..7c9576d7ee 100644 --- a/packages/flutter/test/widgets/tree_shape_test.dart +++ b/packages/flutter/test/widgets/tree_shape_test.dart @@ -13,9 +13,9 @@ import 'multi_view_testing.dart'; void main() { testWidgets('Providing a RenderObjectWidget directly to the RootWidget fails', (WidgetTester tester) async { // No render tree exists to attach the RenderObjectWidget to. - await tester.pumpWidget( - wrapWithView: false, - const ColoredBox(color: Colors.red), + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: const ColoredBox(color: Colors.red), ); expect(tester.takeException(), isFlutterError.having( @@ -31,18 +31,18 @@ void main() { color: Colors.red, ); - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, child: globalKeyedWidget, ), ); expect(tester.takeException(), isNull); - await tester.pumpWidget( - wrapWithView: false, - globalKeyedWidget, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: globalKeyedWidget, ); expect(tester.takeException(), isFlutterError.having( @@ -91,15 +91,15 @@ void main() { child: const ColoredBox(color: Colors.red), ); - await tester.pumpWidget( - wrapWithView: false, - globalKeyedView, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: globalKeyedView, ); expect(tester.takeException(), isNull); - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, child: globalKeyedView, ), @@ -155,9 +155,9 @@ void main() { }); testWidgets('ViewAnchor cannot be used at the top of the widget tree (outside of View)', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - const ViewAnchor( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: const ViewAnchor( child: SizedBox(), ), ); @@ -175,18 +175,18 @@ void main() { child: const SizedBox(), ); - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, child: globalKeyedViewAnchor, ), ); expect(tester.takeException(), isNull); - await tester.pumpWidget( - wrapWithView: false, - globalKeyedViewAnchor, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: globalKeyedViewAnchor, ); expect(tester.takeException(), isFlutterError.having( @@ -197,9 +197,9 @@ void main() { }); testWidgets('View can be used at the top of the widget tree', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, child: Container(), ), @@ -214,9 +214,9 @@ void main() { child: const ColoredBox(color: Colors.red), ); - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, child: ViewAnchor( view: globalKeyView, // This one has trouble when deactivating @@ -228,9 +228,9 @@ void main() { expect(find.byType(SizedBox), findsOneWidget); expect(find.byType(ColoredBox), findsOneWidget); - await tester.pumpWidget( - wrapWithView: false, - globalKeyView, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: globalKeyView, ); expect(tester.takeException(), isNull); expect(find.byType(SizedBox), findsNothing); @@ -238,9 +238,9 @@ void main() { }); testWidgets('ViewCollection can be used at the top of the widget tree', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: tester.view, @@ -291,9 +291,9 @@ void main() { }); testWidgets('ViewCollection cannot have render object widgets as children', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - const ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: const ViewCollection( views: [ ColoredBox(color: Colors.red), ], @@ -319,9 +319,9 @@ void main() { child: const ColoredBox(color: Colors.red), ); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ greenView, ViewCollection( @@ -335,9 +335,9 @@ void main() { expect(tester.takeException(), isNull); expect(find.byType(ColoredBox), findsNWidgets(2)); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ redView, ViewCollection( @@ -371,9 +371,9 @@ void main() { return result; } - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: greenView, @@ -412,9 +412,9 @@ void main() { expect(leafRenderObject[redView.viewId], isNot(isA())); // Move the child. - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: greenView, @@ -476,9 +476,9 @@ void main() { return result; } - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: greenView, @@ -517,9 +517,9 @@ void main() { expect(leafRenderObject[greenView.viewId], isNot(isA())); // Move the child. - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: greenView, @@ -571,9 +571,9 @@ void main() { key: GlobalKey(), ); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( key: greenKey, @@ -610,9 +610,9 @@ void main() { final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!)); // Move the child and remove its view. - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( key: greenKey, @@ -652,9 +652,9 @@ void main() { key: GlobalKey(), ); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( key: greenKey, @@ -691,9 +691,9 @@ void main() { final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!)); // Move the child and remove its view. - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( key: redKey, @@ -1009,9 +1009,9 @@ void main() { final FlutterView redView = tester.view; final FlutterView greenView = FakeView(tester.view); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: redView, @@ -1029,9 +1029,9 @@ void main() { expect(findsColoredBox(Colors.red), findsOneWidget); final RenderObject box = tester.renderObject(findsColoredBox(Colors.green)); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: redView, @@ -1052,17 +1052,17 @@ void main() { child: const SizedBox(), ); - await tester.pumpWidget( - wrapWithView: false, - view, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: view, ); final RenderObject renderView = tester.renderObject(find.byType(View)); final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox)); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [view], ), ); @@ -1070,9 +1070,9 @@ void main() { expect(tester.renderObject(find.byType(View)), same(renderView)); expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox)); - await tester.pumpWidget( - wrapWithView: false, - view, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: view, ); expect(tester.renderObject(find.byType(View)), same(renderView)); @@ -1114,9 +1114,9 @@ void main() { child: const SizedBox(), ) ); - await tester.pumpWidget( - wrapWithView: false, - view, + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: view, ); final RenderObject renderSemantics = tester.renderObject(find.bySemanticsLabel('Hello')); @@ -1124,9 +1124,9 @@ void main() { expect(semantics.id, 1); expect(renderSemantics.debugSemantics, same(semantics)); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ view, ], @@ -1144,3 +1144,9 @@ void main() { Finder findsColoredBox(Color color) { return find.byWidgetPredicate((Widget widget) => widget is ColoredBox && widget.color == color); } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/widgets/view_test.dart b/packages/flutter/test/widgets/view_test.dart index 547dc560cc..7f93176504 100644 --- a/packages/flutter/test/widgets/view_test.dart +++ b/packages/flutter/test/widgets/view_test.dart @@ -77,9 +77,9 @@ void main() { PipelineOwner? outsideParent; PipelineOwner? insideParent; - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { outsideView = View.maybeOf(context); outsideParent = View.pipelineOwnerOf(context); @@ -114,9 +114,9 @@ void main() { }); testWidgets('cannot have multiple views with same FlutterView', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: tester.view, @@ -224,9 +224,9 @@ void main() { }); testWidgets('visitChildren of ViewCollection visits all children', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: tester.view, @@ -250,9 +250,9 @@ void main() { }); expect(children, hasLength(3)); - await tester.pumpWidget( - wrapWithView: false, - ViewCollection( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: ViewCollection( views: [ View( view: tester.view, @@ -271,9 +271,9 @@ void main() { group('renderObject getter', () { testWidgets('ancestors of view see RenderView as renderObject', (WidgetTester tester) async { late BuildContext builderContext; - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { builderContext = context; return View( @@ -293,9 +293,9 @@ void main() { testWidgets('ancestors of ViewCollection get null for renderObject', (WidgetTester tester) async { late BuildContext builderContext; - await tester.pumpWidget( - wrapWithView: false, - Builder( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { builderContext = context; return ViewCollection( @@ -345,9 +345,9 @@ void main() { }); testWidgets('correctly switches between view configurations', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner, deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView, @@ -359,9 +359,9 @@ void main() { expect(renderView.owner, same(tester.binding.pipelineOwner)); expect(tester.renderObject(find.byType(SizedBox)).owner, same(tester.binding.pipelineOwner)); - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, child: const SizedBox(), ), @@ -371,9 +371,9 @@ void main() { expect(renderView.owner, isNot(same(tester.binding.pipelineOwner))); expect(tester.renderObject(find.byType(SizedBox)).owner, isNot(same(tester.binding.pipelineOwner))); - await tester.pumpWidget( - wrapWithView: false, - View( + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( view: tester.view, deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner, deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView, @@ -513,6 +513,12 @@ void main() { }); } +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} + class SpyRenderWidget extends SizedBox { const SpyRenderWidget({super.key, required this.label, required this.log, super.child}); diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index cf1412b670..7d8799cda5 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -581,23 +581,15 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// ``` /// {@end-tool} /// - /// By default, the provided `widget` is rendered into [WidgetTester.view], - /// whose properties tests can modify to simulate different scenarios (e.g. - /// running on a large/small screen). Tests that want to control the - /// [FlutterView] into which content is rendered can set `wrapWithView` to - /// false and use [View] widgets in the provided `widget` tree to specify the - /// desired [FlutterView]s. - /// /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how /// this method works when the test is run with `flutter run`. Future pumpWidget( Widget widget, { Duration? duration, EnginePhase phase = EnginePhase.sendSemanticsUpdate, - bool wrapWithView = true, }) { return TestAsyncUtils.guard(() { - binding.attachRootWidget(wrapWithView ? binding.wrapWithDefaultView(widget) : widget); + binding.attachRootWidget(binding.wrapWithDefaultView(widget)); binding.scheduleFrame(); return binding.pump(duration, phase); }); diff --git a/packages/flutter_test/test/multi_view_accessibility_test.dart b/packages/flutter_test/test/multi_view_accessibility_test.dart index 3cd5596da0..f93ba4b756 100644 --- a/packages/flutter_test/test/multi_view_accessibility_test.dart +++ b/packages/flutter_test/test/multi_view_accessibility_test.dart @@ -112,8 +112,7 @@ Future pumpViews({required WidgetTester tester, required List vie ), ]; - return tester.pumpWidget( - wrapWithView: false, + tester.binding.attachRootWidget( Directionality( textDirection: TextDirection.ltr, child: ViewCollection( @@ -121,4 +120,6 @@ Future pumpViews({required WidgetTester tester, required List vie ), ), ); + tester.binding.scheduleFrame(); + return tester.binding.pump(); } diff --git a/packages/flutter_test/test/multi_view_controller_test.dart b/packages/flutter_test/test/multi_view_controller_test.dart index fc9ec17609..a9488933d7 100644 --- a/packages/flutter_test/test/multi_view_controller_test.dart +++ b/packages/flutter_test/test/multi_view_controller_test.dart @@ -208,8 +208,7 @@ Future pumpViews({required WidgetTester tester}) { ), ]; - return tester.pumpWidget( - wrapWithView: false, + tester.binding.attachRootWidget( Directionality( textDirection: TextDirection.ltr, child: ViewCollection( @@ -217,4 +216,6 @@ Future pumpViews({required WidgetTester tester}) { ), ), ); + tester.binding.scheduleFrame(); + return tester.binding.pump(); } diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 8c52c0b60d..846994a053 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -638,56 +638,6 @@ void main() { .checkMockMessageHandler(SystemChannels.accessibility.name, null), isTrue); }); }); - - testWidgets('wrapWithView: false does not include View', (WidgetTester tester) async { - FlutterView? flutterView; - View? view; - int builderCount = 0; - await tester.pumpWidget( - wrapWithView: false, - Builder( - builder: (BuildContext context) { - builderCount++; - flutterView = View.maybeOf(context); - view = context.findAncestorWidgetOfExactType(); - return const ViewCollection(views: []); - }, - ), - ); - - expect(builderCount, 1); - expect(view, isNull); - expect(flutterView, isNull); - expect(find.byType(View), findsNothing); - }); - - testWidgets('passing a view to pumpWidget with wrapWithView: true throws', (WidgetTester tester) async { - await tester.pumpWidget( - View( - view: tester.view, - child: const SizedBox.shrink(), - ), - ); - expect( - tester.takeException(), - isFlutterError.having( - (FlutterError e) => e.message, - 'message', - contains('consider setting the "wrapWithView" parameter of that method to false'), - ), - ); - }); - - testWidgets('can pass a View to pumpWidget when wrapWithView: false', (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithView: false, - View( - view: tester.view, - child: const SizedBox.shrink(), - ), - ); - expect(find.byType(View), findsOne); - }); } class FakeMatcher extends AsyncMatcher {