From a45727d81360b278cc51c5a9ae429529038cf069 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 25 Jan 2023 14:22:00 -0800 Subject: [PATCH] Add MediaQuery to View (#118004) * Add MediaQuery to View * unify API * fix test * add test * comment * better doc * Apply suggestions from code review Co-authored-by: Greg Spencer Co-authored-by: Greg Spencer --- packages/flutter/lib/src/widgets/app.dart | 3 +- .../flutter/lib/src/widgets/media_query.dart | 246 ++++++---- packages/flutter/lib/src/widgets/view.dart | 34 +- .../flutter/test/material/debug_test.dart | 39 +- .../flutter/test/material/scaffold_test.dart | 43 +- .../test/widgets/animated_container_test.dart | 2 +- packages/flutter/test/widgets/app_test.dart | 26 ++ .../flutter/test/widgets/container_test.dart | 100 ++-- .../custom_multi_child_layout_test.dart | 3 +- packages/flutter/test/widgets/debug_test.dart | 13 +- .../test/widgets/focus_manager_test.dart | 5 +- .../flutter/test/widgets/init_state_test.dart | 2 +- .../flutter/test/widgets/keep_alive_test.dart | 281 ++++++----- .../widgets/list_view_viewporting_test.dart | 14 +- .../test/widgets/media_query_test.dart | 437 +++++++++++++++++- .../test/widgets/selectable_text_test.dart | 8 - .../slotted_render_object_widget_test.dart | 7 +- packages/flutter/test/widgets/table_test.dart | 20 +- .../flutter_test/test/widget_tester_test.dart | 4 +- 19 files changed, 934 insertions(+), 353 deletions(-) diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index 931623c6b806..9e915a79c0ff 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -1730,8 +1730,7 @@ class _WidgetsAppState extends State with WidgetsBindingObserver { child: title, ); - final MediaQueryData? data = MediaQuery.maybeOf(context); - if (!widget.useInheritedMediaQuery || data == null) { + if (!widget.useInheritedMediaQuery || MediaQuery.maybeOf(context) == null) { child = MediaQuery.fromWindow( child: child, ); diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index 890dd98186e3..68f38b653d2d 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -138,8 +138,8 @@ enum _MediaQueryAspect { class MediaQueryData { /// Creates data for a media query with explicit values. /// - /// Consider using [MediaQueryData.fromWindow] to create data based on a - /// [dart:ui.PlatformDispatcher]. + /// Consider using [MediaQueryData.fromView] to create data based on a + /// [dart:ui.FlutterView]. const MediaQueryData({ this.size = Size.zero, this.devicePixelRatio = 1.0, @@ -167,24 +167,60 @@ class MediaQueryData { /// window's metrics change. For example, see /// [WidgetsBindingObserver.didChangeMetrics] or /// [dart:ui.PlatformDispatcher.onMetricsChanged]. - MediaQueryData.fromWindow(ui.FlutterView window) - : size = window.physicalSize / window.devicePixelRatio, - devicePixelRatio = window.devicePixelRatio, - textScaleFactor = window.platformDispatcher.textScaleFactor, - platformBrightness = window.platformDispatcher.platformBrightness, - padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio), - viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio), - viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio), - systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio), - accessibleNavigation = window.platformDispatcher.accessibilityFeatures.accessibleNavigation, - invertColors = window.platformDispatcher.accessibilityFeatures.invertColors, - disableAnimations = window.platformDispatcher.accessibilityFeatures.disableAnimations, - boldText = window.platformDispatcher.accessibilityFeatures.boldText, - highContrast = window.platformDispatcher.accessibilityFeatures.highContrast, - alwaysUse24HourFormat = window.platformDispatcher.alwaysUse24HourFormat, - navigationMode = NavigationMode.traditional, - gestureSettings = DeviceGestureSettings.fromWindow(window), - displayFeatures = window.displayFeatures; + factory MediaQueryData.fromWindow(ui.FlutterView window) => MediaQueryData.fromView(window); + + /// Creates data for a [MediaQuery] based on the given `view`. + /// + /// If provided, the `platformData` is used to fill in the platform-specific + /// aspects of the newly created [MediaQueryData]. If `platformData` is null, + /// the `view`'s [PlatformDispatcher] is consulted to construct the + /// platform-specific data. + /// + /// Data which is exposed directly on the [FlutterView] is considered + /// view-specific. Data which is only exposed via the + /// [FlutterView.platformDispatcher] property is considered platform-specific. + /// + /// Callers of this method should ensure that they also register for + /// notifications so that the [MediaQueryData] can be updated when any data + /// used to construct it changes. Notifications to consider are: + /// + /// * [WidgetsBindingObserver.didChangeMetrics] or + /// [dart:ui.PlatformDispatcher.onMetricsChanged], + /// * [WidgetsBindingObserver.didChangeAccessibilityFeatures] or + /// [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged], + /// * [WidgetsBindingObserver.didChangeTextScaleFactor] or + /// [dart:ui.PlatformDispatcher.onTextScaleFactorChanged], + /// * [WidgetsBindingObserver.didChangePlatformBrightness] or + /// [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged]. + /// + /// The last three notifications are only relevant if no `platformData` is + /// provided. If `platformData` is provided, callers should ensure to call + /// this method again when it changes to keep the constructed [MediaQueryData] + /// updated. + /// + /// See also: + /// + /// * [MediaQuery.fromView], which constructs [MediaQueryData] from a provided + /// [FlutterView], makes it available to descendant widgets, and sets up + /// the appropriate notification listeners to keep the data updated. + MediaQueryData.fromView(ui.FlutterView view, {MediaQueryData? platformData}) + : size = view.physicalSize / view.devicePixelRatio, + devicePixelRatio = view.devicePixelRatio, + textScaleFactor = platformData?.textScaleFactor ?? view.platformDispatcher.textScaleFactor, + platformBrightness = platformData?.platformBrightness ?? view.platformDispatcher.platformBrightness, + padding = EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio), + viewPadding = EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio), + viewInsets = EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio), + systemGestureInsets = EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio), + accessibleNavigation = platformData?.accessibleNavigation ?? view.platformDispatcher.accessibilityFeatures.accessibleNavigation, + invertColors = platformData?.invertColors ?? view.platformDispatcher.accessibilityFeatures.invertColors, + disableAnimations = platformData?.disableAnimations ?? view.platformDispatcher.accessibilityFeatures.disableAnimations, + boldText = platformData?.boldText ?? view.platformDispatcher.accessibilityFeatures.boldText, + highContrast = platformData?.highContrast ?? view.platformDispatcher.accessibilityFeatures.highContrast, + alwaysUse24HourFormat = platformData?.alwaysUse24HourFormat ?? view.platformDispatcher.alwaysUse24HourFormat, + navigationMode = platformData?.navigationMode ?? NavigationMode.traditional, + gestureSettings = DeviceGestureSettings.fromWindow(view), + displayFeatures = view.displayFeatures; /// The size of the media in logical pixels (e.g, the size of the screen). /// @@ -889,17 +925,43 @@ class MediaQuery extends InheritedModel<_MediaQueryAspect> { /// and its dependents are updated when `window` changes, instead of /// rebuilding the whole widget tree. /// - /// This should be inserted into the widget tree when the [MediaQuery] view - /// padding is consumed by a widget in such a way that the view padding is no - /// longer exposed to the widget's descendants or siblings. - /// /// The [child] argument is required and must not be null. static Widget fromWindow({ Key? key, required Widget child, }) { - return _MediaQueryFromWindow( + return _MediaQueryFromView( + key: key, + view: WidgetsBinding.instance.window, + ignoreParentData: true, + child: child, + ); + } + + /// Wraps the [child] in a [MediaQuery] which is built using data from the + /// provided [view]. + /// + /// The [MediaQuery] is constructed using the platform-specific data of the + /// surrounding [MediaQuery] and the view-specific data of the provided + /// [view]. If no surrounding [MediaQuery] exists, the platform-specific data + /// is generated from the [PlatformDispatcher] associated with the provided + /// [view]. Any information that's exposed via the [PlatformDispatcher] is + /// considered platform-specific. Data exposed directly on the [FlutterView] + /// (excluding its [FlutterView.platformDispatcher] property) is considered + /// view-specific. + /// + /// The injected [MediaQuery] automatically updates when any of the data used + /// to construct it changes. + /// + /// The [view] and [child] argument is required and must not be null. + static Widget fromView({ + Key? key, + required FlutterView view, + required Widget child, + }) { + return _MediaQueryFromView( key: key, + view: view, child: child, ); } @@ -1399,99 +1461,121 @@ enum NavigationMode { directional, } -/// Provides a [MediaQuery] which is built and updated using the latest -/// [WidgetsBinding.window] values. -/// -/// Receives `window` updates by listening to [WidgetsBinding]. -/// -/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and -/// its dependents when `window` changes, instead of rebuilding the entire -/// widget tree. -/// -/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it. -/// -/// See also: -/// -/// * [MediaQuery], which establishes a subtree in which media queries resolve -/// to a [MediaQueryData]. -class _MediaQueryFromWindow extends StatefulWidget { - /// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its - /// descendants using the `window` to keep [MediaQueryData] up to date. - /// - /// The [child] must not be null. - const _MediaQueryFromWindow({ +class _MediaQueryFromView extends StatefulWidget { + const _MediaQueryFromView({ super.key, + required this.view, + this.ignoreParentData = false, required this.child, }); - /// {@macro flutter.widgets.ProxyWidget.child} + final FlutterView view; + final bool ignoreParentData; final Widget child; @override - State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState(); + State<_MediaQueryFromView> createState() => _MediaQueryFromViewState(); } -class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver { +class _MediaQueryFromViewState extends State<_MediaQueryFromView> with WidgetsBindingObserver { + MediaQueryData? _parentData; + MediaQueryData? _data; + @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } - // ACCESSIBILITY + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateParentData(); + _updateData(); + assert(_data != null); + } @override - void didChangeAccessibilityFeatures() { - setState(() { - // The properties of window have changed. We use them in our build - // function, so we need setState(), but we don't cache anything locally. - }); + void didUpdateWidget(_MediaQueryFromView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.ignoreParentData != oldWidget.ignoreParentData) { + _updateParentData(); + } + if (_data == null || oldWidget.view != widget.view) { + _updateData(); + } + assert(_data != null); + } + + void _updateParentData() { + _parentData = widget.ignoreParentData ? null : MediaQuery.maybeOf(context); + _data = null; // _updateData must be called again after changing parent data. } - // METRICS + void _updateData() { + final MediaQueryData newData = MediaQueryData.fromView(widget.view, platformData: _parentData); + if (newData != _data) { + setState(() { + _data = newData; + }); + } + } + + @override + void didChangeAccessibilityFeatures() { + // If we have a parent, it dictates our accessibility features. If we don't + // have a parent, we get our accessibility features straight from the + // PlatformDispatcher and need to update our data in response to the + // PlatformDispatcher changing its accessibility features setting. + if (_parentData == null) { + _updateData(); + } + } @override void didChangeMetrics() { - setState(() { - // The properties of window have changed. We use them in our build - // function, so we need setState(), but we don't cache anything locally. - }); + _updateData(); } @override void didChangeTextScaleFactor() { - setState(() { - // The textScaleFactor property of window has changed. We reference - // window in our build function, so we need to call setState(), but - // we don't need to cache anything locally. - }); + // If we have a parent, it dictates our text scale factor. If we don't have + // a parent, we get our text scale factor from the PlatformDispatcher and + // need to update our data in response to the PlatformDispatcher changing + // its text scale factor setting. + if (_parentData == null) { + _updateData(); + } } - // RENDERING @override void didChangePlatformBrightness() { - setState(() { - // The platformBrightness property of window has changed. We reference - // window in our build function, so we need to call setState(), but - // we don't need to cache anything locally. - }); + // If we have a parent, it dictates our platform brightness. If we don't + // have a parent, we get our platform brightness from the PlatformDispatcher + // and need to update our data in response to the PlatformDispatcher + // changing its platform brightness setting. + if (_parentData == null) { + _updateData(); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); } @override Widget build(BuildContext context) { - MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance.window); - if (!kReleaseMode) { - data = data.copyWith(platformBrightness: debugBrightnessOverride); + MediaQueryData effectiveData = _data!; + // If we get our platformBrightness from the PlatformDispatcher (i.e. we have no parentData) replace it + // with the debugBrightnessOverride in non-release mode. + if (!kReleaseMode && _parentData == null && effectiveData.platformBrightness != debugBrightnessOverride) { + effectiveData = effectiveData.copyWith(platformBrightness: debugBrightnessOverride); } return MediaQuery( - data: data, + data: effectiveData, child: widget.child, ); } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } } diff --git a/packages/flutter/lib/src/widgets/view.dart b/packages/flutter/lib/src/widgets/view.dart index 0a2068599fa3..14f87c6d10df 100644 --- a/packages/flutter/lib/src/widgets/view.dart +++ b/packages/flutter/lib/src/widgets/view.dart @@ -6,10 +6,14 @@ import 'dart:ui' show FlutterView; import 'framework.dart'; import 'lookup_boundary.dart'; +import 'media_query.dart'; /// Injects a [FlutterView] into the tree and makes it available to descendants /// within the same [LookupBoundary] via [View.of] and [View.maybeOf]. /// +/// The provided [child] is wrapped in a [MediaQuery] constructed from the given +/// [view]. +/// /// 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 @@ -20,15 +24,26 @@ import 'lookup_boundary.dart'; /// [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 { +class View extends StatelessWidget { /// Injects the provided [view] into the widget tree. - View({required this.view, required super.child}) : super(key: GlobalObjectKey(view)); + View({required this.view, required this.child}) : super(key: GlobalObjectKey(view)); /// The [FlutterView] to be injected into the tree. final FlutterView view; + /// {@macro flutter.widgets.ProxyWidget.child} + final Widget child; + @override - bool updateShouldNotify(View oldWidget) => view != oldWidget.view; + Widget build(BuildContext context) { + return _ViewScope( + view: view, + child: MediaQuery.fromView( + view: view, + child: child, + ), + ); + } /// Returns the [FlutterView] that the provided `context` will render into. /// @@ -47,7 +62,7 @@ class View extends InheritedWidget { /// * [View.of], which throws instead of returning null if no [FlutterView] /// is found. static FlutterView? maybeOf(BuildContext context) { - return LookupBoundary.dependOnInheritedWidgetOfExactType(context)?.view; + return LookupBoundary.dependOnInheritedWidgetOfExactType<_ViewScope>(context)?.view; } /// Returns the [FlutterView] that the provided `context` will render into. @@ -70,7 +85,7 @@ class View extends InheritedWidget { final FlutterView? result = maybeOf(context); assert(() { if (result == null) { - final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType(context); + final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<_ViewScope>(context); final List information = [ if (hiddenByBoundary) ...[ ErrorSummary('View.of() was called with a context that does not have access to a View widget.'), @@ -92,3 +107,12 @@ class View extends InheritedWidget { return result!; } } + +class _ViewScope extends InheritedWidget { + const _ViewScope({required this.view, required super.child}); + + final FlutterView view; + + @override + bool updateShouldNotify(_ViewScope oldWidget) => view != oldWidget.view; +} diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart index b955a1cba29f..c9f7a3bcb491 100644 --- a/packages/flutter/test/material/debug_test.dart +++ b/packages/flutter/test/material/debug_test.dart @@ -193,7 +193,7 @@ void main() { ' Semantics\n' ' Localizations\n' ' MediaQuery\n' - ' _MediaQueryFromWindow\n' + ' _MediaQueryFromView\n' ' Semantics\n' ' _FocusInheritedScope\n' ' Focus\n' @@ -235,6 +235,9 @@ void main() { ' HeroControllerScope\n' ' ScrollConfiguration\n' ' MaterialApp\n' + ' MediaQuery\n' + ' _MediaQueryFromView\n' + ' _ViewScope\n' ' View-[GlobalObjectKey TestWindow#00000]\n' ' [root]\n' ' Typically, the Scaffold widget is introduced by the MaterialApp\n' @@ -251,18 +254,15 @@ void main() { ); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: ScaffoldMessenger( - key: scaffoldMessengerKey, - child: Builder( - builder: (BuildContext context) { - return Scaffold( - key: scaffoldKey, - body: Container(), - ); - }, - ), + child: ScaffoldMessenger( + key: scaffoldMessengerKey, + child: Builder( + builder: (BuildContext context) { + return Scaffold( + key: scaffoldKey, + body: Container(), + ); + }, ), ), )); @@ -278,12 +278,9 @@ void main() { // Pump widget to rebuild without ScaffoldMessenger await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Scaffold( - key: scaffoldKey, - body: Container(), - ), + child: Scaffold( + key: scaffoldKey, + body: Container(), ), )); // Tap SnackBarAction to dismiss. @@ -378,8 +375,10 @@ void main() { ' ScrollNotificationObserver\n' ' _ScaffoldScope\n' ' Scaffold-[LabeledGlobalKey#00000]\n' - ' MediaQuery\n' ' Directionality\n' + ' MediaQuery\n' + ' _MediaQueryFromView\n' + ' _ViewScope\n' ' View-[GlobalObjectKey TestWindow#00000]\n' ' [root]\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n' diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 12709de908be..f905d0cfc2d2 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -136,12 +136,6 @@ void main() { ), ); } - await tester.pumpWidget(boilerplate(Scaffold( - appBar: AppBar(title: const Text('Title')), - body: Container(key: bodyKey), - ), - )); - expect(tester.takeException(), isFlutterError); await tester.pumpWidget(MaterialApp( home: Scaffold( @@ -2391,24 +2385,21 @@ void main() { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - key: tapTarget, - onTap: () { - ScaffoldMessenger.of(context); - }, - behavior: HitTestBehavior.opaque, - child: const SizedBox( - height: 100.0, - width: 100.0, - ), - ); - }, - ), + child: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + key: tapTarget, + onTap: () { + ScaffoldMessenger.of(context); + }, + behavior: HitTestBehavior.opaque, + child: const SizedBox( + height: 100.0, + width: 100.0, + ), + ); + }, ), ), )); @@ -2459,8 +2450,10 @@ void main() { ' ScrollNotificationObserver\n' ' _ScaffoldScope\n' ' Scaffold\n' - ' MediaQuery\n' ' Directionality\n' + ' MediaQuery\n' + ' _MediaQueryFromView\n' + ' _ViewScope\n' ' View-[GlobalObjectKey TestWindow#e6136]\n' ' [root]\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n' diff --git a/packages/flutter/test/widgets/animated_container_test.dart b/packages/flutter/test/widgets/animated_container_test.dart index 476df620e56e..ce9f17c64302 100644 --- a/packages/flutter/test/widgets/animated_container_test.dart +++ b/packages/flutter/test/widgets/animated_container_test.dart @@ -77,7 +77,7 @@ void main() { ' │ decoration: BoxDecoration:\n' ' │ color: Color(0xff0000ff)\n' ' │ configuration: ImageConfiguration(bundle:\n' - ' │ PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' + ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' ' │ android)\n' ' │\n' ' └─child: RenderLimitedBox#00000\n' diff --git a/packages/flutter/test/widgets/app_test.dart b/packages/flutter/test/widgets/app_test.dart index 6bcf5c1a671d..6d58a4afd2e5 100644 --- a/packages/flutter/test/widgets/app_test.dart +++ b/packages/flutter/test/widgets/app_test.dart @@ -644,6 +644,32 @@ void main() { expect(MediaQuery.of(capturedContext), isNotNull); }); + testWidgets("WidgetsApp doesn't have dependency on MediaQuery", (WidgetTester tester) async { + int routeBuildCount = 0; + + final Widget widget = WidgetsApp( + color: const Color.fromARGB(255, 255, 255, 255), + onGenerateRoute: (_) { + return PageRouteBuilder(pageBuilder: (_, __, ___) { + routeBuildCount++; + return const Placeholder(); + }); + }, + ); + + await tester.pumpWidget( + MediaQuery(data: const MediaQueryData(textScaleFactor: 10), child: widget), + ); + + expect(routeBuildCount, equals(1)); + + await tester.pumpWidget( + MediaQuery(data: const MediaQueryData(textScaleFactor: 20), child: widget), + ); + + expect(routeBuildCount, equals(1)); + }); + testWidgets('WidgetsApp provides meta based shortcuts for iOS and macOS', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); final SelectAllSpy selectAllSpy = SelectAllSpy(); diff --git a/packages/flutter/test/widgets/container_test.dart b/packages/flutter/test/widgets/container_test.dart index 7e3a507d197e..28c3ef6419ad 100644 --- a/packages/flutter/test/widgets/container_test.dart +++ b/packages/flutter/test/widgets/container_test.dart @@ -78,7 +78,7 @@ void main() { ' │ decoration: BoxDecoration:\n' ' │ color: Color(0x7f0000ff)\n' ' │ configuration: ImageConfiguration(bundle:\n' - ' │ PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' + ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' ' │ android)\n' ' │\n' ' └─child: _RenderColoredBox#00000\n' @@ -114,7 +114,7 @@ void main() { ' decoration: BoxDecoration:\n' ' color: Color(0xffffff00)\n' ' configuration: ImageConfiguration(bundle:\n' - ' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' + ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' ' android)\n', ), ); @@ -123,7 +123,8 @@ void main() { box.toStringDeep(), equalsIgnoringHashCodes( 'RenderPadding#00000 relayoutBoundary=up1\n' - ' │ creator: Padding ← Container ← Align ← View-[GlobalObjectKey\n' + ' │ creator: Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← 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' @@ -132,6 +133,7 @@ void main() { ' │\n' ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\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' @@ -140,20 +142,21 @@ void main() { ' │\n' ' └─child: RenderDecoratedBox#00000\n' ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n' - ' │ Align ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ Align ← MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ size: Size(53.0, 78.0)\n' ' │ decoration: BoxDecoration:\n' ' │ color: Color(0x7f0000ff)\n' ' │ configuration: ImageConfiguration(bundle:\n' - ' │ PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' + ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' ' │ android)\n' ' │\n' ' └─child: _RenderColoredBox#00000\n' ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n' - ' │ Container ← Align ← View-[GlobalObjectKey TestWindow#00000] ←\n' - ' │ [root]\n' + ' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n' + ' │ _ViewScope ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ size: Size(53.0, 78.0)\n' @@ -161,8 +164,8 @@ void main() { ' │\n' ' └─child: RenderPadding#00000\n' ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n' - ' │ Padding ← Container ← Align ← View-[GlobalObjectKey\n' - ' │ TestWindow#00000] ← [root]\n' + ' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n' + ' │ ← _ViewScope ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ size: Size(53.0, 78.0)\n' @@ -170,8 +173,9 @@ void main() { ' │\n' ' └─child: RenderPositionedBox#00000\n' ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n' - ' │ ConstrainedBox ← Padding ← Container ← Align ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestWindow#00000] ← ⋯\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' @@ -181,8 +185,8 @@ void main() { ' │\n' ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n' - ' │ ConstrainedBox ← Padding ← Container ← Align ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← ⋯\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' @@ -191,15 +195,15 @@ void main() { ' └─child: RenderDecoratedBox#00000\n' ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' - ' View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' MediaQuery ← _MediaQueryFromView ← ⋯\n' ' parentData: (can use size)\n' ' constraints: BoxConstraints(w=25.0, h=33.0)\n' ' size: Size(25.0, 33.0)\n' ' decoration: BoxDecoration:\n' ' color: Color(0xffffff00)\n' ' configuration: ImageConfiguration(bundle:\n' - ' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' - ' android)\n' + ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' + ' android)\n', ), ); @@ -207,7 +211,8 @@ void main() { box.toStringDeep(minLevel: DiagnosticLevel.fine), equalsIgnoringHashCodes( 'RenderPadding#00000 relayoutBoundary=up1\n' - ' │ creator: Padding ← Container ← Align ← View-[GlobalObjectKey\n' + ' │ creator: Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← 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' @@ -219,6 +224,7 @@ void main() { ' │\n' ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\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' @@ -229,7 +235,8 @@ void main() { ' │\n' ' └─child: RenderDecoratedBox#00000\n' ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n' - ' │ Align ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ Align ← MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ layer: null\n' @@ -244,13 +251,13 @@ void main() { ' │ gradient: null\n' ' │ shape: rectangle\n' ' │ configuration: ImageConfiguration(bundle:\n' - ' │ PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' + ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' ' │ android)\n' ' │\n' ' └─child: _RenderColoredBox#00000\n' ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n' - ' │ Container ← Align ← View-[GlobalObjectKey TestWindow#00000] ←\n' - ' │ [root]\n' + ' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n' + ' │ _ViewScope ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ layer: null\n' @@ -260,8 +267,8 @@ void main() { ' │\n' ' └─child: RenderPadding#00000\n' ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n' - ' │ Padding ← Container ← Align ← View-[GlobalObjectKey\n' - ' │ TestWindow#00000] ← [root]\n' + ' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n' + ' │ ← _ViewScope ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ layer: null\n' @@ -272,8 +279,9 @@ void main() { ' │\n' ' └─child: RenderPositionedBox#00000\n' ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n' - ' │ ConstrainedBox ← Padding ← Container ← Align ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestWindow#00000] ← ⋯\n' ' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n' ' │ constraints: BoxConstraints(w=39.0, h=64.0)\n' ' │ layer: null\n' @@ -286,8 +294,8 @@ void main() { ' │\n' ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n' - ' │ ConstrainedBox ← Padding ← Container ← Align ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← ⋯\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' @@ -298,7 +306,7 @@ void main() { ' └─child: RenderDecoratedBox#00000\n' ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' - ' View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' MediaQuery ← _MediaQueryFromView ← ⋯\n' ' parentData: (can use size)\n' ' constraints: BoxConstraints(w=25.0, h=33.0)\n' ' layer: null\n' @@ -313,8 +321,8 @@ void main() { ' gradient: null\n' ' shape: rectangle\n' ' configuration: ImageConfiguration(bundle:\n' - ' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' - ' android)\n' + ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' + ' android)\n', ), ); @@ -323,7 +331,8 @@ void main() { equalsIgnoringHashCodes( 'RenderPadding#00000 relayoutBoundary=up1\n' ' │ needsCompositing: false\n' - ' │ creator: Padding ← Container ← Align ← View-[GlobalObjectKey\n' + ' │ creator: Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← 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' @@ -338,6 +347,7 @@ void main() { ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' │ needsCompositing: false\n' ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\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' @@ -351,7 +361,8 @@ void main() { ' └─child: RenderDecoratedBox#00000\n' ' │ needsCompositing: false\n' ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n' - ' │ Align ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ Align ← MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ layer: null\n' @@ -368,14 +379,14 @@ void main() { ' │ gradient: null\n' ' │ shape: rectangle\n' ' │ configuration: ImageConfiguration(bundle:\n' - ' │ PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' + ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' ' │ android)\n' ' │\n' ' └─child: _RenderColoredBox#00000\n' ' │ needsCompositing: false\n' ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n' - ' │ Container ← Align ← View-[GlobalObjectKey TestWindow#00000] ←\n' - ' │ [root]\n' + ' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n' + ' │ _ViewScope ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ layer: null\n' @@ -388,8 +399,8 @@ void main() { ' └─child: RenderPadding#00000\n' ' │ needsCompositing: false\n' ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n' - ' │ Padding ← Container ← Align ← View-[GlobalObjectKey\n' - ' │ TestWindow#00000] ← [root]\n' + ' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n' + ' │ ← _ViewScope ← View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ layer: null\n' @@ -403,8 +414,9 @@ void main() { ' └─child: RenderPositionedBox#00000\n' ' │ needsCompositing: false\n' ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n' - ' │ ConstrainedBox ← Padding ← Container ← Align ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestWindow#00000] ← ⋯\n' ' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n' ' │ constraints: BoxConstraints(w=39.0, h=64.0)\n' ' │ layer: null\n' @@ -420,8 +432,8 @@ void main() { ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' ' │ needsCompositing: false\n' ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n' - ' │ ConstrainedBox ← Padding ← Container ← Align ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← ⋯\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' @@ -435,7 +447,7 @@ void main() { ' needsCompositing: false\n' ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' - ' View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' MediaQuery ← _MediaQueryFromView ← ⋯\n' ' parentData: (can use size)\n' ' constraints: BoxConstraints(w=25.0, h=33.0)\n' ' layer: null\n' @@ -452,8 +464,8 @@ void main() { ' gradient: null\n' ' shape: rectangle\n' ' configuration: ImageConfiguration(bundle:\n' - ' PlatformAssetBundle#00000(), devicePixelRatio: 1.0, platform:\n' - ' android)\n' + ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' + ' android)\n', ), ); diff --git a/packages/flutter/test/widgets/custom_multi_child_layout_test.dart b/packages/flutter/test/widgets/custom_multi_child_layout_test.dart index 5fd8c0455e2e..732ae02ea92e 100644 --- a/packages/flutter/test/widgets/custom_multi_child_layout_test.dart +++ b/packages/flutter/test/widgets/custom_multi_child_layout_test.dart @@ -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 ← View-[GlobalObjectKey\n' + ' CustomMultiChildLayout ← Center ← MediaQuery ←\n' + ' _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' ' TestWindow#00000] ← [root]\n' ' parentData: offset=Offset(0.0, 0.0); id=null\n' ' constraints: MISSING\n' diff --git a/packages/flutter/test/widgets/debug_test.dart b/packages/flutter/test/widgets/debug_test.dart index 30e2329ff195..ee38464adbb5 100644 --- a/packages/flutter/test/widgets/debug_test.dart +++ b/packages/flutter/test/widgets/debug_test.dart @@ -96,8 +96,11 @@ void main() { }); testWidgets('debugCheckHasMediaQuery control test', (WidgetTester tester) async { - await tester.pumpWidget( - Builder( + // Cannot use tester.pumpWidget here because it wraps the widget in a View, + // which introduces a MediaQuery ancestor. + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { late FlutterError error; try { @@ -339,3 +342,9 @@ void main() { expect(renderObject.debugLayer?.debugCreator, isNotNull); }); } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index dd36f86a0ee8..53501fc62ebd 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -1228,8 +1228,9 @@ void main() { equalsIgnoringHashCodes( 'FocusManager#00000\n' ' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n' - ' │ primaryFocusCreator: Container-[GlobalKey#00000] ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ primaryFocusCreator: Container-[GlobalKey#00000] ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestWindow#00000] ← [root]\n' ' │\n' ' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n' ' │ IN FOCUS PATH\n' diff --git a/packages/flutter/test/widgets/init_state_test.dart b/packages/flutter/test/widgets/init_state_test.dart index 828893db68c9..e70267e55e6d 100644 --- a/packages/flutter/test/widgets/init_state_test.dart +++ b/packages/flutter/test/widgets/init_state_test.dart @@ -30,7 +30,7 @@ class TestWidgetState extends State { 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(['Parent', 'View', 'RenderObjectToWidgetAdapter'])); + expect(ancestors, containsAllInOrder(['Parent', 'View', 'RenderObjectToWidgetAdapter'])); }); } diff --git a/packages/flutter/test/widgets/keep_alive_test.dart b/packages/flutter/test/widgets/keep_alive_test.dart index 5249f5c311ac..3b5f9e6dc84d 100644 --- a/packages/flutter/test/widgets/keep_alive_test.dart +++ b/packages/flutter/test/widgets/keep_alive_test.dart @@ -303,7 +303,7 @@ void main() { ' │ ScrollDirection.idle)\n' ' │ anchor: 0.0\n' ' │\n' - ' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n' + ' └─center child: RenderSliverPadding#00000 relayoutBoundary=up1\n' ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' ' │ constraints: SliverConstraints(AxisDirection.down,\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' @@ -314,49 +314,63 @@ void main() { ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n' ' │ cacheExtent: 850.0)\n' - ' │ currently live children: 0 to 2\n' + ' │ padding: EdgeInsets.zero\n' + ' │ textDirection: ltr\n' ' │\n' - ' ├─child with index 0: RenderLimitedBox#00000\n' - ' │ │ parentData: index=0; layoutOffset=0.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ painter: _PlaceholderPainter#00000()\n' - ' │ preferredSize: Size(Infinity, Infinity)\n' - ' │\n' - ' ├─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here - ' │ │ parentData: index=1; layoutOffset=400.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ painter: _PlaceholderPainter#00000()\n' - ' │ preferredSize: Size(Infinity, Infinity)\n' - ' │\n' - ' └─child with index 2: RenderLimitedBox#00000 NEEDS-PAINT\n' - ' │ parentData: index=2; layoutOffset=800.0\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ maxWidth: 400.0\n' - ' │ maxHeight: 400.0\n' + ' └─child: RenderSliverFixedExtentList#00000 relayoutBoundary=up2\n' + ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: SliverConstraints(AxisDirection.down,\n' + ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' + ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' + ' │ crossAxisDirection: AxisDirection.right,\n' + ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0,\n' + ' │ cacheOrigin: 0.0)\n' + ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' + ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n' + ' │ cacheExtent: 850.0)\n' + ' │ currently live children: 0 to 2\n' + ' │\n' + ' ├─child with index 0: RenderLimitedBox#00000\n' + ' │ │ parentData: index=0; layoutOffset=0.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ painter: _PlaceholderPainter#00000()\n' + ' │ preferredSize: Size(Infinity, Infinity)\n' ' │\n' - ' └─child: RenderCustomPaint#00000 NEEDS-PAINT\n' - ' parentData: (can use size)\n' - ' constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' size: Size(800.0, 400.0)\n' - ' painter: _PlaceholderPainter#00000()\n' - ' preferredSize: Size(Infinity, Infinity)\n', + ' ├─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here + ' │ │ parentData: index=1; layoutOffset=400.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ painter: _PlaceholderPainter#00000()\n' + ' │ preferredSize: Size(Infinity, Infinity)\n' + ' │\n' + ' └─child with index 2: RenderLimitedBox#00000 NEEDS-PAINT\n' + ' │ parentData: index=2; layoutOffset=800.0\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ maxWidth: 400.0\n' + ' │ maxHeight: 400.0\n' + ' │\n' + ' └─child: RenderCustomPaint#00000 NEEDS-PAINT\n' + ' parentData: (can use size)\n' + ' constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' size: Size(800.0, 400.0)\n' + ' painter: _PlaceholderPainter#00000()\n' + ' preferredSize: Size(Infinity, Infinity)\n', )); const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(true); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); @@ -463,7 +477,7 @@ void main() { ' │ ScrollDirection.idle)\n' ' │ anchor: 0.0\n' ' │\n' - ' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n' + ' └─center child: RenderSliverPadding#00000 relayoutBoundary=up1\n' ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' ' │ constraints: SliverConstraints(AxisDirection.down,\n' ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' @@ -474,92 +488,105 @@ void main() { ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n' ' │ cacheExtent: 1100.0)\n' - ' │ currently live children: 4 to 7\n' - ' │\n' - ' ├─child with index 4: RenderLimitedBox#00000 NEEDS-PAINT\n' - ' │ │ parentData: index=4; layoutOffset=1600.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000 NEEDS-PAINT\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ painter: _PlaceholderPainter#00000()\n' - ' │ preferredSize: Size(Infinity, Infinity)\n' - ' │\n' - ' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0 - ' │ │ parentData: index=5; layoutOffset=2000.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ painter: _PlaceholderPainter#00000()\n' - ' │ preferredSize: Size(Infinity, Infinity)\n' + ' │ padding: EdgeInsets.zero\n' + ' │ textDirection: ltr\n' ' │\n' - ' ├─child with index 6: RenderLimitedBox#00000\n' - ' │ │ parentData: index=6; layoutOffset=2400.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ painter: _PlaceholderPainter#00000()\n' - ' │ preferredSize: Size(Infinity, Infinity)\n' - ' │\n' - ' ├─child with index 7: RenderLimitedBox#00000 NEEDS-PAINT\n' - ' ╎ │ parentData: index=7; layoutOffset=2800.0\n' - ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ │ size: Size(800.0, 400.0)\n' - ' ╎ │ maxWidth: 400.0\n' - ' ╎ │ maxHeight: 400.0\n' - ' ╎ │\n' - ' ╎ └─child: RenderCustomPaint#00000 NEEDS-PAINT\n' - ' ╎ parentData: (can use size)\n' - ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ size: Size(800.0, 400.0)\n' - ' ╎ painter: _PlaceholderPainter#00000()\n' - ' ╎ preferredSize: Size(Infinity, Infinity)\n' - ' ╎\n' - ' ╎╌child with index 0 (kept alive but not laid out): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being kept alive but not laid out - ' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n' - ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ │ size: Size(800.0, 400.0)\n' - ' ╎ │ maxWidth: 400.0\n' - ' ╎ │ maxHeight: 400.0\n' - ' ╎ │\n' - ' ╎ └─child: RenderCustomPaint#00000\n' - ' ╎ parentData: (can use size)\n' - ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ size: Size(800.0, 400.0)\n' - ' ╎ painter: _PlaceholderPainter#00000()\n' - ' ╎ preferredSize: Size(Infinity, Infinity)\n' - ' ╎\n' // <----- dashed line ends here - ' └╌child with index 3 (kept alive but not laid out): RenderLimitedBox#00000\n' - ' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ maxWidth: 400.0\n' - ' │ maxHeight: 400.0\n' + ' └─child: RenderSliverFixedExtentList#00000 relayoutBoundary=up2\n' + ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: SliverConstraints(AxisDirection.down,\n' + ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' + ' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' + ' │ crossAxisDirection: AxisDirection.right,\n' + ' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 1100.0,\n' + ' │ cacheOrigin: -250.0)\n' + ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' + ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n' + ' │ cacheExtent: 1100.0)\n' + ' │ currently live children: 4 to 7\n' + ' │\n' + ' ├─child with index 4: RenderLimitedBox#00000 NEEDS-PAINT\n' + ' │ │ parentData: index=4; layoutOffset=1600.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000 NEEDS-PAINT\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ painter: _PlaceholderPainter#00000()\n' + ' │ preferredSize: Size(Infinity, Infinity)\n' + ' │\n' + ' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0 + ' │ │ parentData: index=5; layoutOffset=2000.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ painter: _PlaceholderPainter#00000()\n' + ' │ preferredSize: Size(Infinity, Infinity)\n' ' │\n' - ' └─child: RenderCustomPaint#00000\n' - ' parentData: (can use size)\n' - ' constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' size: Size(800.0, 400.0)\n' - ' painter: _PlaceholderPainter#00000()\n' - ' preferredSize: Size(Infinity, Infinity)\n', + ' ├─child with index 6: RenderLimitedBox#00000\n' + ' │ │ parentData: index=6; layoutOffset=2400.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ painter: _PlaceholderPainter#00000()\n' + ' │ preferredSize: Size(Infinity, Infinity)\n' + ' │\n' + ' ├─child with index 7: RenderLimitedBox#00000 NEEDS-PAINT\n' + ' ╎ │ parentData: index=7; layoutOffset=2800.0\n' + ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ │ size: Size(800.0, 400.0)\n' + ' ╎ │ maxWidth: 400.0\n' + ' ╎ │ maxHeight: 400.0\n' + ' ╎ │\n' + ' ╎ └─child: RenderCustomPaint#00000 NEEDS-PAINT\n' + ' ╎ parentData: (can use size)\n' + ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ size: Size(800.0, 400.0)\n' + ' ╎ painter: _PlaceholderPainter#00000()\n' + ' ╎ preferredSize: Size(Infinity, Infinity)\n' + ' ╎\n' + ' ╎╌child with index 0 (kept alive but not laid out): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being kept alive but not laid out + ' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n' + ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ │ size: Size(800.0, 400.0)\n' + ' ╎ │ maxWidth: 400.0\n' + ' ╎ │ maxHeight: 400.0\n' + ' ╎ │\n' + ' ╎ └─child: RenderCustomPaint#00000\n' + ' ╎ parentData: (can use size)\n' + ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ size: Size(800.0, 400.0)\n' + ' ╎ painter: _PlaceholderPainter#00000()\n' + ' ╎ preferredSize: Size(Infinity, Infinity)\n' + ' ╎\n' + ' └╌child with index 3 (kept alive but not laid out): RenderLimitedBox#00000\n' + ' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ maxWidth: 400.0\n' + ' │ maxHeight: 400.0\n' + ' │\n' + ' └─child: RenderCustomPaint#00000\n' + ' parentData: (can use size)\n' + ' constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' size: Size(800.0, 400.0)\n' + ' painter: _PlaceholderPainter#00000()\n' + ' preferredSize: Size(Infinity, Infinity)\n', )); }, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87876 - } diff --git a/packages/flutter/test/widgets/list_view_viewporting_test.dart b/packages/flutter/test/widgets/list_view_viewporting_test.dart index 11a79060b6b5..277b20e0d151 100644 --- a/packages/flutter/test/widgets/list_view_viewporting_test.dart +++ b/packages/flutter/test/widgets/list_view_viewporting_test.dart @@ -326,7 +326,7 @@ void main() { expect( list.toStringDeep(minLevel: DiagnosticLevel.info), equalsIgnoringHashCodes( - 'RenderSliverList#00000 relayoutBoundary=up1\n' + 'RenderSliverList#00000 relayoutBoundary=up2\n' ' │ needs compositing\n' ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' ' │ constraints: SliverConstraints(AxisDirection.down,\n' @@ -339,7 +339,7 @@ void main() { ' │ maxPaintExtent: 300.0, cacheExtent: 300.0)\n' ' │ currently live children: 0 to 2\n' ' │\n' - ' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' + ' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up3\n' ' │ │ needs compositing\n' ' │ │ parentData: index=0; layoutOffset=0.0 (can use size)\n' ' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n' @@ -349,7 +349,7 @@ void main() { ' │ │ diagnosis: insufficient data to draw conclusion (less than five\n' ' │ │ repaints)\n' ' │ │\n' - ' │ └─child: RenderConstrainedBox#00000 relayoutBoundary=up3\n' + ' │ └─child: RenderConstrainedBox#00000 relayoutBoundary=up4\n' ' │ │ parentData: (can use size)\n' ' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n' ' │ │ size: Size(800.0, 100.0)\n' @@ -368,7 +368,7 @@ void main() { ' │ size: Size(800.0, 100.0)\n' ' │ additionalConstraints: BoxConstraints(biggest)\n' ' │\n' - ' ├─child with index 1: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' + ' ├─child with index 1: RenderRepaintBoundary#00000 relayoutBoundary=up3\n' ' │ │ needs compositing\n' ' │ │ parentData: index=1; layoutOffset=100.0 (can use size)\n' ' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n' @@ -378,7 +378,7 @@ void main() { ' │ │ diagnosis: insufficient data to draw conclusion (less than five\n' ' │ │ repaints)\n' ' │ │\n' - ' │ └─child: RenderConstrainedBox#00000 relayoutBoundary=up3\n' + ' │ └─child: RenderConstrainedBox#00000 relayoutBoundary=up4\n' ' │ │ parentData: (can use size)\n' ' │ │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n' ' │ │ size: Size(800.0, 100.0)\n' @@ -397,7 +397,7 @@ void main() { ' │ size: Size(800.0, 100.0)\n' ' │ additionalConstraints: BoxConstraints(biggest)\n' ' │\n' - ' └─child with index 2: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' + ' └─child with index 2: RenderRepaintBoundary#00000 relayoutBoundary=up3\n' ' │ needs compositing\n' ' │ parentData: index=2; layoutOffset=200.0 (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n' @@ -407,7 +407,7 @@ void main() { ' │ diagnosis: insufficient data to draw conclusion (less than five\n' ' │ repaints)\n' ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up3\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up4\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n' ' │ size: Size(800.0, 100.0)\n' diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index 0dea1093ca78..5cecc50738d4 100644 --- a/packages/flutter/test/widgets/media_query_test.dart +++ b/packages/flutter/test/widgets/media_query_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show Brightness, DisplayFeature, DisplayFeatureState, DisplayFeatureType, GestureSettings, ViewConfiguration; +import 'dart:ui' show Brightness, DisplayFeature, DisplayFeatureState, DisplayFeatureType, GestureSettings, PlatformDispatcher, ViewConfiguration, WindowPadding; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -45,8 +45,11 @@ class _MediaQueryAspectVariant extends TestVariant<_MediaQueryAspectCase> { void main() { testWidgets('MediaQuery does not have a default', (WidgetTester tester) async { bool tested = false; - await tester.pumpWidget( - Builder( + // Cannot use tester.pumpWidget here because it wraps the widget in a View, + // which introduces a MediaQuery ancestor. + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { tested = true; MediaQuery.of(context); // should throw @@ -108,8 +111,11 @@ void main() { testWidgets('MediaQuery.maybeOf defaults to null', (WidgetTester tester) async { bool tested = false; - await tester.pumpWidget( - Builder( + // Cannot use tester.pumpWidget here because it wraps the widget in a View, + // which introduces a MediaQuery ancestor. + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( builder: (BuildContext context) { final MediaQueryData? data = MediaQuery.maybeOf(context); expect(data, isNull); @@ -154,6 +160,358 @@ void main() { expect(data.displayFeatures, isEmpty); }); + testWidgets('MediaQueryData.fromView uses platformData if provided', (WidgetTester tester) async { + const MediaQueryData platformData = MediaQueryData( + textScaleFactor: 1234, + platformBrightness: Brightness.dark, + accessibleNavigation: true, + invertColors: true, + disableAnimations: true, + boldText: true, + highContrast: true, + alwaysUse24HourFormat: true, + navigationMode: NavigationMode.directional, + ); + + final TestView view = TestView( + physicalSize: const Size(300, 600), + devicePixelRatio: 3.0, + padding: const TestWindowPadding(15), + viewPadding: const TestWindowPadding(75), + viewInsets: const TestWindowPadding(45), + systemGestureInsets: const TestWindowPadding(9), + ); + + final MediaQueryData data = MediaQueryData.fromView(view, platformData: platformData); + expect(data, hasOneLineDescription); + expect(data.hashCode, data.copyWith().hashCode); + expect(data.size, view.physicalSize / view.devicePixelRatio); + expect(data.devicePixelRatio, view.devicePixelRatio); + expect(data.textScaleFactor, platformData.textScaleFactor); + expect(data.platformBrightness, platformData.platformBrightness); + expect(data.padding, EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio)); + expect(data.accessibleNavigation, platformData.accessibleNavigation); + expect(data.invertColors, platformData.invertColors); + expect(data.disableAnimations, platformData.disableAnimations); + expect(data.boldText, platformData.boldText); + expect(data.highContrast, platformData.highContrast); + expect(data.alwaysUse24HourFormat, platformData.alwaysUse24HourFormat); + expect(data.navigationMode, platformData.navigationMode); + expect(data.gestureSettings, DeviceGestureSettings.fromWindow(view)); + expect(data.displayFeatures, view.displayFeatures); + }); + + testWidgets('MediaQueryData.fromView uses data from platformDispatcher if no platformData is provided', (WidgetTester tester) async { + final TestPlatformDispatcher platformDispatcher = TestPlatformDispatcher(platformDispatcher: tester.binding.platformDispatcher); + platformDispatcher + ..textScaleFactorTestValue = 123 + ..platformBrightnessTestValue = Brightness.dark + ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; + addTearDown(() => platformDispatcher.clearAllTestValues()); + + final TestView view = TestView( + platformDispatcher: platformDispatcher, + physicalSize: const Size(300, 600), + devicePixelRatio: 3.0, + padding: const TestWindowPadding(15), + viewPadding: const TestWindowPadding(75), + viewInsets: const TestWindowPadding(45), + systemGestureInsets: const TestWindowPadding(9), + ); + + final MediaQueryData data = MediaQueryData.fromView(view); + expect(data, hasOneLineDescription); + expect(data.hashCode, data.copyWith().hashCode); + expect(data.size, view.physicalSize / view.devicePixelRatio); + expect(data.devicePixelRatio, view.devicePixelRatio); + expect(data.textScaleFactor, platformDispatcher.textScaleFactor); + expect(data.platformBrightness, platformDispatcher.platformBrightness); + expect(data.padding, EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio)); + expect(data.accessibleNavigation, platformDispatcher.accessibilityFeatures.accessibleNavigation); + expect(data.invertColors, platformDispatcher.accessibilityFeatures.invertColors); + expect(data.disableAnimations, platformDispatcher.accessibilityFeatures.disableAnimations); + expect(data.boldText, platformDispatcher.accessibilityFeatures.boldText); + expect(data.highContrast, platformDispatcher.accessibilityFeatures.highContrast); + expect(data.alwaysUse24HourFormat, platformDispatcher.alwaysUse24HourFormat); + expect(data.navigationMode, NavigationMode.traditional); + expect(data.gestureSettings, DeviceGestureSettings.fromWindow(view)); + expect(data.displayFeatures, view.displayFeatures); + }); + + testWidgets('MediaQuery.fromView injects a new MediaQuery with data from view, preserving platform-specific data', (WidgetTester tester) async { + const MediaQueryData platformData = MediaQueryData( + textScaleFactor: 1234, + platformBrightness: Brightness.dark, + accessibleNavigation: true, + invertColors: true, + disableAnimations: true, + boldText: true, + highContrast: true, + alwaysUse24HourFormat: true, + navigationMode: NavigationMode.directional, + ); + + final TestView view = TestView( + physicalSize: const Size(300, 600), + devicePixelRatio: 3.0, + padding: const TestWindowPadding(15), + viewPadding: const TestWindowPadding(75), + viewInsets: const TestWindowPadding(45), + systemGestureInsets: const TestWindowPadding(9), + ); + + late MediaQueryData data; + await tester.pumpWidget(MediaQuery( + data: platformData, + child: MediaQuery.fromView( + view: view, + child: Builder( + builder: (BuildContext context) { + data = MediaQuery.of(context); + return const Placeholder(); + }, + ) + ) + )); + + expect(data, isNot(platformData)); + expect(data.size, view.physicalSize / view.devicePixelRatio); + expect(data.devicePixelRatio, view.devicePixelRatio); + expect(data.textScaleFactor, platformData.textScaleFactor); + expect(data.platformBrightness, platformData.platformBrightness); + expect(data.padding, EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio)); + expect(data.accessibleNavigation, platformData.accessibleNavigation); + expect(data.invertColors, platformData.invertColors); + expect(data.disableAnimations, platformData.disableAnimations); + expect(data.boldText, platformData.boldText); + expect(data.highContrast, platformData.highContrast); + expect(data.alwaysUse24HourFormat, platformData.alwaysUse24HourFormat); + expect(data.navigationMode, platformData.navigationMode); + expect(data.gestureSettings, DeviceGestureSettings.fromWindow(view)); + expect(data.displayFeatures, view.displayFeatures); + }); + + testWidgets('MediaQuery.fromView injects a new MediaQuery with data from view when no sourrounding MediaQuery exists', (WidgetTester tester) async { + final TestPlatformDispatcher platformDispatcher = TestPlatformDispatcher(platformDispatcher: tester.binding.platformDispatcher); + platformDispatcher + ..textScaleFactorTestValue = 123 + ..platformBrightnessTestValue = Brightness.dark + ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; + addTearDown(() => platformDispatcher.clearAllTestValues()); + + final TestView view = TestView( + platformDispatcher: platformDispatcher, + physicalSize: const Size(300, 600), + devicePixelRatio: 3.0, + padding: const TestWindowPadding(15), + viewPadding: const TestWindowPadding(75), + viewInsets: const TestWindowPadding(45), + systemGestureInsets: const TestWindowPadding(9), + ); + + late MediaQueryData data; + MediaQueryData? outerData; + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( + builder: (BuildContext context) { + outerData = MediaQuery.maybeOf(context); + return MediaQuery.fromView( + view: view, + child: Builder( + builder: (BuildContext context) { + data = MediaQuery.of(context); + return const Placeholder(); + }, + ) + ); + }, + ), + ); + + expect(outerData, isNull); + expect(data.size, view.physicalSize / view.devicePixelRatio); + expect(data.devicePixelRatio, view.devicePixelRatio); + expect(data.textScaleFactor, platformDispatcher.textScaleFactor); + expect(data.platformBrightness, platformDispatcher.platformBrightness); + expect(data.padding, EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio)); + expect(data.accessibleNavigation, platformDispatcher.accessibilityFeatures.accessibleNavigation); + expect(data.invertColors, platformDispatcher.accessibilityFeatures.invertColors); + expect(data.disableAnimations, platformDispatcher.accessibilityFeatures.disableAnimations); + expect(data.boldText, platformDispatcher.accessibilityFeatures.boldText); + expect(data.highContrast, platformDispatcher.accessibilityFeatures.highContrast); + expect(data.alwaysUse24HourFormat, platformDispatcher.alwaysUse24HourFormat); + expect(data.navigationMode, NavigationMode.traditional); + expect(data.gestureSettings, DeviceGestureSettings.fromWindow(view)); + expect(data.displayFeatures, view.displayFeatures); + }); + + testWidgets('MediaQuery.fromView updates on notifications (no parent data)', (WidgetTester tester) async { + tester.binding.platformDispatcher + ..textScaleFactorTestValue = 123 + ..platformBrightnessTestValue = Brightness.dark + ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; + addTearDown(() => tester.binding.platformDispatcher.clearAllTestValues()); + tester.binding.window.devicePixelRatioTestValue = 44; + addTearDown(() => tester.binding.window.clearAllTestValues()); + + late MediaQueryData data; + MediaQueryData? outerData; + int rebuildCount = 0; + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: Builder( + builder: (BuildContext context) { + outerData = MediaQuery.maybeOf(context); + return MediaQuery.fromView( + view: tester.binding.window, + child: Builder( + builder: (BuildContext context) { + rebuildCount++; + data = MediaQuery.of(context); + return const Placeholder(); + }, + ), + ); + }, + ), + ); + + expect(outerData, isNull); + expect(rebuildCount, 1); + + expect(data.textScaleFactor, 123); + tester.binding.platformDispatcher.textScaleFactorTestValue = 456; + await tester.pump(); + expect(data.textScaleFactor, 456); + expect(rebuildCount, 2); + + expect(data.platformBrightness, Brightness.dark); + tester.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; + await tester.pump(); + expect(data.platformBrightness, Brightness.light); + expect(rebuildCount, 3); + + expect(data.accessibleNavigation, true); + tester.binding.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures(); + await tester.pump(); + expect(data.accessibleNavigation, false); + expect(rebuildCount, 4); + + expect(data.devicePixelRatio, 44); + tester.binding.window.devicePixelRatioTestValue = 55; + await tester.pump(); + expect(data.devicePixelRatio, 55); + expect(rebuildCount, 5); + }); + + testWidgets('MediaQuery.fromView updates on notifications (with parent data)', (WidgetTester tester) async { + tester.binding.platformDispatcher + ..textScaleFactorTestValue = 123 + ..platformBrightnessTestValue = Brightness.dark + ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; + addTearDown(() => tester.binding.platformDispatcher.clearAllTestValues()); + tester.binding.window.devicePixelRatioTestValue = 44; + addTearDown(() => tester.binding.window.clearAllTestValues()); + + late MediaQueryData data; + int rebuildCount = 0; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData( + textScaleFactor: 44, + platformBrightness: Brightness.dark, + accessibleNavigation: true, + ), + child: MediaQuery.fromView( + view: tester.binding.window, + child: Builder( + builder: (BuildContext context) { + rebuildCount++; + data = MediaQuery.of(context); + return const Placeholder(); + }, + ), + ), + ), + ); + + expect(rebuildCount, 1); + + expect(data.textScaleFactor, 44); + tester.binding.platformDispatcher.textScaleFactorTestValue = 456; + await tester.pump(); + expect(data.textScaleFactor, 44); + expect(rebuildCount, 1); + + expect(data.platformBrightness, Brightness.dark); + tester.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; + await tester.pump(); + expect(data.platformBrightness, Brightness.dark); + expect(rebuildCount, 1); + + expect(data.accessibleNavigation, true); + tester.binding.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures(); + await tester.pump(); + expect(data.accessibleNavigation, true); + expect(rebuildCount, 1); + + expect(data.devicePixelRatio, 44); + tester.binding.window.devicePixelRatioTestValue = 55; + await tester.pump(); + expect(data.devicePixelRatio, 55); + expect(rebuildCount, 2); + }); + + testWidgets('MediaQuery.fromView updates when parent data changes', (WidgetTester tester) async { + late MediaQueryData data; + int rebuildCount = 0; + double textScaleFactor = 55; + late StateSetter stateSetter; + await tester.pumpWidget( + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + stateSetter = setState; + return MediaQuery( + data: MediaQueryData(textScaleFactor: textScaleFactor), + child: MediaQuery.fromView( + view: tester.binding.window, + child: Builder( + builder: (BuildContext context) { + rebuildCount++; + data = MediaQuery.of(context); + return const Placeholder(); + }, + ), + ), + ); + }, + ), + ); + + expect(rebuildCount, 1); + expect(data.textScaleFactor, 55); + + stateSetter(() { + textScaleFactor = 66; + }); + await tester.pump(); + expect(data.textScaleFactor, 66); + expect(rebuildCount, 2); + }); + testWidgets('MediaQueryData.copyWith defaults to source', (WidgetTester tester) async { final MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance.window); final MediaQueryData copied = data.copyWith(); @@ -753,19 +1111,17 @@ void main() { }); testWidgets('MediaQuery.fromWindow creates a MediaQuery', (WidgetTester tester) async { - bool hasMediaQueryAsParentOutside = false; - bool hasMediaQueryAsParentInside = false; + MediaQuery? mediaQueryOutside; + MediaQuery? mediaQueryInside; await tester.pumpWidget( Builder( builder: (BuildContext context) { - hasMediaQueryAsParentOutside = - context.findAncestorWidgetOfExactType() != null; + mediaQueryOutside = context.findAncestorWidgetOfExactType(); return MediaQuery.fromWindow( child: Builder( builder: (BuildContext context) { - hasMediaQueryAsParentInside = - context.findAncestorWidgetOfExactType() != null; + mediaQueryInside = context.findAncestorWidgetOfExactType(); return const SizedBox(); }, ), @@ -774,8 +1130,8 @@ void main() { ), ); - expect(hasMediaQueryAsParentOutside, false); - expect(hasMediaQueryAsParentInside, true); + expect(mediaQueryInside, isNotNull); + expect(mediaQueryOutside, isNot(mediaQueryInside)); }); testWidgets('MediaQueryData.fromWindow is created using window values', (WidgetTester tester) async { @@ -1150,3 +1506,58 @@ void main() { ] )); } + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} + +class TestView implements FlutterView { + TestView({ + PlatformDispatcher? platformDispatcher, + required this.physicalSize, + required this.devicePixelRatio, + required this.padding, + required this.viewPadding, + required this.viewInsets, + required this.systemGestureInsets, + }) : _platformDispatcher = platformDispatcher; + + @override + PlatformDispatcher get platformDispatcher => _platformDispatcher!; + final PlatformDispatcher? _platformDispatcher; + @override + final Size physicalSize; + @override + final double devicePixelRatio; + @override + final WindowPadding padding; + @override + final WindowPadding viewPadding; + @override + final WindowPadding viewInsets; + @override + final WindowPadding systemGestureInsets; + @override + final ViewConfiguration viewConfiguration = const ViewConfiguration(); + @override + final List displayFeatures = []; + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +class TestWindowPadding implements WindowPadding { + const TestWindowPadding(this.value); + final double value; + + @override + double get bottom => value; + @override + double get left => value; + @override + double get right => value; + @override + double get top => value; +} diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index a3cc2986eac7..a1fdd05de2b1 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -2707,14 +2707,6 @@ void main() { semantics.dispose(); }); - testWidgets('SelectableText throws when not descended from a MediaQuery widget', (WidgetTester tester) async { - const Widget selectableText = SelectableText('something'); - await tester.pumpWidget(selectableText); - final dynamic exception = tester.takeException(); - expect(exception, isFlutterError); - expect(exception.toString(), startsWith('No MediaQuery widget ancestor found.\nSelectableText widgets require a MediaQuery widget ancestor.')); - }); - testWidgets('onTap is called upon tap', (WidgetTester tester) async { int tapCount = 0; await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/slotted_render_object_widget_test.dart b/packages/flutter/test/widgets/slotted_render_object_widget_test.dart index 257951a86eb6..b8816d7bfba4 100644 --- a/packages/flutter/test/widgets/slotted_render_object_widget_test.dart +++ b/packages/flutter/test/widgets/slotted_render_object_widget_test.dart @@ -221,14 +221,16 @@ void main() { tester.renderObject(find.byType(_Diagonal)).toStringDeep(), equalsIgnoringHashCodes( '_RenderDiagonal#00000 relayoutBoundary=up1\n' - ' │ creator: _Diagonal ← Align ← Directionality ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ creator: _Diagonal ← Align ← Directionality ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← 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(190.0, 220.0)\n' ' │\n' ' ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' │ creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' ' │ constraints: BoxConstraints(unconstrained)\n' @@ -237,6 +239,7 @@ void main() { ' │\n' ' └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n' + ' MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' ' View-[GlobalObjectKey TestWindow#00000] ← [root]\n' ' parentData: offset=Offset(80.0, 100.0) (can use size)\n' ' constraints: BoxConstraints(unconstrained)\n' diff --git a/packages/flutter/test/widgets/table_test.dart b/packages/flutter/test/widgets/table_test.dart index 345c941c8e51..ea9fc30ed933 100644 --- a/packages/flutter/test/widgets/table_test.dart +++ b/packages/flutter/test/widgets/table_test.dart @@ -879,24 +879,24 @@ void main() { expect( element.toStringDeep(minLevel: DiagnosticLevel.info), equalsIgnoringHashCodes( - 'Table-[GlobalKey#00000](dependencies: [Directionality], renderObject: RenderTable#00000)\n' - '├Text("A")\n' + 'Table-[GlobalKey#00000](dependencies: [Directionality, MediaQuery], renderObject: RenderTable#00000)\n' + '├Text("A", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "A", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("B")\n' + '├Text("B", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "B", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("C")\n' + '├Text("C", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "C", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("D")\n' + '├Text("D", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "D", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("EEE")\n' + '├Text("EEE", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "EEE", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("F")\n' + '├Text("F", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "F", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("G")\n' + '├Text("G", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "G", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '├Text("H")\n' + '├Text("H", dependencies: [MediaQuery])\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "H", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' - '└Text("III")\n' + '└Text("III", dependencies: [MediaQuery])\n' ' └RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "III", dependencies: [Directionality], renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n', ), ); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index a0b4f8a58364..713406e5cf1f 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -99,7 +99,7 @@ void main() { final String? message = failure.message; expect(message, contains('Expected: no matching nodes in the widget tree\n')); - expect(message, contains('Actual: _TextFinder:\n')); + expect(message, contains('Actual: _TextFinder:\n')); expect(message, contains('Which: means one was found but none were expected\n')); }); @@ -117,7 +117,7 @@ void main() { final String? message = failure.message; expect(message, contains('Expected: no matching nodes in the widget tree\n')); - expect(message, contains('Actual: _TextFinder:\n')); + expect(message, contains('Actual: _TextFinder:\n')); expect(message, contains('Which: means one was found but none were expected\n')); }); });