Unverified Commit bf5fdb9f authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Reland "Inject current FlutterView into tree and make available via...

Reland "Inject current FlutterView into tree and make available via `View.of(context)` (#116924)" (#117244)

* Reland "Inject current `FlutterView` into tree and make available via `View.of(context)` (#116924)"

This reverts commit 9102f2fe.

* remove window placeholder
parent 0a2a1d91
...@@ -19,6 +19,7 @@ import 'framework.dart'; ...@@ -19,6 +19,7 @@ import 'framework.dart';
import 'platform_menu_bar.dart'; import 'platform_menu_bar.dart';
import 'router.dart'; import 'router.dart';
import 'service_extensions.dart'; import 'service_extensions.dart';
import 'view.dart';
import 'widget_inspector.dart'; import 'widget_inspector.dart';
export 'dart:ui' show AppLifecycleState, Locale; export 'dart:ui' show AppLifecycleState, Locale;
...@@ -896,6 +897,22 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -896,6 +897,22 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
@override @override
bool get framesEnabled => super.framesEnabled && _readyToProduceFrames; bool get framesEnabled => super.framesEnabled && _readyToProduceFrames;
/// Used by [runApp] to wrap the provided `rootWidget` in the default [View].
///
/// The [View] determines into what [FlutterView] the app is rendered into.
/// For backwards-compatibility reasons, this method currently chooses
/// [window] (which is a [FlutterView]) as the rendering target. This will
/// change in a future version of Flutter.
///
/// The `rootWidget` widget provided to this method must not already be
/// wrapped in a [View].
Widget wrapWithDefaultView(Widget rootWidget) {
return View(
view: window,
child: rootWidget,
);
}
/// Schedules a [Timer] for attaching the root widget. /// Schedules a [Timer] for attaching the root widget.
/// ///
/// This is called by [runApp] to configure the widget tree. Consider using /// This is called by [runApp] to configure the widget tree. Consider using
...@@ -1014,8 +1031,9 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -1014,8 +1031,9 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
/// * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to /// * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to
/// 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) {
WidgetsFlutterBinding.ensureInitialized() final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
..scheduleAttachRootWidget(app) binding
..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
..scheduleWarmUpFrame(); ..scheduleWarmUpFrame();
} }
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show FlutterView;
import 'framework.dart';
import 'lookup_boundary.dart';
/// Injects a [FlutterView] into the tree and makes it available to descendants
/// within the same [LookupBoundary] via [View.of] and [View.maybeOf].
///
/// In a future version of Flutter, the functionality of this widget will be
/// extended to actually bootstrap the render tree that is going to be rendered
/// into the provided [view]. This will enable rendering content into multiple
/// [FlutterView]s from a single widget tree.
///
/// Each [FlutterView] can be associated with at most one [View] widget in the
/// widget tree. Two or more [View] widgets configured with the same
/// [FlutterView] must never exist within the same widget tree at the same time.
/// Internally, this limitation is enforced by a [GlobalObjectKey] that derives
/// its identity from the [view] provided to this widget.
class View extends InheritedWidget {
/// Injects the provided [view] into the widget tree.
View({required this.view, required super.child}) : super(key: GlobalObjectKey(view));
/// The [FlutterView] to be injected into the tree.
final FlutterView view;
@override
bool updateShouldNotify(View oldWidget) => view != oldWidget.view;
/// Returns the [FlutterView] that the provided `context` will render into.
///
/// Returns null if the `context` is not associated with a [FlutterView].
///
/// The method creates a dependency on the `context`, which will be informed
/// when the identity of the [FlutterView] changes (i.e. the `context` is
/// moved to render into a different [FlutterView] then before). The context
/// will not be informed when the properties on the [FlutterView] itself
/// change their values. To access the property values of a [FlutterView] it
/// is best practise to use [MediaQuery.maybeOf] instead, which will ensure
/// that the `context` is informed when the view properties change.
///
/// See also:
///
/// * [View.of], which throws instead of returning null if no [FlutterView]
/// is found.
static FlutterView? maybeOf(BuildContext context) {
return LookupBoundary.dependOnInheritedWidgetOfExactType<View>(context)?.view;
}
/// Returns the [FlutterView] that the provided `context` will render into.
///
/// Throws if the `context` is not associated with a [FlutterView].
///
/// The method creates a dependency on the `context`, which will be informed
/// when the identity of the [FlutterView] changes (i.e. the `context` is
/// moved to render into a different [FlutterView] then before). The context
/// will not be informed when the properties on the [FlutterView] itself
/// change their values. To access the property values of a [FlutterView] it
/// is best practise to use [MediaQuery.of] instead, which will ensure that
/// the `context` is informed when the view properties change.
///
/// See also:
///
/// * [View.maybeOf], which throws instead of returning null if no
/// [FlutterView] is found.
static FlutterView of(BuildContext context) {
final FlutterView? result = maybeOf(context);
assert(() {
if (result == null) {
final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<View>(context);
final List<DiagnosticsNode> information = <DiagnosticsNode>[
if (hiddenByBoundary) ...<DiagnosticsNode>[
ErrorSummary('View.of() was called with a context that does not have access to a View widget.'),
ErrorDescription('The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.'),
] else ...<DiagnosticsNode>[
ErrorSummary('View.of() was called with a context that does not contain a View widget.'),
ErrorDescription('No View widget ancestor could be found starting from the context that was passed to View.of().'),
],
ErrorDescription(
'The context used was:\n'
' $context',
),
ErrorHint('This usually means that the provided context is not associated with a View.'),
];
throw FlutterError.fromParts(information);
}
return true;
}());
return result!;
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Placeholder to be used in a future version of Flutter.
abstract class Window {
const Window._();
}
...@@ -148,6 +148,8 @@ export 'src/widgets/transitions.dart'; ...@@ -148,6 +148,8 @@ export 'src/widgets/transitions.dart';
export 'src/widgets/tween_animation_builder.dart'; export 'src/widgets/tween_animation_builder.dart';
export 'src/widgets/unique_widget.dart'; export 'src/widgets/unique_widget.dart';
export 'src/widgets/value_listenable_builder.dart'; export 'src/widgets/value_listenable_builder.dart';
// TODO(goderbauer): Enable once clean-up in google3 is done.
// export 'src/widgets/view.dart';
export 'src/widgets/viewport.dart'; export 'src/widgets/viewport.dart';
export 'src/widgets/visibility.dart'; export 'src/widgets/visibility.dart';
export 'src/widgets/widget_inspector.dart'; export 'src/widgets/widget_inspector.dart';
......
...@@ -8,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -8,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('debugCheckHasMaterial control test', (WidgetTester tester) async { testWidgets('debugCheckHasMaterial control test', (WidgetTester tester) async {
await tester.pumpWidget(const Chip(label: Text('label'))); await tester.pumpWidget(const Center(child: Chip(label: Text('label'))));
final dynamic exception = tester.takeException(); final dynamic exception = tester.takeException();
expect(exception, isFlutterError); expect(exception, isFlutterError);
final FlutterError error = exception as FlutterError; final FlutterError error = exception as FlutterError;
...@@ -25,7 +25,7 @@ void main() { ...@@ -25,7 +25,7 @@ void main() {
expect(error.diagnostics[3], isA<DiagnosticsProperty<Element>>()); expect(error.diagnostics[3], isA<DiagnosticsProperty<Element>>());
expect(error.diagnostics[4], isA<DiagnosticsBlock>()); expect(error.diagnostics[4], isA<DiagnosticsBlock>());
expect( expect(
error.toStringDeep(), error.toStringDeep(), startsWith(
'FlutterError\n' 'FlutterError\n'
' No Material widget found.\n' ' No Material widget found.\n'
' Chip widgets require a Material widget ancestor within the\n' ' Chip widgets require a Material widget ancestor within the\n'
...@@ -42,12 +42,13 @@ void main() { ...@@ -42,12 +42,13 @@ void main() {
' The specific widget that could not find a Material ancestor was:\n' ' The specific widget that could not find a Material ancestor was:\n'
' Chip\n' ' Chip\n'
' The ancestors of this widget were:\n' ' The ancestors of this widget were:\n'
' [root]\n', ' Center\n'
); // End of ancestor chain omitted, not relevant for test.
));
}); });
testWidgets('debugCheckHasMaterialLocalizations control test', (WidgetTester tester) async { testWidgets('debugCheckHasMaterialLocalizations control test', (WidgetTester tester) async {
await tester.pumpWidget(const BackButton()); await tester.pumpWidget(const Center(child: BackButton()));
final dynamic exception = tester.takeException(); final dynamic exception = tester.takeException();
expect(exception, isFlutterError); expect(exception, isFlutterError);
final FlutterError error = exception as FlutterError; final FlutterError error = exception as FlutterError;
...@@ -64,7 +65,7 @@ void main() { ...@@ -64,7 +65,7 @@ void main() {
expect(error.diagnostics[4], isA<DiagnosticsProperty<Element>>()); expect(error.diagnostics[4], isA<DiagnosticsProperty<Element>>());
expect(error.diagnostics[5], isA<DiagnosticsBlock>()); expect(error.diagnostics[5], isA<DiagnosticsBlock>());
expect( expect(
error.toStringDeep(), error.toStringDeep(), startsWith(
'FlutterError\n' 'FlutterError\n'
' No MaterialLocalizations found.\n' ' No MaterialLocalizations found.\n'
' BackButton widgets require MaterialLocalizations to be provided\n' ' BackButton widgets require MaterialLocalizations to be provided\n'
...@@ -78,8 +79,9 @@ void main() { ...@@ -78,8 +79,9 @@ void main() {
' ancestor was:\n' ' ancestor was:\n'
' BackButton\n' ' BackButton\n'
' The ancestors of this widget were:\n' ' The ancestors of this widget were:\n'
' [root]\n', ' Center\n'
); // End of ancestor chain omitted, not relevant for test.
));
}); });
testWidgets('debugCheckHasScaffold control test', (WidgetTester tester) async { testWidgets('debugCheckHasScaffold control test', (WidgetTester tester) async {
...@@ -233,6 +235,7 @@ void main() { ...@@ -233,6 +235,7 @@ void main() {
' HeroControllerScope\n' ' HeroControllerScope\n'
' ScrollConfiguration\n' ' ScrollConfiguration\n'
' MaterialApp\n' ' MaterialApp\n'
' View-[GlobalObjectKey TestWindow#00000]\n'
' [root]\n' ' [root]\n'
' Typically, the Scaffold widget is introduced by the MaterialApp\n' ' Typically, the Scaffold widget is introduced by the MaterialApp\n'
' or WidgetsApp widget at the top of your application widget tree.\n' ' or WidgetsApp widget at the top of your application widget tree.\n'
...@@ -377,6 +380,7 @@ void main() { ...@@ -377,6 +380,7 @@ void main() {
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00000]\n' ' Scaffold-[LabeledGlobalKey<ScaffoldState>#00000]\n'
' MediaQuery\n' ' MediaQuery\n'
' Directionality\n' ' Directionality\n'
' View-[GlobalObjectKey TestWindow#00000]\n'
' [root]\n' ' [root]\n'
' Typically, the ScaffoldMessenger widget is introduced by the\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n'
' MaterialApp at the top of your application widget tree.\n' ' MaterialApp at the top of your application widget tree.\n'
......
...@@ -2458,6 +2458,7 @@ void main() { ...@@ -2458,6 +2458,7 @@ void main() {
' Scaffold\n' ' Scaffold\n'
' MediaQuery\n' ' MediaQuery\n'
' Directionality\n' ' Directionality\n'
' View-[GlobalObjectKey TestWindow#e6136]\n'
' [root]\n' ' [root]\n'
' Typically, the ScaffoldMessenger widget is introduced by the\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n'
' MaterialApp at the top of your application widget tree.\n', ' MaterialApp at the top of your application widget tree.\n',
......
...@@ -7431,7 +7431,6 @@ void main() { ...@@ -7431,7 +7431,6 @@ void main() {
final dynamic exception = tester.takeException(); final dynamic exception = tester.takeException();
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect(exception.toString(), startsWith('No Material widget found.')); expect(exception.toString(), startsWith('No Material widget found.'));
expect(exception.toString(), endsWith(':\n $textField\nThe ancestors of this widget were:\n [root]'));
}); });
testWidgets('TextField loses focus when disabled', (WidgetTester tester) async { testWidgets('TextField loses focus when disabled', (WidgetTester tester) async {
......
...@@ -372,7 +372,8 @@ void main() { ...@@ -372,7 +372,8 @@ void main() {
' in its parent data.\n' ' in its parent data.\n'
' The following child has no ID: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT:\n' ' The following child has no ID: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT:\n'
' creator: ConstrainedBox ← Container ← LayoutWithMissingId ←\n' ' creator: ConstrainedBox ← Container ← LayoutWithMissingId ←\n'
' CustomMultiChildLayout ← Center ← [root]\n' ' CustomMultiChildLayout ← Center ← View-[GlobalObjectKey\n'
' TestWindow#00000] ← [root]\n'
' parentData: offset=Offset(0.0, 0.0); id=null\n' ' parentData: offset=Offset(0.0, 0.0); id=null\n'
' constraints: MISSING\n' ' constraints: MISSING\n'
' size: MISSING\n' ' size: MISSING\n'
......
...@@ -79,14 +79,13 @@ void main() { ...@@ -79,14 +79,13 @@ void main() {
expect(error.diagnostics[2], isA<DiagnosticsProperty<Element>>()); expect(error.diagnostics[2], isA<DiagnosticsProperty<Element>>());
expect( expect(
error.toStringDeep(), error.toStringDeep(),
equalsIgnoringHashCodes( startsWith(
'FlutterError\n' 'FlutterError\n'
' No Table widget found.\n' ' No Table widget found.\n'
' Builder widgets require a Table widget ancestor.\n' ' Builder widgets require a Table widget ancestor.\n'
' The specific widget that could not find a Table ancestor was:\n' ' The specific widget that could not find a Table ancestor was:\n'
' Builder\n' ' Builder\n'
' The ownership chain for the affected widget is: "Builder ←\n' ' The ownership chain for the affected widget is: "Builder ←', // End of ownership chain omitted, not relevant for test.
' [root]"\n',
), ),
); );
} }
...@@ -122,15 +121,20 @@ void main() { ...@@ -122,15 +121,20 @@ void main() {
); );
expect( expect(
error.toStringDeep(), error.toStringDeep(),
equalsIgnoringHashCodes( startsWith(
'FlutterError\n' 'FlutterError\n'
' No MediaQuery widget ancestor found.\n' ' No MediaQuery widget ancestor found.\n'
' Builder widgets require a MediaQuery widget ancestor.\n' ' Builder widgets require a MediaQuery widget ancestor.\n'
' The specific widget that could not find a MediaQuery ancestor\n' ' The specific widget that could not find a MediaQuery ancestor\n'
' was:\n' ' was:\n'
' Builder\n' ' Builder\n'
' The ownership chain for the affected widget is: "Builder ←\n' ' The ownership chain for the affected widget is: "Builder ←' // Full chain omitted, not relevant for test.
' [root]"\n' ),
);
expect(
error.toStringDeep(),
endsWith(
'[root]"\n' // End of ownership chain.
' No MediaQuery ancestor could be found starting from the context\n' ' No MediaQuery ancestor could be found starting from the context\n'
' that was passed to MediaQuery.of(). This can happen because you\n' ' that was passed to MediaQuery.of(). This can happen because you\n'
' have not added a WidgetsApp, CupertinoApp, or MaterialApp widget\n' ' have not added a WidgetsApp, CupertinoApp, or MaterialApp widget\n'
......
...@@ -1228,7 +1228,8 @@ void main() { ...@@ -1228,7 +1228,8 @@ void main() {
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'FocusManager#00000\n' 'FocusManager#00000\n'
' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n' ' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n'
' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n' ' │ primaryFocusCreator: Container-[GlobalKey#00000] ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │\n' ' │\n'
' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n' ' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
' │ IN FOCUS PATH\n' ' │ IN FOCUS PATH\n'
...@@ -1266,7 +1267,6 @@ void main() { ...@@ -1266,7 +1267,6 @@ void main() {
}); });
}); });
group('Autofocus', () { group('Autofocus', () {
testWidgets( testWidgets(
'works when the previous focused node is detached', 'works when the previous focused node is detached',
...@@ -1695,7 +1695,6 @@ void main() { ...@@ -1695,7 +1695,6 @@ void main() {
debugPrint = oldDebugPrint; debugPrint = oldDebugPrint;
} }
final String messagesStr = messages.toString(); final String messagesStr = messages.toString();
expect(messagesStr.split('\n').length, equals(58));
expect(messagesStr, contains(RegExp(r' └─Child 1: FocusScopeNode#[a-f0-9]{5}\(parent1 \[PRIMARY FOCUS\]\)'))); expect(messagesStr, contains(RegExp(r' └─Child 1: FocusScopeNode#[a-f0-9]{5}\(parent1 \[PRIMARY FOCUS\]\)')));
expect(messagesStr, contains('FOCUS: Notified 2 dirty nodes')); expect(messagesStr, contains('FOCUS: Notified 2 dirty nodes'));
expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1'))); expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1')));
......
...@@ -1183,13 +1183,13 @@ void main() { ...@@ -1183,13 +1183,13 @@ void main() {
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect( expect(
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( startsWith(
'The children of `MultiChildRenderObjectElement` must each has an associated render object.\n' 'The children of `MultiChildRenderObjectElement` must each has an associated render object.\n'
'This typically means that the `_EmptyWidget` or its children\n' 'This typically means that the `_EmptyWidget` or its children\n'
'are not a subtype of `RenderObjectWidget`.\n' 'are not a subtype of `RenderObjectWidget`.\n'
'The following element does not have an associated render object:\n' 'The following element does not have an associated render object:\n'
' _EmptyWidget\n' ' _EmptyWidget\n'
'debugCreator: _EmptyWidget ← Column ← [root]', 'debugCreator: _EmptyWidget ← Column ← ', // Omitted end of debugCreator chain because it's irrelevant for test.
), ),
); );
}); });
...@@ -1216,13 +1216,13 @@ void main() { ...@@ -1216,13 +1216,13 @@ void main() {
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect( expect(
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( startsWith(
'The children of `MultiChildRenderObjectElement` must each has an associated render object.\n' 'The children of `MultiChildRenderObjectElement` must each has an associated render object.\n'
'This typically means that the `_EmptyWidget` or its children\n' 'This typically means that the `_EmptyWidget` or its children\n'
'are not a subtype of `RenderObjectWidget`.\n' 'are not a subtype of `RenderObjectWidget`.\n'
'The following element does not have an associated render object:\n' 'The following element does not have an associated render object:\n'
' _EmptyWidget\n' ' _EmptyWidget\n'
'debugCreator: _EmptyWidget ← Column ← [root]', 'debugCreator: _EmptyWidget ← Column ← ', // Omitted end of debugCreator chain because it's irrelevant for test.
), ),
); );
}); });
......
...@@ -30,7 +30,7 @@ class TestWidgetState extends State<TestWidget> { ...@@ -30,7 +30,7 @@ class TestWidgetState extends State<TestWidget> {
void main() { void main() {
testWidgets('initState() is called when we are in the tree', (WidgetTester tester) async { testWidgets('initState() is called when we are in the tree', (WidgetTester tester) async {
await tester.pumpWidget(const Parent(child: TestWidget())); await tester.pumpWidget(const Parent(child: TestWidget()));
expect(ancestors, equals(<String>['Parent', 'RenderObjectToWidgetAdapter<RenderBox>'])); expect(ancestors, equals(<String>['Parent', 'View', 'RenderObjectToWidgetAdapter<RenderBox>']));
}); });
} }
......
...@@ -63,15 +63,20 @@ void main() { ...@@ -63,15 +63,20 @@ void main() {
expect(error.diagnostics.last, isA<ErrorHint>()); expect(error.diagnostics.last, isA<ErrorHint>());
expect( expect(
error.toStringDeep(), error.toStringDeep(),
equalsIgnoringHashCodes( startsWith(
'FlutterError\n' 'FlutterError\n'
' No MediaQuery widget ancestor found.\n' ' No MediaQuery widget ancestor found.\n'
' Builder widgets require a MediaQuery widget ancestor.\n' ' Builder widgets require a MediaQuery widget ancestor.\n'
' The specific widget that could not find a MediaQuery ancestor\n' ' The specific widget that could not find a MediaQuery ancestor\n'
' was:\n' ' was:\n'
' Builder\n' ' Builder\n'
' The ownership chain for the affected widget is: "Builder ←\n' ' The ownership chain for the affected widget is: "Builder ←', // Full ownership chain omitted, not relevant for test.
' [root]"\n' ),
);
expect(
error.toStringDeep(),
endsWith(
'[root]"\n' // End of ownership chain.
' No MediaQuery ancestor could be found starting from the context\n' ' No MediaQuery ancestor could be found starting from the context\n'
' that was passed to MediaQuery.of(). This can happen because you\n' ' that was passed to MediaQuery.of(). This can happen because you\n'
' have not added a WidgetsApp, CupertinoApp, or MaterialApp widget\n' ' have not added a WidgetsApp, CupertinoApp, or MaterialApp widget\n'
......
...@@ -274,7 +274,7 @@ void main() { ...@@ -274,7 +274,7 @@ void main() {
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect( expect(
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( startsWith(
'Incorrect use of ParentDataWidget.\n' 'Incorrect use of ParentDataWidget.\n'
'The following ParentDataWidgets are providing parent data to the same RenderObject:\n' 'The following ParentDataWidgets are providing parent data to the same RenderObject:\n'
'- Positioned(left: 7.0, top: 6.0) (typically placed directly inside a Stack widget)\n' '- Positioned(left: 7.0, top: 6.0) (typically placed directly inside a Stack widget)\n'
...@@ -283,7 +283,7 @@ void main() { ...@@ -283,7 +283,7 @@ void main() {
'Usually, this indicates that at least one of the offending ParentDataWidgets listed ' 'Usually, this indicates that at least one of the offending ParentDataWidgets listed '
'above is not placed directly inside a compatible ancestor widget.\n' 'above is not placed directly inside a compatible ancestor widget.\n'
'The ownership chain for the RenderObject that received the parent data was:\n' 'The ownership chain for the RenderObject that received the parent data was:\n'
' DecoratedBox ← Positioned ← Positioned ← Stack ← Directionality ← [root]', ' DecoratedBox ← Positioned ← Positioned ← Stack ← Directionality ← ', // End of chain omitted, not relevant for test.
), ),
); );
...@@ -311,7 +311,7 @@ void main() { ...@@ -311,7 +311,7 @@ void main() {
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect( expect(
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( startsWith(
'Incorrect use of ParentDataWidget.\n' 'Incorrect use of ParentDataWidget.\n'
'The ParentDataWidget Positioned(left: 7.0, top: 6.0) wants to apply ParentData of type ' 'The ParentDataWidget Positioned(left: 7.0, top: 6.0) wants to apply ParentData of type '
'StackParentData to a RenderObject, which has been set up to accept ParentData of ' 'StackParentData to a RenderObject, which has been set up to accept ParentData of '
...@@ -320,7 +320,7 @@ void main() { ...@@ -320,7 +320,7 @@ void main() {
'Typically, Positioned widgets are placed directly inside Stack widgets.\n' 'Typically, Positioned widgets are placed directly inside Stack widgets.\n'
'The offending Positioned is currently placed inside a Row widget.\n' 'The offending Positioned is currently placed inside a Row widget.\n'
'The ownership chain for the RenderObject that received the incompatible parent data was:\n' 'The ownership chain for the RenderObject that received the incompatible parent data was:\n'
' DecoratedBox ← Positioned ← Row ← DummyWidget ← Directionality ← [root]', ' DecoratedBox ← Positioned ← Row ← DummyWidget ← Directionality ← ', // End of chain omitted, not relevant for test.
), ),
); );
...@@ -410,7 +410,7 @@ void main() { ...@@ -410,7 +410,7 @@ void main() {
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect( expect(
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( startsWith(
'Incorrect use of ParentDataWidget.\n' 'Incorrect use of ParentDataWidget.\n'
'The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type ' 'The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type '
'FlexParentData to a RenderObject, which has been set up to accept ParentData of ' 'FlexParentData to a RenderObject, which has been set up to accept ParentData of '
...@@ -419,7 +419,7 @@ void main() { ...@@ -419,7 +419,7 @@ void main() {
'Typically, Expanded widgets are placed directly inside Flex widgets.\n' 'Typically, Expanded widgets are placed directly inside Flex widgets.\n'
'The offending Expanded is currently placed inside a Stack widget.\n' 'The offending Expanded is currently placed inside a Stack widget.\n'
'The ownership chain for the RenderObject that received the incompatible parent data was:\n' 'The ownership chain for the RenderObject that received the incompatible parent data was:\n'
' LimitedBox ← Container ← Expanded ← Stack ← Row ← Directionality ← [root]', ' LimitedBox ← Container ← Expanded ← Stack ← Row ← Directionality ← ', // Omitted end of debugCreator chain because it's irrelevant for test.
), ),
); );
}); });
......
...@@ -2486,7 +2486,7 @@ void main() { ...@@ -2486,7 +2486,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']), containsAllInOrder(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
); );
onPlatformViewCreatedCallBack(createdPlatformViewId); onPlatformViewCreatedCallBack(createdPlatformViewId);
...@@ -2495,7 +2495,7 @@ void main() { ...@@ -2495,7 +2495,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']), containsAllInOrder(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']),
); );
expect(createdPlatformViewId, currentViewId + 1); expect(createdPlatformViewId, currentViewId + 1);
...@@ -2535,7 +2535,7 @@ void main() { ...@@ -2535,7 +2535,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['Center', 'SizedBox', 'PlatformViewLink', '_PlatformViewPlaceHolder']), containsAllInOrder(<String>['Center', 'SizedBox', 'PlatformViewLink', '_PlatformViewPlaceHolder']),
); );
// 'create' should not have been called by PlatformViewLink, since its // 'create' should not have been called by PlatformViewLink, since its
...@@ -2580,7 +2580,7 @@ void main() { ...@@ -2580,7 +2580,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']), containsAllInOrder(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
); );
// Layout should have triggered a create call. Simulate the callback // Layout should have triggered a create call. Simulate the callback
...@@ -2592,7 +2592,7 @@ void main() { ...@@ -2592,7 +2592,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']), containsAllInOrder(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']),
); );
expect(createdPlatformViewId, currentViewId + 1); expect(createdPlatformViewId, currentViewId + 1);
...@@ -2678,7 +2678,7 @@ void main() { ...@@ -2678,7 +2678,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']), containsAllInOrder(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
); );
onPlatformViewCreatedCallBack(createdPlatformViewId); onPlatformViewCreatedCallBack(createdPlatformViewId);
...@@ -2687,7 +2687,7 @@ void main() { ...@@ -2687,7 +2687,7 @@ void main() {
expect( expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(), tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']), containsAllInOrder(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']),
); );
expect(createdPlatformViewId, currentViewId + 1); expect(createdPlatformViewId, currentViewId + 1);
......
...@@ -219,27 +219,30 @@ void main() { ...@@ -219,27 +219,30 @@ void main() {
expect( expect(
tester.renderObject(find.byType(_Diagonal)).toStringDeep(), tester.renderObject(find.byType(_Diagonal)).toStringDeep(),
equalsIgnoringHashCodes(r''' equalsIgnoringHashCodes(
_RenderDiagonal#00000 relayoutBoundary=up1 '_RenderDiagonal#00000 relayoutBoundary=up1\n'
│ creator: _Diagonal ← Align ← Directionality ← [root] ' │ creator: _Diagonal ← Align ← Directionality ←\n'
│ parentData: offset=Offset(0.0, 0.0) (can use size) ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
│ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0) ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
│ size: Size(190.0, 220.0) ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ size: Size(190.0, 220.0)\n'
├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2 ' │\n'
│ creator: SizedBox ← _Diagonal ← Align ← Directionality ← [root] ' ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
│ parentData: offset=Offset(0.0, 0.0) (can use size) ' │ creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
│ constraints: BoxConstraints(unconstrained) ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
│ size: Size(80.0, 100.0) ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
│ additionalConstraints: BoxConstraints(w=80.0, h=100.0) ' │ constraints: BoxConstraints(unconstrained)\n'
' │ size: Size(80.0, 100.0)\n'
└─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2 ' │ additionalConstraints: BoxConstraints(w=80.0, h=100.0)\n'
creator: SizedBox ← _Diagonal ← Align ← Directionality ← [root] ' │\n'
parentData: offset=Offset(80.0, 100.0) (can use size) ' └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
constraints: BoxConstraints(unconstrained) ' creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
size: Size(110.0, 120.0) ' View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
additionalConstraints: BoxConstraints(w=110.0, h=120.0) ' parentData: offset=Offset(80.0, 100.0) (can use size)\n'
''') ' constraints: BoxConstraints(unconstrained)\n'
' size: Size(110.0, 120.0)\n'
' additionalConstraints: BoxConstraints(w=110.0, h=120.0)\n',
)
); );
}); });
} }
......
...@@ -783,21 +783,27 @@ void main() { ...@@ -783,21 +783,27 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
Stack(), Stack(),
); );
final String exception = tester.takeException().toString();
expect( expect(
tester.takeException().toString(), exception, startsWith(
'No Directionality widget found.\n' 'No Directionality widget found.\n'
"Stack widgets require a Directionality widget ancestor to resolve the 'alignment' argument.\n" "Stack widgets require a Directionality widget ancestor to resolve the 'alignment' argument.\n"
"The default value for 'alignment' is AlignmentDirectional.topStart, which requires a text direction.\n" "The default value for 'alignment' is AlignmentDirectional.topStart, which requires a text direction.\n"
'The specific widget that could not find a Directionality ancestor was:\n' 'The specific widget that could not find a Directionality ancestor was:\n'
' Stack\n' ' Stack\n'
'The ownership chain for the affected widget is: "Stack ← [root]"\n' 'The ownership chain for the affected widget is: "Stack ← ', // Omitted full ownership chain because it is not relevant for the test.
));
expect(
exception, endsWith(
'← [root]"\n' // End of ownership chain.
'Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the ' 'Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the '
'top of your application widget tree. It determines the ambient reading direction and is used, for ' 'top of your application widget tree. It determines the ambient reading direction and is used, for '
'example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve ' 'example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve '
'EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.\n' 'EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.\n'
'Instead of providing a Directionality widget, another solution would be passing a non-directional ' 'Instead of providing a Directionality widget, another solution would be passing a non-directional '
"'alignment', or an explicit 'textDirection', to the Stack.", "'alignment', or an explicit 'textDirection', to the Stack.",
); ));
}); });
testWidgets('Can update clipBehavior of IndexedStack', testWidgets('Can update clipBehavior of IndexedStack',
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/src/widgets/view.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Widgets running with runApp can find View', (WidgetTester tester) async {
FlutterView? viewOf;
FlutterView? viewMaybeOf;
runApp(
Builder(
builder: (BuildContext context) {
viewOf = View.of(context);
viewMaybeOf = View.maybeOf(context);
return Container();
},
),
);
expect(viewOf, isNotNull);
expect(viewOf, isA<FlutterView>());
expect(viewMaybeOf, isNotNull);
expect(viewMaybeOf, isA<FlutterView>());
});
testWidgets('Widgets running with pumpWidget can find View', (WidgetTester tester) async {
FlutterView? view;
FlutterView? viewMaybeOf;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
view = View.of(context);
viewMaybeOf = View.maybeOf(context);
return Container();
},
),
);
expect(view, isNotNull);
expect(view, isA<FlutterView>());
expect(viewMaybeOf, isNotNull);
expect(viewMaybeOf, isA<FlutterView>());
});
testWidgets('cannot find View behind a LookupBoundary', (WidgetTester tester) async {
await tester.pumpWidget(
LookupBoundary(
child: Container(),
),
);
final BuildContext context = tester.element(find.byType(Container));
expect(View.maybeOf(context), isNull);
expect(
() => View.of(context),
throwsA(isA<FlutterError>().having(
(FlutterError error) => error.message,
'message',
contains('The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.'),
)),
);
});
}
...@@ -4277,11 +4277,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ...@@ -4277,11 +4277,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
final Element element = tester.element(find.byType(Directionality).first); final Element element = tester.element(find.byType(Directionality).first);
Element? root; Element? root;
element.visitAncestorElements((Element ancestor) { element.visitAncestorElements((Element ancestor) {
if (root == null) { root = ancestor;
root = ancestor;
// Stop traversing ancestors.
return false;
}
return true; return true;
}); });
expect(root, isNotNull); expect(root, isNotNull);
......
...@@ -553,12 +553,24 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -553,12 +553,24 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
EnginePhase phase = EnginePhase.sendSemanticsUpdate, EnginePhase phase = EnginePhase.sendSemanticsUpdate,
]) { ]) {
return TestAsyncUtils.guard<void>(() { return TestAsyncUtils.guard<void>(() {
binding.attachRootWidget(widget); return _pumpWidget(
binding.scheduleFrame(); binding.wrapWithDefaultView(widget),
return binding.pump(duration, phase); duration,
phase,
);
}); });
} }
Future<void> _pumpWidget(
Widget widget, [
Duration? duration,
EnginePhase phase = EnginePhase.sendSemanticsUpdate,
]) {
binding.attachRootWidget(widget);
binding.scheduleFrame();
return binding.pump(duration, phase);
}
@override @override
Future<List<Duration>> handlePointerEventRecord(Iterable<PointerEventRecord> records) { Future<List<Duration>> handlePointerEventRecord(Iterable<PointerEventRecord> records) {
assert(records != null); assert(records != null);
...@@ -697,7 +709,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -697,7 +709,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
// The interval following the last frame doesn't have to be within the fullDuration. // The interval following the last frame doesn't have to be within the fullDuration.
Duration elapsed = Duration.zero; Duration elapsed = Duration.zero;
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
binding.attachRootWidget(target); binding.attachRootWidget(binding.wrapWithDefaultView(target));
binding.scheduleFrame(); binding.scheduleFrame();
while (elapsed < maxDuration) { while (elapsed < maxDuration) {
await binding.pump(interval); await binding.pump(interval);
...@@ -720,12 +732,14 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -720,12 +732,14 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
'therefore no restoration data has been collected to restore from. Did you forget to wrap ' 'therefore no restoration data has been collected to restore from. Did you forget to wrap '
'your widget tree in a RootRestorationScope?', 'your widget tree in a RootRestorationScope?',
); );
final Widget widget = ((binding.renderViewElement! as RenderObjectToWidgetElement<RenderObject>).widget as RenderObjectToWidgetAdapter<RenderObject>).child!; return TestAsyncUtils.guard<void>(() async {
final TestRestorationData restorationData = binding.restorationManager.restorationData; final Widget widget = ((binding.renderViewElement! as RenderObjectToWidgetElement<RenderObject>).widget as RenderObjectToWidgetAdapter<RenderObject>).child!;
runApp(Container(key: UniqueKey())); final TestRestorationData restorationData = binding.restorationManager.restorationData;
await pump(); runApp(Container(key: UniqueKey()));
binding.restorationManager.restoreFrom(restorationData); await pump();
return pumpWidget(widget); binding.restorationManager.restoreFrom(restorationData);
return _pumpWidget(widget);
});
} }
/// Retrieves the current restoration data from the [RestorationManager]. /// Retrieves the current restoration data from the [RestorationManager].
......
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