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

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: 's avatarGreg Spencer <gspencergoog@users.noreply.github.com>
Co-authored-by: 's avatarGreg Spencer <gspencergoog@users.noreply.github.com>
parent 3be330aa
......@@ -1730,8 +1730,7 @@ class _WidgetsAppState extends State<WidgetsApp> 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,
);
......
......@@ -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() {
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.
}
void _updateData() {
final MediaQueryData newData = MediaQueryData.fromView(widget.view, platformData: _parentData);
if (newData != _data) {
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.
_data = newData;
});
}
}
// METRICS
@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();
}
}
......@@ -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<View>(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<View>(context);
final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<_ViewScope>(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.'),
......@@ -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;
}
......@@ -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,8 +254,6 @@ void main() {
);
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Builder(
......@@ -264,7 +265,6 @@ void main() {
},
),
),
),
));
final List<dynamic> exceptions = <dynamic>[];
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
......@@ -278,13 +278,10 @@ 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(),
),
),
));
// Tap SnackBarAction to dismiss.
// The SnackBarAction should assert we still have an ancestor
......@@ -378,8 +375,10 @@ void main() {
' ScrollNotificationObserver\n'
' _ScaffoldScope\n'
' Scaffold-[LabeledGlobalKey<ScaffoldState>#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'
......
......@@ -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,8 +2385,6 @@ void main() {
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
......@@ -2410,7 +2402,6 @@ void main() {
},
),
),
),
));
await tester.tap(find.byKey(tapTarget));
FlutterError.onError = oldHandler;
......@@ -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'
......
......@@ -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'
......
......@@ -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<void>(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();
......
......@@ -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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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: <none> (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',
),
);
......
......@@ -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'
......
......@@ -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<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) {
tester.binding.attachRootWidget(widget);
tester.binding.scheduleFrame();
return tester.binding.pump();
}
......@@ -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'
......
......@@ -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', 'View', 'RenderObjectToWidgetAdapter<RenderBox>']));
expect(ancestors, containsAllInOrder(<String>['Parent', 'View', 'RenderObjectToWidgetAdapter<RenderBox>']));
});
}
......
......@@ -303,7 +303,21 @@ 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'
' │ 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'
' │ padding: EdgeInsets.zero\n'
' │ textDirection: ltr\n'
' │\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'
......@@ -463,7 +477,21 @@ 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'
' │ 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'
' │ padding: EdgeInsets.zero\n'
' │ textDirection: ltr\n'
' │\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'
......@@ -545,7 +573,7 @@ void main() {
' ╎ size: Size(800.0, 400.0)\n'
' ╎ painter: _PlaceholderPainter#00000()\n'
' ╎ preferredSize: Size(Infinity, Infinity)\n'
' \n' // <----- dashed line ends here
' \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'
......@@ -561,5 +589,4 @@ void main() {
' preferredSize: Size(Infinity, Infinity)\n',
));
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87876
}
......@@ -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: <none> (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: <none> (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: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, 0.0<=h<=Infinity)\n'
' │ size: Size(800.0, 100.0)\n'
......
......@@ -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<MediaQuery>() != null;
mediaQueryOutside = context.findAncestorWidgetOfExactType<MediaQuery>();
return MediaQuery.fromWindow(
child: Builder(
builder: (BuildContext context) {
hasMediaQueryAsParentInside =
context.findAncestorWidgetOfExactType<MediaQuery>() != null;
mediaQueryInside = context.findAncestorWidgetOfExactType<MediaQuery>();
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<void> 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<DisplayFeature> displayFeatures = <DisplayFeature>[];
@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;
}
......@@ -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(
......
......@@ -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'
......
......@@ -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',
),
);
......
......@@ -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:<exactly one widget with text "foo": Text("foo", textDirection: ltr)>\n'));
expect(message, contains('Actual: _TextFinder:<exactly one widget with text "foo": Text("foo", textDirection: ltr, dependencies: [MediaQuery])>\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:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo", textDirection: ltr)>\n'));
expect(message, contains('Actual: _TextFinder:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo", textDirection: ltr, dependencies: [MediaQuery])>\n'));
expect(message, contains('Which: means one was found but none were expected\n'));
});
});
......
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