Unverified Commit 671d8eaf authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Relands "Add runWidget to bootstrap a widget tree without a default View" (#142344)

Reverts flutter/flutter#142339

In the original change one of the tests included the same view twice which resulted in a different failure than the expected one. The second commit contains the fix for this. I don't understand how this wasn't caught presubmit on CI.
parent a5ad088f
...@@ -24,6 +24,10 @@ import 'widget_inspector.dart'; ...@@ -24,6 +24,10 @@ import 'widget_inspector.dart';
export 'dart:ui' show AppLifecycleState, Locale; 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. /// Interface for classes that register with the Widgets layer binding.
/// ///
/// This can be used by any class, not just widgets. It provides an interface /// This can be used by any class, not just widgets. It provides an interface
...@@ -1152,14 +1156,20 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -1152,14 +1156,20 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
} }
} }
/// Inflate the given widget and attach it to the screen. /// 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.
/// ///
/// The widget is given constraints during layout that force it to fill the /// The widget is given constraints during layout that force it to fill the
/// entire screen. If you wish to align your widget to one side of the screen /// entire view. If you wish to align your widget to one side of the view
/// (e.g., the top), consider using the [Align] widget. If you wish to center /// (e.g., the top), consider using the [Align] widget. If you wish to center
/// your widget, you can also use the [Center] widget. /// your widget, you can also use the [Center] widget.
/// ///
/// Calling [runApp] again will detach the previous root widget from the screen /// Calling [runApp] again will detach the previous root widget from the view
/// and attach the given widget in its place. The new widget tree is compared /// 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 /// against the previous widget tree and any differences are applied to the
/// underlying render tree, similar to what happens when a [StatefulWidget] /// underlying render tree, similar to what happens when a [StatefulWidget]
...@@ -1167,6 +1177,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -1167,6 +1177,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
/// ///
/// Initializes the binding using [WidgetsFlutterBinding] if necessary. /// Initializes the binding using [WidgetsFlutterBinding] if necessary.
/// ///
/// {@template flutter.widgets.runApp.shutdown}
/// ## Application shutdown /// ## Application shutdown
/// ///
/// This widget tree is not torn down when the application shuts down, because /// This widget tree is not torn down when the application shuts down, because
...@@ -1178,29 +1189,32 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -1178,29 +1189,32 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
/// Applications are responsible for ensuring that they are well-behaved /// Applications are responsible for ensuring that they are well-behaved
/// even in the face of a rapid unscheduled termination. /// even in the face of a rapid unscheduled termination.
/// ///
/// To artificially cause the entire widget tree to be disposed, consider
/// calling [runApp] with a widget such as [SizedBox.shrink].
///
/// To listen for platform shutdown messages (and other lifecycle changes), /// To listen for platform shutdown messages (and other lifecycle changes),
/// consider the [AppLifecycleListener] API. /// consider the [AppLifecycleListener] API.
/// {@endtemplate}
/// ///
/// ## Dismissing Flutter UI via platform native methods /// 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} /// {@template flutter.widgets.runApp.dismissal}
/// ## Dismissing Flutter UI via platform native methods
///
/// An application may have both Flutter and non-Flutter UI in it. If the /// 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 /// application calls non-Flutter methods to remove Flutter based UI such as
/// platform native API to manipulate the platform native navigation stack, /// platform native API to manipulate the platform native navigation stack,
/// the framework does not know if the developer intends to eagerly free /// the framework does not know if the developer intends to eagerly free
/// resources or not. The widget tree remains mounted and ready to render /// resources or not. The widget tree remains mounted and ready to render
/// as soon as it is displayed again. /// as soon as it is displayed again.
/// {@endtemplate}
/// ///
/// To release resources more eagerly, establish a [platform channel](https://flutter.dev/platform-channels/) /// 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 /// and use it to call [runApp] with a widget such as [SizedBox.shrink] when
/// the framework should dispose of the active widget tree. /// the framework should dispose of the active widget tree.
/// {@endtemplate}
/// ///
/// See also: /// 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 /// * [WidgetsBinding.attachRootWidget], which creates the root widget for the
/// widget hierarchy. /// widget hierarchy.
/// * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root /// * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root
...@@ -1209,9 +1223,75 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -1209,9 +1223,75 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
/// ensure the widget, element, and render trees are all built. /// ensure the widget, element, and render trees are all built.
void runApp(Widget app) { void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
assert(binding.debugCheckZone('runApp')); _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));
binding binding
..scheduleAttachRootWidget(binding.wrapWithDefaultView(app)) ..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame(); ..scheduleWarmUpFrame();
} }
......
...@@ -1325,10 +1325,11 @@ abstract class State<T extends StatefulWidget> with Diagnosticable { ...@@ -1325,10 +1325,11 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
/// To listen for platform shutdown messages (and other lifecycle changes), /// To listen for platform shutdown messages (and other lifecycle changes),
/// consider the [AppLifecycleListener] API. /// consider the [AppLifecycleListener] API.
/// ///
/// ### Dismissing Flutter UI via platform native methods
///
/// {@macro flutter.widgets.runApp.dismissal} /// {@macro flutter.widgets.runApp.dismissal}
/// ///
/// See the method used to bootstrap the app (e.g. [runApp] or [runWidget])
/// for suggestions on how to release resources more eagerly.
///
/// See also: /// See also:
/// ///
/// * [deactivate], which is called prior to [dispose]. /// * [deactivate], which is called prior to [dispose].
...@@ -6970,9 +6971,16 @@ abstract class RenderTreeRootElement extends RenderObjectElement { ...@@ -6970,9 +6971,16 @@ abstract class RenderTreeRootElement extends RenderObjectElement {
'however, expects that a child will be attached.', 'however, expects that a child will be attached.',
), ),
ErrorHint( ErrorHint(
'Try moving the subtree that contains the ${toStringShort()} widget into the ' 'Try moving the subtree that contains the ${toStringShort()} widget '
'view property of a ViewAnchor widget or to the root of the widget tree, where ' 'to a location where it is not expected to attach its RenderObject '
'it is not expected to attach its RenderObject to a parent.', '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.'
), ),
], ],
); );
......
...@@ -13,9 +13,9 @@ void main() { ...@@ -13,9 +13,9 @@ void main() {
..physicalConstraints = ViewConstraints.tight(const Size(1008.0, 2198.0)) ..physicalConstraints = ViewConstraints.tight(const Size(1008.0, 2198.0))
..devicePixelRatio = 1.912500023841858; ..devicePixelRatio = 1.912500023841858;
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: view, view: view,
child: const SizedBox(), child: const SizedBox(),
), ),
...@@ -35,9 +35,3 @@ class FlutterViewSpy extends TestFlutterView { ...@@ -35,9 +35,3 @@ class FlutterViewSpy extends TestFlutterView {
sizes.add(size); sizes.add(size);
} }
} }
Future<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}
...@@ -98,9 +98,9 @@ void main() { ...@@ -98,9 +98,9 @@ void main() {
testWidgets('debugCheckHasMediaQuery control test', (WidgetTester tester) async { testWidgets('debugCheckHasMediaQuery control test', (WidgetTester tester) async {
// Cannot use tester.pumpWidget here because it wraps the widget in a View, // Cannot use tester.pumpWidget here because it wraps the widget in a View,
// which introduces a MediaQuery ancestor. // which introduces a MediaQuery ancestor.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
late FlutterError error; late FlutterError error;
try { try {
...@@ -343,9 +343,3 @@ void main() { ...@@ -343,9 +343,3 @@ void main() {
expect(renderObject.debugLayer?.debugCreator, isNotNull); 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();
}
...@@ -47,9 +47,9 @@ void main() { ...@@ -47,9 +47,9 @@ void main() {
late final FlutterError error; late final FlutterError error;
// Cannot use tester.pumpWidget here because it wraps the widget in a View, // Cannot use tester.pumpWidget here because it wraps the widget in a View,
// which introduces a MediaQuery ancestor. // which introduces a MediaQuery ancestor.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
try { try {
MediaQuery.of(context); MediaQuery.of(context);
...@@ -111,9 +111,9 @@ void main() { ...@@ -111,9 +111,9 @@ void main() {
bool tested = false; bool tested = false;
// Cannot use tester.pumpWidget here because it wraps the widget in a View, // Cannot use tester.pumpWidget here because it wraps the widget in a View,
// which introduces a MediaQuery ancestor. // which introduces a MediaQuery ancestor.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
final MediaQueryData? data = MediaQuery.maybeOf(context); final MediaQueryData? data = MediaQuery.maybeOf(context);
expect(data, isNull); expect(data, isNull);
...@@ -287,9 +287,9 @@ void main() { ...@@ -287,9 +287,9 @@ void main() {
late MediaQueryData data; late MediaQueryData data;
MediaQueryData? outerData; MediaQueryData? outerData;
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
outerData = MediaQuery.maybeOf(context); outerData = MediaQuery.maybeOf(context);
return MediaQuery.fromView( return MediaQuery.fromView(
...@@ -342,9 +342,9 @@ void main() { ...@@ -342,9 +342,9 @@ void main() {
late MediaQueryData data; late MediaQueryData data;
MediaQueryData? outerData; MediaQueryData? outerData;
int rebuildCount = 0; int rebuildCount = 0;
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
outerData = MediaQuery.maybeOf(context); outerData = MediaQuery.maybeOf(context);
return MediaQuery.fromView( return MediaQuery.fromView(
...@@ -1531,9 +1531,3 @@ void main() { ...@@ -1531,9 +1531,3 @@ void main() {
] ]
)); ));
} }
Future<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}
...@@ -26,7 +26,7 @@ void main() { ...@@ -26,7 +26,7 @@ void main() {
final RootWidget rootWidget = RootWidget( final RootWidget rootWidget = RootWidget(
child: View( child: View(
view: tester.view, view: FakeFlutterView(tester.view),
child: const ColoredBox(color: Colors.orange), child: const ColoredBox(color: Colors.orange),
), ),
); );
...@@ -36,4 +36,52 @@ void main() { ...@@ -36,4 +36,52 @@ void main() {
expect(tester.binding.rootElement!.widget, equals(rootWidget)); expect(tester.binding.rootElement!.widget, equals(rootWidget));
expect(tester.element(find.byType(ColoredBox)).owner, equals(tester.binding.buildOwner)); 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);
} }
...@@ -15,9 +15,9 @@ void main() { ...@@ -15,9 +15,9 @@ void main() {
child: const TestWidget(), child: const TestWidget(),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: widget, widget,
); );
expect(find.text('Hello'), findsOneWidget); expect(find.text('Hello'), findsOneWidget);
...@@ -29,9 +29,9 @@ void main() { ...@@ -29,9 +29,9 @@ void main() {
expect(find.text('World'), findsOneWidget); expect(find.text('World'), findsOneWidget);
expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World'); expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World');
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[widget], views: <Widget>[widget],
), ),
); );
...@@ -40,9 +40,9 @@ void main() { ...@@ -40,9 +40,9 @@ void main() {
expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World'); expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World');
tester.state<TestWidgetState>(find.byType(TestWidget)).text = 'FooBar'; tester.state<TestWidgetState>(find.byType(TestWidget)).text = 'FooBar';
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: widget, widget,
); );
expect(find.text('World'), findsNothing); expect(find.text('World'), findsNothing);
expect(find.text('FooBar'), findsOneWidget); expect(find.text('FooBar'), findsOneWidget);
...@@ -65,9 +65,9 @@ void main() { ...@@ -65,9 +65,9 @@ void main() {
child: TestWidget(key: key2), child: TestWidget(key: key2),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[view1, view2], views: <Widget>[view1, view2],
), ),
); );
...@@ -91,9 +91,9 @@ void main() { ...@@ -91,9 +91,9 @@ void main() {
expect(renderParagraphTexts(), <String>['Guten', 'Abend']); expect(renderParagraphTexts(), <String>['Guten', 'Abend']);
tester.state<TestWidgetState>(find.byKey(key2)).text = 'Morgen'; tester.state<TestWidgetState>(find.byKey(key2)).text = 'Morgen';
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[view1, ViewCollection(views: <Widget>[view2])], views: <Widget>[view1, ViewCollection(views: <Widget>[view2])],
), ),
); );
...@@ -202,9 +202,3 @@ class TestWidgetState extends State<TestWidget> { ...@@ -202,9 +202,3 @@ class TestWidgetState extends State<TestWidget> {
return Text(text, textDirection: TextDirection.ltr); 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();
}
...@@ -13,9 +13,9 @@ import 'multi_view_testing.dart'; ...@@ -13,9 +13,9 @@ import 'multi_view_testing.dart';
void main() { void main() {
testWidgets('Providing a RenderObjectWidget directly to the RootWidget fails', (WidgetTester tester) async { testWidgets('Providing a RenderObjectWidget directly to the RootWidget fails', (WidgetTester tester) async {
// No render tree exists to attach the RenderObjectWidget to. // No render tree exists to attach the RenderObjectWidget to.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: const ColoredBox(color: Colors.red), const ColoredBox(color: Colors.red),
); );
expect(tester.takeException(), isFlutterError.having( expect(tester.takeException(), isFlutterError.having(
...@@ -31,18 +31,18 @@ void main() { ...@@ -31,18 +31,18 @@ void main() {
color: Colors.red, color: Colors.red,
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
child: globalKeyedWidget, child: globalKeyedWidget,
), ),
); );
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: globalKeyedWidget, globalKeyedWidget,
); );
expect(tester.takeException(), isFlutterError.having( expect(tester.takeException(), isFlutterError.having(
...@@ -91,15 +91,15 @@ void main() { ...@@ -91,15 +91,15 @@ void main() {
child: const ColoredBox(color: Colors.red), child: const ColoredBox(color: Colors.red),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: globalKeyedView, globalKeyedView,
); );
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
child: globalKeyedView, child: globalKeyedView,
), ),
...@@ -155,9 +155,9 @@ void main() { ...@@ -155,9 +155,9 @@ void main() {
}); });
testWidgets('ViewAnchor cannot be used at the top of the widget tree (outside of View)', (WidgetTester tester) async { testWidgets('ViewAnchor cannot be used at the top of the widget tree (outside of View)', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: const ViewAnchor( const ViewAnchor(
child: SizedBox(), child: SizedBox(),
), ),
); );
...@@ -175,18 +175,18 @@ void main() { ...@@ -175,18 +175,18 @@ void main() {
child: const SizedBox(), child: const SizedBox(),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
child: globalKeyedViewAnchor, child: globalKeyedViewAnchor,
), ),
); );
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: globalKeyedViewAnchor, globalKeyedViewAnchor,
); );
expect(tester.takeException(), isFlutterError.having( expect(tester.takeException(), isFlutterError.having(
...@@ -197,9 +197,9 @@ void main() { ...@@ -197,9 +197,9 @@ void main() {
}); });
testWidgets('View can be used at the top of the widget tree', (WidgetTester tester) async { testWidgets('View can be used at the top of the widget tree', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
child: Container(), child: Container(),
), ),
...@@ -214,9 +214,9 @@ void main() { ...@@ -214,9 +214,9 @@ void main() {
child: const ColoredBox(color: Colors.red), child: const ColoredBox(color: Colors.red),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
child: ViewAnchor( child: ViewAnchor(
view: globalKeyView, // This one has trouble when deactivating view: globalKeyView, // This one has trouble when deactivating
...@@ -228,9 +228,9 @@ void main() { ...@@ -228,9 +228,9 @@ void main() {
expect(find.byType(SizedBox), findsOneWidget); expect(find.byType(SizedBox), findsOneWidget);
expect(find.byType(ColoredBox), findsOneWidget); expect(find.byType(ColoredBox), findsOneWidget);
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: globalKeyView, globalKeyView,
); );
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
expect(find.byType(SizedBox), findsNothing); expect(find.byType(SizedBox), findsNothing);
...@@ -238,9 +238,9 @@ void main() { ...@@ -238,9 +238,9 @@ void main() {
}); });
testWidgets('ViewCollection can be used at the top of the widget tree', (WidgetTester tester) async { testWidgets('ViewCollection can be used at the top of the widget tree', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: tester.view, view: tester.view,
...@@ -291,9 +291,9 @@ void main() { ...@@ -291,9 +291,9 @@ void main() {
}); });
testWidgets('ViewCollection cannot have render object widgets as children', (WidgetTester tester) async { testWidgets('ViewCollection cannot have render object widgets as children', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: const ViewCollection( const ViewCollection(
views: <Widget>[ views: <Widget>[
ColoredBox(color: Colors.red), ColoredBox(color: Colors.red),
], ],
...@@ -319,9 +319,9 @@ void main() { ...@@ -319,9 +319,9 @@ void main() {
child: const ColoredBox(color: Colors.red), child: const ColoredBox(color: Colors.red),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
greenView, greenView,
ViewCollection( ViewCollection(
...@@ -335,9 +335,9 @@ void main() { ...@@ -335,9 +335,9 @@ void main() {
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
expect(find.byType(ColoredBox), findsNWidgets(2)); expect(find.byType(ColoredBox), findsNWidgets(2));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
redView, redView,
ViewCollection( ViewCollection(
...@@ -371,9 +371,9 @@ void main() { ...@@ -371,9 +371,9 @@ void main() {
return result; return result;
} }
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: greenView, view: greenView,
...@@ -412,9 +412,9 @@ void main() { ...@@ -412,9 +412,9 @@ void main() {
expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>())); expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>()));
// Move the child. // Move the child.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: greenView, view: greenView,
...@@ -476,9 +476,9 @@ void main() { ...@@ -476,9 +476,9 @@ void main() {
return result; return result;
} }
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: greenView, view: greenView,
...@@ -517,9 +517,9 @@ void main() { ...@@ -517,9 +517,9 @@ void main() {
expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>())); expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>()));
// Move the child. // Move the child.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: greenView, view: greenView,
...@@ -571,9 +571,9 @@ void main() { ...@@ -571,9 +571,9 @@ void main() {
key: GlobalKey(), key: GlobalKey(),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
key: greenKey, key: greenKey,
...@@ -610,9 +610,9 @@ void main() { ...@@ -610,9 +610,9 @@ void main() {
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!)); final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
// Move the child and remove its view. // Move the child and remove its view.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
key: greenKey, key: greenKey,
...@@ -652,9 +652,9 @@ void main() { ...@@ -652,9 +652,9 @@ void main() {
key: GlobalKey(), key: GlobalKey(),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
key: greenKey, key: greenKey,
...@@ -691,9 +691,9 @@ void main() { ...@@ -691,9 +691,9 @@ void main() {
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!)); final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
// Move the child and remove its view. // Move the child and remove its view.
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
key: redKey, key: redKey,
...@@ -1009,9 +1009,9 @@ void main() { ...@@ -1009,9 +1009,9 @@ void main() {
final FlutterView redView = tester.view; final FlutterView redView = tester.view;
final FlutterView greenView = FakeView(tester.view); final FlutterView greenView = FakeView(tester.view);
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: redView, view: redView,
...@@ -1029,9 +1029,9 @@ void main() { ...@@ -1029,9 +1029,9 @@ void main() {
expect(findsColoredBox(Colors.red), findsOneWidget); expect(findsColoredBox(Colors.red), findsOneWidget);
final RenderObject box = tester.renderObject(findsColoredBox(Colors.green)); final RenderObject box = tester.renderObject(findsColoredBox(Colors.green));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: redView, view: redView,
...@@ -1052,17 +1052,17 @@ void main() { ...@@ -1052,17 +1052,17 @@ void main() {
child: const SizedBox(), child: const SizedBox(),
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: view, view,
); );
final RenderObject renderView = tester.renderObject(find.byType(View)); final RenderObject renderView = tester.renderObject(find.byType(View));
final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox)); final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[view], views: <Widget>[view],
), ),
); );
...@@ -1070,9 +1070,9 @@ void main() { ...@@ -1070,9 +1070,9 @@ void main() {
expect(tester.renderObject(find.byType(View)), same(renderView)); expect(tester.renderObject(find.byType(View)), same(renderView));
expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox)); expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: view, view,
); );
expect(tester.renderObject(find.byType(View)), same(renderView)); expect(tester.renderObject(find.byType(View)), same(renderView));
...@@ -1114,9 +1114,9 @@ void main() { ...@@ -1114,9 +1114,9 @@ void main() {
child: const SizedBox(), child: const SizedBox(),
) )
); );
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: view, view,
); );
final RenderObject renderSemantics = tester.renderObject(find.bySemanticsLabel('Hello')); final RenderObject renderSemantics = tester.renderObject(find.bySemanticsLabel('Hello'));
...@@ -1124,9 +1124,9 @@ void main() { ...@@ -1124,9 +1124,9 @@ void main() {
expect(semantics.id, 1); expect(semantics.id, 1);
expect(renderSemantics.debugSemantics, same(semantics)); expect(renderSemantics.debugSemantics, same(semantics));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
view, view,
], ],
...@@ -1144,9 +1144,3 @@ void main() { ...@@ -1144,9 +1144,3 @@ void main() {
Finder findsColoredBox(Color color) { Finder findsColoredBox(Color color) {
return find.byWidgetPredicate((Widget widget) => widget is ColoredBox && widget.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();
}
...@@ -77,9 +77,9 @@ void main() { ...@@ -77,9 +77,9 @@ void main() {
PipelineOwner? outsideParent; PipelineOwner? outsideParent;
PipelineOwner? insideParent; PipelineOwner? insideParent;
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
outsideView = View.maybeOf(context); outsideView = View.maybeOf(context);
outsideParent = View.pipelineOwnerOf(context); outsideParent = View.pipelineOwnerOf(context);
...@@ -114,9 +114,9 @@ void main() { ...@@ -114,9 +114,9 @@ void main() {
}); });
testWidgets('cannot have multiple views with same FlutterView', (WidgetTester tester) async { testWidgets('cannot have multiple views with same FlutterView', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: tester.view, view: tester.view,
...@@ -224,9 +224,9 @@ void main() { ...@@ -224,9 +224,9 @@ void main() {
}); });
testWidgets('visitChildren of ViewCollection visits all children', (WidgetTester tester) async { testWidgets('visitChildren of ViewCollection visits all children', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: tester.view, view: tester.view,
...@@ -250,9 +250,9 @@ void main() { ...@@ -250,9 +250,9 @@ void main() {
}); });
expect(children, hasLength(3)); expect(children, hasLength(3));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: ViewCollection( ViewCollection(
views: <Widget>[ views: <Widget>[
View( View(
view: tester.view, view: tester.view,
...@@ -271,9 +271,9 @@ void main() { ...@@ -271,9 +271,9 @@ void main() {
group('renderObject getter', () { group('renderObject getter', () {
testWidgets('ancestors of view see RenderView as renderObject', (WidgetTester tester) async { testWidgets('ancestors of view see RenderView as renderObject', (WidgetTester tester) async {
late BuildContext builderContext; late BuildContext builderContext;
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
builderContext = context; builderContext = context;
return View( return View(
...@@ -293,9 +293,9 @@ void main() { ...@@ -293,9 +293,9 @@ void main() {
testWidgets('ancestors of ViewCollection get null for renderObject', (WidgetTester tester) async { testWidgets('ancestors of ViewCollection get null for renderObject', (WidgetTester tester) async {
late BuildContext builderContext; late BuildContext builderContext;
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: Builder( Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
builderContext = context; builderContext = context;
return ViewCollection( return ViewCollection(
...@@ -345,9 +345,9 @@ void main() { ...@@ -345,9 +345,9 @@ void main() {
}); });
testWidgets('correctly switches between view configurations', (WidgetTester tester) async { testWidgets('correctly switches between view configurations', (WidgetTester tester) async {
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner, deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner,
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView, deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView,
...@@ -359,9 +359,9 @@ void main() { ...@@ -359,9 +359,9 @@ void main() {
expect(renderView.owner, same(tester.binding.pipelineOwner)); expect(renderView.owner, same(tester.binding.pipelineOwner));
expect(tester.renderObject(find.byType(SizedBox)).owner, same(tester.binding.pipelineOwner)); expect(tester.renderObject(find.byType(SizedBox)).owner, same(tester.binding.pipelineOwner));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
child: const SizedBox(), child: const SizedBox(),
), ),
...@@ -371,9 +371,9 @@ void main() { ...@@ -371,9 +371,9 @@ void main() {
expect(renderView.owner, isNot(same(tester.binding.pipelineOwner))); expect(renderView.owner, isNot(same(tester.binding.pipelineOwner)));
expect(tester.renderObject(find.byType(SizedBox)).owner, isNot(same(tester.binding.pipelineOwner))); expect(tester.renderObject(find.byType(SizedBox)).owner, isNot(same(tester.binding.pipelineOwner)));
await pumpWidgetWithoutViewWrapper( await tester.pumpWidget(
tester: tester, wrapWithView: false,
widget: View( View(
view: tester.view, view: tester.view,
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner, deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner,
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView, deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView,
...@@ -513,12 +513,6 @@ void main() { ...@@ -513,12 +513,6 @@ 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 { class SpyRenderWidget extends SizedBox {
const SpyRenderWidget({super.key, required this.label, required this.log, super.child}); const SpyRenderWidget({super.key, required this.label, required this.log, super.child});
......
...@@ -581,15 +581,23 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -581,15 +581,23 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// ``` /// ```
/// {@end-tool} /// {@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 /// See also [LiveTestWidgetsFlutterBindingFramePolicy], which affects how
/// this method works when the test is run with `flutter run`. /// this method works when the test is run with `flutter run`.
Future<void> pumpWidget( Future<void> pumpWidget(
Widget widget, { Widget widget, {
Duration? duration, Duration? duration,
EnginePhase phase = EnginePhase.sendSemanticsUpdate, EnginePhase phase = EnginePhase.sendSemanticsUpdate,
bool wrapWithView = true,
}) { }) {
return TestAsyncUtils.guard<void>(() { return TestAsyncUtils.guard<void>(() {
binding.attachRootWidget(binding.wrapWithDefaultView(widget)); binding.attachRootWidget(wrapWithView ? binding.wrapWithDefaultView(widget) : widget);
binding.scheduleFrame(); binding.scheduleFrame();
return binding.pump(duration, phase); return binding.pump(duration, phase);
}); });
......
...@@ -112,7 +112,8 @@ Future<void> pumpViews({required WidgetTester tester, required List<Widget> vie ...@@ -112,7 +112,8 @@ Future<void> pumpViews({required WidgetTester tester, required List<Widget> vie
), ),
]; ];
tester.binding.attachRootWidget( return tester.pumpWidget(
wrapWithView: false,
Directionality( Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: ViewCollection( child: ViewCollection(
...@@ -120,6 +121,4 @@ Future<void> pumpViews({required WidgetTester tester, required List<Widget> vie ...@@ -120,6 +121,4 @@ Future<void> pumpViews({required WidgetTester tester, required List<Widget> vie
), ),
), ),
); );
tester.binding.scheduleFrame();
return tester.binding.pump();
} }
...@@ -208,7 +208,8 @@ Future<void> pumpViews({required WidgetTester tester}) { ...@@ -208,7 +208,8 @@ Future<void> pumpViews({required WidgetTester tester}) {
), ),
]; ];
tester.binding.attachRootWidget( return tester.pumpWidget(
wrapWithView: false,
Directionality( Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: ViewCollection( child: ViewCollection(
...@@ -216,6 +217,4 @@ Future<void> pumpViews({required WidgetTester tester}) { ...@@ -216,6 +217,4 @@ Future<void> pumpViews({required WidgetTester tester}) {
), ),
), ),
); );
tester.binding.scheduleFrame();
return tester.binding.pump();
} }
...@@ -16,6 +16,8 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -16,6 +16,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:matcher/expect.dart' as matcher; import 'package:matcher/expect.dart' as matcher;
import 'package:matcher/src/expect/async_matcher.dart'; import 'package:matcher/src/expect/async_matcher.dart';
import 'multi_view_testing.dart';
void main() { void main() {
group('expectLater', () { group('expectLater', () {
testWidgets('completes when matcher completes', (WidgetTester tester) async { testWidgets('completes when matcher completes', (WidgetTester tester) async {
...@@ -638,6 +640,56 @@ void main() { ...@@ -638,6 +640,56 @@ void main() {
.checkMockMessageHandler(SystemChannels.accessibility.name, null), isTrue); .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: FakeView(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 { class FakeMatcher extends AsyncMatcher {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment