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';
import 'platform_menu_bar.dart';
import 'router.dart';
import 'service_extensions.dart';
import 'view.dart';
import 'widget_inspector.dart';
export 'dart:ui' show AppLifecycleState, Locale;
......@@ -896,6 +897,22 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
@override
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.
///
/// This is called by [runApp] to configure the widget tree. Consider using
......@@ -1014,8 +1031,9 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
/// * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to
/// ensure the widget, element, and render trees are all built.
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
binding
..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
..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';
export 'src/widgets/tween_animation_builder.dart';
export 'src/widgets/unique_widget.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/visibility.dart';
export 'src/widgets/widget_inspector.dart';
......
......@@ -8,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
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();
expect(exception, isFlutterError);
final FlutterError error = exception as FlutterError;
......@@ -25,7 +25,7 @@ void main() {
expect(error.diagnostics[3], isA<DiagnosticsProperty<Element>>());
expect(error.diagnostics[4], isA<DiagnosticsBlock>());
expect(
error.toStringDeep(),
error.toStringDeep(), startsWith(
'FlutterError\n'
' No Material widget found.\n'
' Chip widgets require a Material widget ancestor within the\n'
......@@ -42,12 +42,13 @@ void main() {
' The specific widget that could not find a Material ancestor was:\n'
' Chip\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 {
await tester.pumpWidget(const BackButton());
await tester.pumpWidget(const Center(child: BackButton()));
final dynamic exception = tester.takeException();
expect(exception, isFlutterError);
final FlutterError error = exception as FlutterError;
......@@ -64,7 +65,7 @@ void main() {
expect(error.diagnostics[4], isA<DiagnosticsProperty<Element>>());
expect(error.diagnostics[5], isA<DiagnosticsBlock>());
expect(
error.toStringDeep(),
error.toStringDeep(), startsWith(
'FlutterError\n'
' No MaterialLocalizations found.\n'
' BackButton widgets require MaterialLocalizations to be provided\n'
......@@ -78,8 +79,9 @@ void main() {
' ancestor was:\n'
' BackButton\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 {
......@@ -233,6 +235,7 @@ void main() {
' HeroControllerScope\n'
' ScrollConfiguration\n'
' MaterialApp\n'
' View-[GlobalObjectKey TestWindow#00000]\n'
' [root]\n'
' Typically, the Scaffold widget is introduced by the MaterialApp\n'
' or WidgetsApp widget at the top of your application widget tree.\n'
......@@ -377,6 +380,7 @@ void main() {
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00000]\n'
' MediaQuery\n'
' Directionality\n'
' View-[GlobalObjectKey TestWindow#00000]\n'
' [root]\n'
' Typically, the ScaffoldMessenger widget is introduced by the\n'
' MaterialApp at the top of your application widget tree.\n'
......
......@@ -2458,6 +2458,7 @@ void main() {
' Scaffold\n'
' MediaQuery\n'
' Directionality\n'
' View-[GlobalObjectKey TestWindow#e6136]\n'
' [root]\n'
' Typically, the ScaffoldMessenger widget is introduced by the\n'
' MaterialApp at the top of your application widget tree.\n',
......
......@@ -7431,7 +7431,6 @@ void main() {
final dynamic exception = tester.takeException();
expect(exception, isFlutterError);
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 {
......
......@@ -122,14 +122,16 @@ void main() {
box.toStringDeep(),
equalsIgnoringHashCodes(
'RenderPadding#00000 relayoutBoundary=up1\n'
' │ creator: Padding ← Container ← Align ← [root]\n'
' │ creator: Padding ← Container ← Align ← View-[GlobalObjectKey\n'
' │ TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ size: Size(63.0, 88.0)\n'
' │ padding: EdgeInsets.all(5.0)\n'
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
' │ size: Size(53.0, 78.0)\n'
......@@ -137,7 +139,7 @@ void main() {
' │\n'
' └─child: RenderDecoratedBox#00000\n'
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
' │ Align ← [root]\n'
' │ Align ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ size: Size(53.0, 78.0)\n'
......@@ -149,7 +151,8 @@ void main() {
' │\n'
' └─child: _RenderColoredBox#00000\n'
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
' │ Container ← Align ← [root]\n'
' │ Container ← Align ← View-[GlobalObjectKey TestWindow#00000] ←\n'
' │ [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ size: Size(53.0, 78.0)\n'
......@@ -157,7 +160,8 @@ void main() {
' │\n'
' └─child: RenderPadding#00000\n'
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
' │ Padding ← Container ← Align ← [root]\n'
' │ Padding ← Container ← Align ← View-[GlobalObjectKey\n'
' │ TestWindow#00000] ← [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ size: Size(53.0, 78.0)\n'
......@@ -165,7 +169,8 @@ void main() {
' │\n'
' └─child: RenderPositionedBox#00000\n'
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
' │ ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
' │ size: Size(39.0, 64.0)\n'
......@@ -175,7 +180,8 @@ void main() {
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
' │ ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
' │ size: Size(25.0, 33.0)\n'
......@@ -184,7 +190,7 @@ void main() {
' └─child: RenderDecoratedBox#00000\n'
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
' [root]\n'
' View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
' size: Size(25.0, 33.0)\n'
......@@ -192,7 +198,7 @@ void main() {
' color: Color(0xffffff00)\n'
' configuration: ImageConfiguration(bundle:\n'
' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n'
' android)\n',
' android)\n'
),
);
......@@ -200,7 +206,8 @@ void main() {
box.toStringDeep(minLevel: DiagnosticLevel.fine),
equalsIgnoringHashCodes(
'RenderPadding#00000 relayoutBoundary=up1\n'
' │ creator: Padding ← Container ← Align ← [root]\n'
' │ creator: Padding ← Container ← Align ← View-[GlobalObjectKey\n'
' │ TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ layer: null\n'
......@@ -210,7 +217,8 @@ void main() {
' │ textDirection: null\n'
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
' │ layer: null\n'
......@@ -220,7 +228,7 @@ void main() {
' │\n'
' └─child: RenderDecoratedBox#00000\n'
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
' │ Align ← [root]\n'
' │ Align ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ layer: null\n'
......@@ -240,7 +248,8 @@ void main() {
' │\n'
' └─child: _RenderColoredBox#00000\n'
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
' │ Container ← Align ← [root]\n'
' │ Container ← Align ← View-[GlobalObjectKey TestWindow#00000] ←\n'
' │ [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ layer: null\n'
......@@ -250,7 +259,8 @@ void main() {
' │\n'
' └─child: RenderPadding#00000\n'
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
' │ Padding ← Container ← Align ← [root]\n'
' │ Padding ← Container ← Align ← View-[GlobalObjectKey\n'
' │ TestWindow#00000] ← [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ layer: null\n'
......@@ -261,7 +271,8 @@ void main() {
' │\n'
' └─child: RenderPositionedBox#00000\n'
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
' │ ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
' │ layer: null\n'
......@@ -274,7 +285,8 @@ void main() {
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
' │ ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
' │ layer: null\n'
......@@ -285,7 +297,7 @@ void main() {
' └─child: RenderDecoratedBox#00000\n'
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
' [root]\n'
' View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
' layer: null\n'
......@@ -301,7 +313,7 @@ void main() {
' shape: rectangle\n'
' configuration: ImageConfiguration(bundle:\n'
' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n'
' android)\n',
' android)\n'
),
);
......@@ -310,7 +322,8 @@ void main() {
equalsIgnoringHashCodes(
'RenderPadding#00000 relayoutBoundary=up1\n'
' │ needsCompositing: false\n'
' │ creator: Padding ← Container ← Align ← [root]\n'
' │ creator: Padding ← Container ← Align ← View-[GlobalObjectKey\n'
' │ TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ layer: null\n'
......@@ -323,7 +336,8 @@ void main() {
' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ needsCompositing: false\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
' │ layer: null\n'
......@@ -336,7 +350,7 @@ void main() {
' └─child: RenderDecoratedBox#00000\n'
' │ needsCompositing: false\n'
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
' │ Align ← [root]\n'
' │ Align ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ layer: null\n'
......@@ -359,7 +373,8 @@ void main() {
' └─child: _RenderColoredBox#00000\n'
' │ needsCompositing: false\n'
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
' │ Container ← Align ← [root]\n'
' │ Container ← Align ← View-[GlobalObjectKey TestWindow#00000] ←\n'
' │ [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ layer: null\n'
......@@ -372,7 +387,8 @@ void main() {
' └─child: RenderPadding#00000\n'
' │ needsCompositing: false\n'
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
' │ Padding ← Container ← Align ← [root]\n'
' │ Padding ← Container ← Align ← View-[GlobalObjectKey\n'
' │ TestWindow#00000] ← [root]\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ layer: null\n'
......@@ -386,7 +402,8 @@ void main() {
' └─child: RenderPositionedBox#00000\n'
' │ needsCompositing: false\n'
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
' │ ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
' │ layer: null\n'
......@@ -402,7 +419,8 @@ void main() {
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
' │ needsCompositing: false\n'
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
' │ ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
' │ layer: null\n'
......@@ -416,7 +434,7 @@ void main() {
' needsCompositing: false\n'
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
' [root]\n'
' View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
' layer: null\n'
......@@ -434,7 +452,7 @@ void main() {
' shape: rectangle\n'
' configuration: ImageConfiguration(bundle:\n'
' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n'
' android)\n',
' android)\n'
),
);
......
......@@ -372,7 +372,8 @@ void main() {
' in its parent data.\n'
' The following child has no ID: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT:\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'
' constraints: MISSING\n'
' size: MISSING\n'
......
......@@ -79,14 +79,13 @@ void main() {
expect(error.diagnostics[2], isA<DiagnosticsProperty<Element>>());
expect(
error.toStringDeep(),
equalsIgnoringHashCodes(
startsWith(
'FlutterError\n'
' No Table widget found.\n'
' Builder widgets require a Table widget ancestor.\n'
' The specific widget that could not find a Table ancestor was:\n'
' Builder\n'
' The ownership chain for the affected widget is: "Builder ←\n'
' [root]"\n',
' The ownership chain for the affected widget is: "Builder ←', // End of ownership chain omitted, not relevant for test.
),
);
}
......@@ -122,15 +121,20 @@ void main() {
);
expect(
error.toStringDeep(),
equalsIgnoringHashCodes(
startsWith(
'FlutterError\n'
' No MediaQuery widget ancestor found.\n'
' Builder widgets require a MediaQuery widget ancestor.\n'
' The specific widget that could not find a MediaQuery ancestor\n'
' was:\n'
' Builder\n'
' The ownership chain for the affected widget is: "Builder ←\n'
' [root]"\n'
' The ownership chain for the affected widget is: "Builder ←' // Full chain omitted, not relevant for test.
),
);
expect(
error.toStringDeep(),
endsWith(
'[root]"\n' // End of ownership chain.
' No MediaQuery ancestor could be found starting from the context\n'
' that was passed to MediaQuery.of(). This can happen because you\n'
' have not added a WidgetsApp, CupertinoApp, or MaterialApp widget\n'
......
......@@ -1228,7 +1228,8 @@ void main() {
equalsIgnoringHashCodes(
'FocusManager#00000\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'
' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
' │ IN FOCUS PATH\n'
......@@ -1266,7 +1267,6 @@ void main() {
});
});
group('Autofocus', () {
testWidgets(
'works when the previous focused node is detached',
......@@ -1695,7 +1695,6 @@ void main() {
debugPrint = oldDebugPrint;
}
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('FOCUS: Notified 2 dirty nodes'));
expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1')));
......
......@@ -1183,13 +1183,13 @@ void main() {
expect(exception, isFlutterError);
expect(
exception.toString(),
equalsIgnoringHashCodes(
startsWith(
'The children of `MultiChildRenderObjectElement` must each has an associated render object.\n'
'This typically means that the `_EmptyWidget` or its children\n'
'are not a subtype of `RenderObjectWidget`.\n'
'The following element does not have an associated render object:\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() {
expect(exception, isFlutterError);
expect(
exception.toString(),
equalsIgnoringHashCodes(
startsWith(
'The children of `MultiChildRenderObjectElement` must each has an associated render object.\n'
'This typically means that the `_EmptyWidget` or its children\n'
'are not a subtype of `RenderObjectWidget`.\n'
'The following element does not have an associated render object:\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> {
void main() {
testWidgets('initState() is called when we are in the tree', (WidgetTester tester) async {
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() {
expect(error.diagnostics.last, isA<ErrorHint>());
expect(
error.toStringDeep(),
equalsIgnoringHashCodes(
startsWith(
'FlutterError\n'
' No MediaQuery widget ancestor found.\n'
' Builder widgets require a MediaQuery widget ancestor.\n'
' The specific widget that could not find a MediaQuery ancestor\n'
' was:\n'
' Builder\n'
' The ownership chain for the affected widget is: "Builder ←\n'
' [root]"\n'
' The ownership chain for the affected widget is: "Builder ←', // Full ownership chain omitted, not relevant for test.
),
);
expect(
error.toStringDeep(),
endsWith(
'[root]"\n' // End of ownership chain.
' No MediaQuery ancestor could be found starting from the context\n'
' that was passed to MediaQuery.of(). This can happen because you\n'
' have not added a WidgetsApp, CupertinoApp, or MaterialApp widget\n'
......
......@@ -274,7 +274,7 @@ void main() {
expect(exception, isFlutterError);
expect(
exception.toString(),
equalsIgnoringHashCodes(
startsWith(
'Incorrect use of ParentDataWidget.\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'
......@@ -283,7 +283,7 @@ void main() {
'Usually, this indicates that at least one of the offending ParentDataWidgets listed '
'above is not placed directly inside a compatible ancestor widget.\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() {
expect(exception, isFlutterError);
expect(
exception.toString(),
equalsIgnoringHashCodes(
startsWith(
'Incorrect use of ParentDataWidget.\n'
'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 '
......@@ -320,7 +320,7 @@ void main() {
'Typically, Positioned widgets are placed directly inside Stack widgets.\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'
' 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() {
expect(exception, isFlutterError);
expect(
exception.toString(),
equalsIgnoringHashCodes(
startsWith(
'Incorrect use of ParentDataWidget.\n'
'The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type '
'FlexParentData to a RenderObject, which has been set up to accept ParentData of '
......@@ -419,7 +419,7 @@ void main() {
'Typically, Expanded widgets are placed directly inside Flex widgets.\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'
' 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() {
expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
containsAllInOrder(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
);
onPlatformViewCreatedCallBack(createdPlatformViewId);
......@@ -2495,7 +2495,7 @@ void main() {
expect(
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);
......@@ -2535,7 +2535,7 @@ void main() {
expect(
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
......@@ -2580,7 +2580,7 @@ void main() {
expect(
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
......@@ -2592,7 +2592,7 @@ void main() {
expect(
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);
......@@ -2678,7 +2678,7 @@ void main() {
expect(
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
containsAllInOrder(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
);
onPlatformViewCreatedCallBack(createdPlatformViewId);
......@@ -2687,7 +2687,7 @@ void main() {
expect(
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);
......
......@@ -219,27 +219,30 @@ void main() {
expect(
tester.renderObject(find.byType(_Diagonal)).toStringDeep(),
equalsIgnoringHashCodes(r'''
_RenderDiagonal#00000 relayoutBoundary=up1
│ creator: _Diagonal ← Align ← Directionality ← [root]
│ parentData: offset=Offset(0.0, 0.0) (can use size)
│ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)
│ size: Size(190.0, 220.0)
├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2
│ creator: SizedBox ← _Diagonal ← Align ← Directionality ← [root]
│ parentData: offset=Offset(0.0, 0.0) (can use size)
│ constraints: BoxConstraints(unconstrained)
│ size: Size(80.0, 100.0)
│ additionalConstraints: BoxConstraints(w=80.0, h=100.0)
└─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2
creator: SizedBox ← _Diagonal ← Align ← Directionality ← [root]
parentData: offset=Offset(80.0, 100.0) (can use size)
constraints: BoxConstraints(unconstrained)
size: Size(110.0, 120.0)
additionalConstraints: BoxConstraints(w=110.0, h=120.0)
''')
equalsIgnoringHashCodes(
'_RenderDiagonal#00000 relayoutBoundary=up1\n'
' │ creator: _Diagonal ← Align ← Directionality ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ size: Size(190.0, 220.0)\n'
' │\n'
' ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(unconstrained)\n'
' │ size: Size(80.0, 100.0)\n'
' │ additionalConstraints: BoxConstraints(w=80.0, h=100.0)\n'
' │\n'
' └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
' View-[GlobalObjectKey TestWindow#00000] ← [root]\n'
' 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() {
await tester.pumpWidget(
Stack(),
);
final String exception = tester.takeException().toString();
expect(
tester.takeException().toString(),
exception, startsWith(
'No Directionality widget found.\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 specific widget that could not find a Directionality ancestor was:\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 '
'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 '
'EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.\n'
'Instead of providing a Directionality widget, another solution would be passing a non-directional '
"'alignment', or an explicit 'textDirection', to the Stack.",
);
));
});
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 {
final Element element = tester.element(find.byType(Directionality).first);
Element? root;
element.visitAncestorElements((Element ancestor) {
if (root == null) {
root = ancestor;
// Stop traversing ancestors.
return false;
}
return true;
});
expect(root, isNotNull);
......
......@@ -553,10 +553,22 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
EnginePhase phase = EnginePhase.sendSemanticsUpdate,
]) {
return TestAsyncUtils.guard<void>(() {
return _pumpWidget(
binding.wrapWithDefaultView(widget),
duration,
phase,
);
});
}
Future<void> _pumpWidget(
Widget widget, [
Duration? duration,
EnginePhase phase = EnginePhase.sendSemanticsUpdate,
]) {
binding.attachRootWidget(widget);
binding.scheduleFrame();
return binding.pump(duration, phase);
});
}
@override
......@@ -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.
Duration elapsed = Duration.zero;
return TestAsyncUtils.guard<void>(() async {
binding.attachRootWidget(target);
binding.attachRootWidget(binding.wrapWithDefaultView(target));
binding.scheduleFrame();
while (elapsed < maxDuration) {
await binding.pump(interval);
......@@ -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 '
'your widget tree in a RootRestorationScope?',
);
return TestAsyncUtils.guard<void>(() async {
final Widget widget = ((binding.renderViewElement! as RenderObjectToWidgetElement<RenderObject>).widget as RenderObjectToWidgetAdapter<RenderObject>).child!;
final TestRestorationData restorationData = binding.restorationManager.restorationData;
runApp(Container(key: UniqueKey()));
await pump();
binding.restorationManager.restoreFrom(restorationData);
return pumpWidget(widget);
return _pumpWidget(widget);
});
}
/// 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