Reverts "Add runWidget to bootstrap a widget tree without a default View" (#142339)

Reverts flutter/flutter#141484
Initiated by: eliasyishak
This change reverts the following previous change:
Original Description:
The existing `runApp` bootstraps the widget tree and renders the provided widget into the default view (which is currently the implicit View from `PlatformDispatcher.implicitView` and - in the future - may be a default-created window). Apps, that want more control over the View they are rendered in, need a new way to bootstrap the widget tree: `runWidget`. It does not make any assumptions about the View the provided widget is rendered into. Instead, it is up to the caller to include a View widget in the provided widget tree that specifies where content should be rendered. In the future, this may enable developers to create a custom window for their app instead of relying on the default-created one.
This commit is contained in:
auto-submit[bot] 2024-01-26 21:06:27 +00:00 committed by GitHub
parent 81215322c7
commit 114261a63a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 209 additions and 365 deletions

View file

@ -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();
}

View file

@ -1325,10 +1325,9 @@ abstract class State<T extends StatefulWidget> 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.',
),
],
);

View file

@ -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<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -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<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -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<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -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: <Widget>[]));
expect(find.byType(ViewCollection), findsOne);
});
}
class FakeFlutterView extends TestFlutterView {
FakeFlutterView(TestFlutterView view) : super(view: view, display: view.display, platformDispatcher: view.platformDispatcher);
}

View file

@ -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<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World');
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[widget],
),
);
@ -40,9 +40,9 @@ void main() {
expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World');
tester.state<TestWidgetState>(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: <Widget>[view1, view2],
),
);
@ -91,9 +91,9 @@ void main() {
expect(renderParagraphTexts(), <String>['Guten', 'Abend']);
tester.state<TestWidgetState>(find.byKey(key2)).text = 'Morgen';
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[view1, ViewCollection(views: <Widget>[view2])],
),
);
@ -202,3 +202,9 @@ class TestWidgetState extends State<TestWidget> {
return Text(text, textDirection: TextDirection.ltr);
}
}
Future<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
redView,
ViewCollection(
@ -371,9 +371,9 @@ void main() {
return result;
}
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[
View(
view: greenView,
@ -412,9 +412,9 @@ void main() {
expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>()));
// Move the child.
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[
View(
view: greenView,
@ -476,9 +476,9 @@ void main() {
return result;
}
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[
View(
view: greenView,
@ -517,9 +517,9 @@ void main() {
expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>()));
// Move the child.
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[
View(
view: greenView,
@ -571,9 +571,9 @@ void main() {
key: GlobalKey(),
);
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[
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: <Widget>[
View(
key: greenKey,
@ -652,9 +652,9 @@ void main() {
key: GlobalKey(),
);
await tester.pumpWidget(
wrapWithView: false,
ViewCollection(
await pumpWidgetWithoutViewWrapper(
tester: tester,
widget: ViewCollection(
views: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[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: <Widget>[
view,
],
@ -1144,3 +1144,9 @@ void main() {
Finder findsColoredBox(Color color) {
return find.byWidgetPredicate((Widget widget) => widget is ColoredBox && widget.color == color);
}
Future<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -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: <Widget>[
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: <Widget>[
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: <Widget>[
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<void> 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});

View file

@ -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<void> pumpWidget(
Widget widget, {
Duration? duration,
EnginePhase phase = EnginePhase.sendSemanticsUpdate,
bool wrapWithView = true,
}) {
return TestAsyncUtils.guard<void>(() {
binding.attachRootWidget(wrapWithView ? binding.wrapWithDefaultView(widget) : widget);
binding.attachRootWidget(binding.wrapWithDefaultView(widget));
binding.scheduleFrame();
return binding.pump(duration, phase);
});

View file

@ -112,8 +112,7 @@ Future<void> pumpViews({required WidgetTester tester, required List<Widget> vie
),
];
return tester.pumpWidget(
wrapWithView: false,
tester.binding.attachRootWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ViewCollection(
@ -121,4 +120,6 @@ Future<void> pumpViews({required WidgetTester tester, required List<Widget> vie
),
),
);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -208,8 +208,7 @@ Future<void> pumpViews({required WidgetTester tester}) {
),
];
return tester.pumpWidget(
wrapWithView: false,
tester.binding.attachRootWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ViewCollection(
@ -217,4 +216,6 @@ Future<void> pumpViews({required WidgetTester tester}) {
),
),
);
tester.binding.scheduleFrame();
return tester.binding.pump();
}

View file

@ -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<View>();
return const ViewCollection(views: <Widget>[]);
},
),
);
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 {