From a626f4db6f80ece52768f72aad545e54d80df9a6 Mon Sep 17 00:00:00 2001 From: pdblasi-google <109253501+pdblasi-google@users.noreply.github.com> Date: Tue, 7 Mar 2023 13:12:59 -0800 Subject: [PATCH] Reland: Removes single window assumptions from `flutter_test` (#122060) Reland: Removes single window assumptions from `flutter_test` --- packages/flutter/lib/src/widgets/binding.dart | 6 +- .../flutter/test/material/debug_test.dart | 4 +- .../flutter/test/material/scaffold_test.dart | 2 +- .../flutter/test/widgets/container_test.dart | 953 +++++----- .../custom_multi_child_layout_test.dart | 2 +- .../test/widgets/focus_manager_test.dart | 2 +- .../test/widgets/media_query_test.dart | 259 +-- .../slotted_render_object_widget_test.dart | 6 +- packages/flutter_test/lib/src/binding.dart | 15 +- packages/flutter_test/lib/src/controller.dart | 52 + packages/flutter_test/lib/src/window.dart | 1607 +++++++++++------ .../flutter_test/test/controller_test.dart | 19 + .../test/platform_dispatcher_test.dart | 68 +- .../test/utils/fake_and_mock_utils.dart | 114 ++ packages/flutter_test/test/view_test.dart | 445 +++++ packages/flutter_test/test/window_test.dart | 120 +- 16 files changed, 2333 insertions(+), 1341 deletions(-) create mode 100644 packages/flutter_test/test/utils/fake_and_mock_utils.dart create mode 100644 packages/flutter_test/test/view_test.dart diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 98124fbbe3..53f595ce7e 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -901,15 +901,13 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB /// Used by [runApp] to wrap the provided `rootWidget` in the default [View]. /// /// The [View] determines into what [FlutterView] the app is rendered into. - /// For backwards-compatibility reasons, this method currently chooses - /// [window] (which is a [FlutterView]) as the rendering target. This will - /// change in a future version of Flutter. + /// This is currently [PlatformDispatcher.implicitView] from [platformDispatcher]. /// /// The `rootWidget` widget provided to this method must not already be /// wrapped in a [View]. Widget wrapWithDefaultView(Widget rootWidget) { return View( - view: window, + view: platformDispatcher.implicitView!, child: rootWidget, ); } diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart index 919b3ded16..00882c4901 100644 --- a/packages/flutter/test/material/debug_test.dart +++ b/packages/flutter/test/material/debug_test.dart @@ -235,7 +235,7 @@ void main() { ' MediaQuery\n' ' _MediaQueryFromView\n' ' _ViewScope\n' - ' View-[GlobalObjectKey TestWindow#00000]\n' + ' View-[GlobalObjectKey TestFlutterView#00000]\n' ' [root]\n' ' Typically, the Scaffold widget is introduced by the MaterialApp\n' ' or WidgetsApp widget at the top of your application widget tree.\n' @@ -376,7 +376,7 @@ void main() { ' MediaQuery\n' ' _MediaQueryFromView\n' ' _ViewScope\n' - ' View-[GlobalObjectKey TestWindow#00000]\n' + ' View-[GlobalObjectKey TestFlutterView#00000]\n' ' [root]\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n' ' MaterialApp at the top of your application widget tree.\n' diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index a8167bbab9..3f040b1f65 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2454,7 +2454,7 @@ void main() { ' MediaQuery\n' ' _MediaQueryFromView\n' ' _ViewScope\n' - ' View-[GlobalObjectKey TestWindow#e6136]\n' + ' View-[GlobalObjectKey TestFlutterView#e6136]\n' ' [root]\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n' ' MaterialApp at the top of your application widget tree.\n', diff --git a/packages/flutter/test/widgets/container_test.dart b/packages/flutter/test/widgets/container_test.dart index 28c3ef6419..7c327bc975 100644 --- a/packages/flutter/test/widgets/container_test.dart +++ b/packages/flutter/test/widgets/container_test.dart @@ -13,7 +13,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; void main() { - testWidgets('Container control test', (WidgetTester tester) async { + group('Container control tests:', () { final Container container = Container( alignment: Alignment.bottomRight, padding: const EdgeInsets.all(7.0), @@ -39,457 +39,522 @@ void main() { ), ); - expect(container, hasOneLineDescription); + testWidgets('paints as expected', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); - await tester.pumpWidget(Align( - alignment: Alignment.topLeft, - child: container, - )); + final RenderBox box = tester.renderObject(find.byType(Container)); + expect(box, isNotNull); - final RenderBox box = tester.renderObject(find.byType(Container)); - expect(box, isNotNull); + expect(box, paints + ..rect(rect: const Rect.fromLTWH(5.0, 5.0, 53.0, 78.0), color: const Color(0xFF00FF00)) + ..rect(rect: const Rect.fromLTWH(26.0, 43.0, 25.0, 33.0), color: const Color(0xFFFFFF00)) + ..rect(rect: const Rect.fromLTWH(5.0, 5.0, 53.0, 78.0), color: const Color(0x7F0000FF)), + ); + }); - expect(box, paints - ..rect(rect: const Rect.fromLTWH(5.0, 5.0, 53.0, 78.0), color: const Color(0xFF00FF00)) - ..rect(rect: const Rect.fromLTWH(26.0, 43.0, 25.0, 33.0), color: const Color(0xFFFFFF00)) - ..rect(rect: const Rect.fromLTWH(5.0, 5.0, 53.0, 78.0), color: const Color(0x7F0000FF)), - ); + group('diagnostics', () { + testWidgets('has reasonable default diagnostics', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); - expect(box, hasAGoodToStringDeep); - expect( - box.toStringDeep(minLevel: DiagnosticLevel.info), - equalsIgnoringHashCodes( - 'RenderPadding#00000 relayoutBoundary=up1\n' - ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' - ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n' - ' │ size: Size(63.0, 88.0)\n' - ' │ padding: EdgeInsets.all(5.0)\n' - ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' - ' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n' - ' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\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: 3.0, platform:\n' - ' │ android)\n' - ' │\n' - ' └─child: _RenderColoredBox#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ behavior: opaque\n' - ' │\n' - ' └─child: RenderPadding#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ padding: EdgeInsets.all(7.0)\n' - ' │\n' - ' └─child: RenderPositionedBox#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' - ' │ alignment: Alignment.bottomRight\n' - ' │ widthFactor: expand\n' - ' │ heightFactor: expand\n' - ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\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' - ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\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: 3.0, platform:\n' - ' android)\n', - ), - ); + final RenderBox box = tester.renderObject(find.byType(Container)); - expect( - box.toStringDeep(), - equalsIgnoringHashCodes( - 'RenderPadding#00000 relayoutBoundary=up1\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' - ' │ size: Size(63.0, 88.0)\n' - ' │ padding: EdgeInsets.all(5.0)\n' - ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' - ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\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' - ' │ size: Size(53.0, 78.0)\n' - ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\n' - ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\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: 3.0, platform:\n' - ' │ android)\n' - ' │\n' - ' └─child: _RenderColoredBox#00000\n' - ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\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' - ' │ behavior: opaque\n' - ' │\n' - ' └─child: RenderPadding#00000\n' - ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\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' - ' │ padding: EdgeInsets.all(7.0)\n' - ' │\n' - ' └─child: RenderPositionedBox#00000\n' - ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\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' - ' │ alignment: Alignment.bottomRight\n' - ' │ widthFactor: expand\n' - ' │ heightFactor: expand\n' - ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' - ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\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' - ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\n' - ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' - ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\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: 3.0, platform:\n' - ' android)\n', - ), - ); + expect(container, hasOneLineDescription); + expect(box, hasAGoodToStringDeep); + }); - expect( - box.toStringDeep(minLevel: DiagnosticLevel.fine), - equalsIgnoringHashCodes( - 'RenderPadding#00000 relayoutBoundary=up1\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' - ' │ layer: null\n' - ' │ semantics node: null\n' - ' │ size: Size(63.0, 88.0)\n' - ' │ padding: EdgeInsets.all(5.0)\n' - ' │ textDirection: null\n' - ' │\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' - ' │ layer: null\n' - ' │ semantics node: null\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\n' - ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\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' - ' │ semantics node: null\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ decoration: BoxDecoration:\n' - ' │ color: Color(0x7f0000ff)\n' - ' │ image: null\n' - ' │ border: null\n' - ' │ borderRadius: null\n' - ' │ boxShadow: null\n' - ' │ gradient: null\n' - ' │ shape: rectangle\n' - ' │ configuration: ImageConfiguration(bundle:\n' - ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' - ' │ android)\n' - ' │\n' - ' └─child: _RenderColoredBox#00000\n' - ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\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' - ' │ semantics node: null\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ behavior: opaque\n' - ' │\n' - ' └─child: RenderPadding#00000\n' - ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\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' - ' │ semantics node: null\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ padding: EdgeInsets.all(7.0)\n' - ' │ textDirection: null\n' - ' │\n' - ' └─child: RenderPositionedBox#00000\n' - ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\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' - ' │ semantics node: null\n' - ' │ size: Size(39.0, 64.0)\n' - ' │ alignment: Alignment.bottomRight\n' - ' │ textDirection: null\n' - ' │ widthFactor: expand\n' - ' │ heightFactor: expand\n' - ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' - ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\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' - ' │ semantics node: null\n' - ' │ size: Size(25.0, 33.0)\n' - ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\n' - ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' - ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' - ' MediaQuery ← _MediaQueryFromView ← ⋯\n' - ' parentData: (can use size)\n' - ' constraints: BoxConstraints(w=25.0, h=33.0)\n' - ' layer: null\n' - ' semantics node: null\n' - ' size: Size(25.0, 33.0)\n' - ' decoration: BoxDecoration:\n' - ' color: Color(0xffffff00)\n' - ' image: null\n' - ' border: null\n' - ' borderRadius: null\n' - ' boxShadow: null\n' - ' gradient: null\n' - ' shape: rectangle\n' - ' configuration: ImageConfiguration(bundle:\n' - ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' - ' android)\n', - ), - ); + testWidgets('has expected info diagnostics', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); - expect( - box.toStringDeep(minLevel: DiagnosticLevel.hidden), - equalsIgnoringHashCodes( - 'RenderPadding#00000 relayoutBoundary=up1\n' - ' │ needsCompositing: false\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' - ' │ layer: null\n' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(63.0, 88.0)\n' - ' │ padding: EdgeInsets.all(5.0)\n' - ' │ textDirection: null\n' - ' │\n' - ' └─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' - ' │ layer: null\n' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\n' - ' │ needsCompositing: false\n' - ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\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' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ decoration: BoxDecoration:\n' - ' │ color: Color(0x7f0000ff)\n' - ' │ image: null\n' - ' │ border: null\n' - ' │ borderRadius: null\n' - ' │ boxShadow: null\n' - ' │ gradient: null\n' - ' │ shape: rectangle\n' - ' │ configuration: ImageConfiguration(bundle:\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 ← 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' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ behavior: opaque\n' - ' │\n' - ' └─child: RenderPadding#00000\n' - ' │ needsCompositing: false\n' - ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\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' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(53.0, 78.0)\n' - ' │ padding: EdgeInsets.all(7.0)\n' - ' │ textDirection: null\n' - ' │\n' - ' └─child: RenderPositionedBox#00000\n' - ' │ needsCompositing: false\n' - ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\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' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(39.0, 64.0)\n' - ' │ alignment: Alignment.bottomRight\n' - ' │ textDirection: null\n' - ' │ widthFactor: expand\n' - ' │ heightFactor: expand\n' - ' │\n' - ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' - ' │ needsCompositing: false\n' - ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\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' - ' │ semantics node: null\n' - ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' │ isSemanticBoundary: false\n' - ' │ size: Size(25.0, 33.0)\n' - ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' - ' │\n' - ' └─child: RenderDecoratedBox#00000\n' - ' needsCompositing: false\n' - ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' - ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' - ' MediaQuery ← _MediaQueryFromView ← ⋯\n' - ' parentData: (can use size)\n' - ' constraints: BoxConstraints(w=25.0, h=33.0)\n' - ' layer: null\n' - ' semantics node: null\n' - ' isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' - ' isSemanticBoundary: false\n' - ' size: Size(25.0, 33.0)\n' - ' decoration: BoxDecoration:\n' - ' color: Color(0xffffff00)\n' - ' image: null\n' - ' border: null\n' - ' borderRadius: null\n' - ' boxShadow: null\n' - ' gradient: null\n' - ' shape: rectangle\n' - ' configuration: ImageConfiguration(bundle:\n' - ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' - ' android)\n', - ), - ); + final RenderBox box = tester.renderObject(find.byType(Container)); - final RenderBox decoratedBox = tester.renderObject(find.byType(DecoratedBox).last); - final PaintingContext context = _MockPaintingContext(); - late FlutterError error; - try { - decoratedBox.paint(context, Offset.zero); - } on FlutterError catch (e) { - error = e; - } - expect(error, isNotNull); - expect( - error.toStringDeep(), - 'FlutterError\n' - ' BoxDecoration painter had mismatching save and restore calls.\n' - ' Before painting the decoration, the canvas save count was 0.\n' - ' After painting it, the canvas save count was 2. Every call to\n' - ' save() or saveLayer() must be matched by a call to restore().\n' - ' The decoration was:\n' - ' BoxDecoration(color: Color(0xffffff00))\n' - ' The painter was:\n' - ' BoxPainter for BoxDecoration(color: Color(0xffffff00))\n', - ); + expect( + box.toStringDeep(minLevel: DiagnosticLevel.info), + equalsIgnoringHashCodes( + 'RenderPadding#00000 relayoutBoundary=up1\n' + ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n' + ' │ size: Size(63.0, 88.0)\n' + ' │ padding: EdgeInsets.all(5.0)\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' + ' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\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: 3.0, platform:\n' + ' │ android)\n' + ' │\n' + ' └─child: _RenderColoredBox#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ behavior: opaque\n' + ' │\n' + ' └─child: RenderPadding#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ padding: EdgeInsets.all(7.0)\n' + ' │\n' + ' └─child: RenderPositionedBox#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' + ' │ alignment: Alignment.bottomRight\n' + ' │ widthFactor: expand\n' + ' │ heightFactor: expand\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\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' + ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\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: 3.0, platform:\n' + ' android)\n', + ), + ); + }); + + testWidgets('has expected debug diagnostics', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); + + final RenderBox box = tester.renderObject(find.byType(Container)); + + expect( + // Using the redundant value to ensure the test is explicitly for + // debug diagnostics, regardless of any changes to the default value. + // ignore: avoid_redundant_argument_values + box.toStringDeep(minLevel: DiagnosticLevel.debug), + equalsIgnoringHashCodes( + 'RenderPadding#00000 relayoutBoundary=up1\n' + ' │ creator: Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestFlutterView#00000] ← [root]\n' + ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n' + ' │ size: Size(63.0, 88.0)\n' + ' │ padding: EdgeInsets.all(5.0)\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' + ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' + ' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\n' + ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n' + ' │ Align ← MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestFlutterView#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: 3.0, platform:\n' + ' │ android)\n' + ' │\n' + ' └─child: _RenderColoredBox#00000\n' + ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n' + ' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n' + ' │ _ViewScope ← View-[GlobalObjectKey TestFlutterView#00000] ←\n' + ' │ [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ behavior: opaque\n' + ' │\n' + ' └─child: RenderPadding#00000\n' + ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n' + ' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n' + ' │ ← _ViewScope ← View-[GlobalObjectKey TestFlutterView#00000] ←\n' + ' │ [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ padding: EdgeInsets.all(7.0)\n' + ' │\n' + ' └─child: RenderPositionedBox#00000\n' + ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestFlutterView#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' + ' │ alignment: Alignment.bottomRight\n' + ' │ widthFactor: expand\n' + ' │ heightFactor: expand\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' + ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\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' + ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\n' + ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' + ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\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: 3.0, platform:\n' + ' android)\n', + ), + ); + }); + + testWidgets('has expected fine diagnostics', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); + + final RenderBox box = tester.renderObject(find.byType(Container)); + + expect( + box.toStringDeep(minLevel: DiagnosticLevel.fine), + equalsIgnoringHashCodes( + 'RenderPadding#00000 relayoutBoundary=up1\n' + ' │ creator: Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestFlutterView#00000] ← [root]\n' + ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ size: Size(63.0, 88.0)\n' + ' │ padding: EdgeInsets.all(5.0)\n' + ' │ textDirection: null\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' + ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' + ' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\n' + ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n' + ' │ Align ← MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ decoration: BoxDecoration:\n' + ' │ color: Color(0x7f0000ff)\n' + ' │ image: null\n' + ' │ border: null\n' + ' │ borderRadius: null\n' + ' │ boxShadow: null\n' + ' │ gradient: null\n' + ' │ shape: rectangle\n' + ' │ configuration: ImageConfiguration(bundle:\n' + ' │ PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' + ' │ android)\n' + ' │\n' + ' └─child: _RenderColoredBox#00000\n' + ' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n' + ' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n' + ' │ _ViewScope ← View-[GlobalObjectKey TestFlutterView#00000] ←\n' + ' │ [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ behavior: opaque\n' + ' │\n' + ' └─child: RenderPadding#00000\n' + ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n' + ' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n' + ' │ ← _ViewScope ← View-[GlobalObjectKey TestFlutterView#00000] ←\n' + ' │ [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ padding: EdgeInsets.all(7.0)\n' + ' │ textDirection: null\n' + ' │\n' + ' └─child: RenderPositionedBox#00000\n' + ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestFlutterView#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' + ' │ semantics node: null\n' + ' │ size: Size(39.0, 64.0)\n' + ' │ alignment: Alignment.bottomRight\n' + ' │ textDirection: null\n' + ' │ widthFactor: expand\n' + ' │ heightFactor: expand\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' + ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\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' + ' │ semantics node: null\n' + ' │ size: Size(25.0, 33.0)\n' + ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\n' + ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' + ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' + ' MediaQuery ← _MediaQueryFromView ← ⋯\n' + ' parentData: (can use size)\n' + ' constraints: BoxConstraints(w=25.0, h=33.0)\n' + ' layer: null\n' + ' semantics node: null\n' + ' size: Size(25.0, 33.0)\n' + ' decoration: BoxDecoration:\n' + ' color: Color(0xffffff00)\n' + ' image: null\n' + ' border: null\n' + ' borderRadius: null\n' + ' boxShadow: null\n' + ' gradient: null\n' + ' shape: rectangle\n' + ' configuration: ImageConfiguration(bundle:\n' + ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' + ' android)\n', + ), + ); + }); + + testWidgets('has expected hidden diagnostics', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); + + final RenderBox box = tester.renderObject(find.byType(Container)); + + expect( + box.toStringDeep(minLevel: DiagnosticLevel.hidden), + equalsIgnoringHashCodes( + 'RenderPadding#00000 relayoutBoundary=up1\n' + ' │ needsCompositing: false\n' + ' │ creator: Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestFlutterView#00000] ← [root]\n' + ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(63.0, 88.0)\n' + ' │ padding: EdgeInsets.all(5.0)\n' + ' │ textDirection: null\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' + ' │ needsCompositing: false\n' + ' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n' + ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' + ' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n' + ' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ additionalConstraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\n' + ' │ needsCompositing: false\n' + ' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n' + ' │ Align ← MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' + ' │ View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ decoration: BoxDecoration:\n' + ' │ color: Color(0x7f0000ff)\n' + ' │ image: null\n' + ' │ border: null\n' + ' │ borderRadius: null\n' + ' │ boxShadow: null\n' + ' │ gradient: null\n' + ' │ shape: rectangle\n' + ' │ configuration: ImageConfiguration(bundle:\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 ← MediaQuery ← _MediaQueryFromView ←\n' + ' │ _ViewScope ← View-[GlobalObjectKey TestFlutterView#00000] ←\n' + ' │ [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ behavior: opaque\n' + ' │\n' + ' └─child: RenderPadding#00000\n' + ' │ needsCompositing: false\n' + ' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n' + ' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n' + ' │ ← _ViewScope ← View-[GlobalObjectKey TestFlutterView#00000] ←\n' + ' │ [root]\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' + ' │ layer: null\n' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(53.0, 78.0)\n' + ' │ padding: EdgeInsets.all(7.0)\n' + ' │ textDirection: null\n' + ' │\n' + ' └─child: RenderPositionedBox#00000\n' + ' │ needsCompositing: false\n' + ' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n' + ' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n' + ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' + ' │ TestFlutterView#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' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(39.0, 64.0)\n' + ' │ alignment: Alignment.bottomRight\n' + ' │ textDirection: null\n' + ' │ widthFactor: expand\n' + ' │ heightFactor: expand\n' + ' │\n' + ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n' + ' │ needsCompositing: false\n' + ' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\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' + ' │ semantics node: null\n' + ' │ isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' │ isSemanticBoundary: false\n' + ' │ size: Size(25.0, 33.0)\n' + ' │ additionalConstraints: BoxConstraints(w=25.0, h=33.0)\n' + ' │\n' + ' └─child: RenderDecoratedBox#00000\n' + ' needsCompositing: false\n' + ' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n' + ' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n' + ' MediaQuery ← _MediaQueryFromView ← ⋯\n' + ' parentData: (can use size)\n' + ' constraints: BoxConstraints(w=25.0, h=33.0)\n' + ' layer: null\n' + ' semantics node: null\n' + ' isBlockingSemanticsOfPreviouslyPaintedNodes: false\n' + ' isSemanticBoundary: false\n' + ' size: Size(25.0, 33.0)\n' + ' decoration: BoxDecoration:\n' + ' color: Color(0xffffff00)\n' + ' image: null\n' + ' border: null\n' + ' borderRadius: null\n' + ' boxShadow: null\n' + ' gradient: null\n' + ' shape: rectangle\n' + ' configuration: ImageConfiguration(bundle:\n' + ' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n' + ' android)\n', + ), + ); + }); + + testWidgets('painting error has expected diagnostics', (WidgetTester tester) async { + await tester.pumpWidget(Align( + alignment: Alignment.topLeft, + child: container, + )); + + final RenderBox decoratedBox = tester.renderObject(find.byType(DecoratedBox).last); + final PaintingContext context = _MockPaintingContext(); + late FlutterError error; + try { + decoratedBox.paint(context, Offset.zero); + } on FlutterError catch (e) { + error = e; + } + expect(error, isNotNull); + expect( + error.toStringDeep(), + 'FlutterError\n' + ' BoxDecoration painter had mismatching save and restore calls.\n' + ' Before painting the decoration, the canvas save count was 0.\n' + ' After painting it, the canvas save count was 2. Every call to\n' + ' save() or saveLayer() must be matched by a call to restore().\n' + ' The decoration was:\n' + ' BoxDecoration(color: Color(0xffffff00))\n' + ' The painter was:\n' + ' BoxPainter for BoxDecoration(color: Color(0xffffff00))\n', + ); + }); + }); }); testWidgets('Can be placed in an infinite box', (WidgetTester tester) async { 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 732ae02ea9..0a6db8b1c8 100644 --- a/packages/flutter/test/widgets/custom_multi_child_layout_test.dart +++ b/packages/flutter/test/widgets/custom_multi_child_layout_test.dart @@ -374,7 +374,7 @@ void main() { ' creator: ConstrainedBox ← Container ← LayoutWithMissingId ←\n' ' CustomMultiChildLayout ← Center ← MediaQuery ←\n' ' _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' - ' TestWindow#00000] ← [root]\n' + ' TestFlutterView#00000] ← [root]\n' ' parentData: offset=Offset(0.0, 0.0); id=null\n' ' constraints: MISSING\n' ' size: MISSING\n' diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 21c8b0dd83..e6ce67d56b 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -1230,7 +1230,7 @@ void main() { ' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n' ' │ primaryFocusCreator: Container-[GlobalKey#00000] ← MediaQuery ←\n' ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' - ' │ TestWindow#00000] ← [root]\n' + ' │ TestFlutterView#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/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index 73e0e1da89..3b27e84ccb 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, PlatformDispatcher, ViewPadding; +import 'dart:ui' show Brightness, DisplayFeature, DisplayFeatureState, DisplayFeatureType, GestureSettings; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -146,10 +146,10 @@ void main() { }); testWidgets('MediaQueryData.fromView is sane', (WidgetTester tester) async { - final MediaQueryData data = MediaQueryData.fromView(tester.binding.window); + final MediaQueryData data = MediaQueryData.fromView(tester.view); expect(data, hasOneLineDescription); expect(data.hashCode, equals(data.copyWith().hashCode)); - expect(data.size, equals(tester.binding.window.physicalSize / tester.binding.window.devicePixelRatio)); + expect(data.size, equals(tester.view.physicalSize / tester.view.devicePixelRatio)); expect(data.accessibleNavigation, false); expect(data.invertColors, false); expect(data.disableAnimations, false); @@ -173,26 +173,17 @@ void main() { navigationMode: NavigationMode.directional, ); - final TestView view = TestView( - physicalSize: const Size(300, 600), - devicePixelRatio: 3.0, - padding: const TestViewPadding(15), - viewPadding: const TestViewPadding(75), - viewInsets: const TestViewPadding(45), - systemGestureInsets: const TestViewPadding(9), - ); - - final MediaQueryData data = MediaQueryData.fromView(view, platformData: platformData); + final MediaQueryData data = MediaQueryData.fromView(tester.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.size, tester.view.physicalSize / tester.view.devicePixelRatio); + expect(data.devicePixelRatio, tester.view.devicePixelRatio); expect(data.textScaleFactor, platformData.textScaleFactor); expect(data.platformBrightness, platformData.platformBrightness); - expect(data.padding, EdgeInsets.fromViewPadding(view.padding, view.devicePixelRatio)); - expect(data.viewPadding, EdgeInsets.fromViewPadding(view.viewPadding, view.devicePixelRatio)); - expect(data.viewInsets, EdgeInsets.fromViewPadding(view.viewInsets, view.devicePixelRatio)); - expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(view.systemGestureInsets, view.devicePixelRatio)); + expect(data.padding, EdgeInsets.fromViewPadding(tester.view.padding, tester.view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromViewPadding(tester.view.viewPadding, tester.view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromViewPadding(tester.view.viewInsets, tester.view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(tester.view.systemGestureInsets, tester.view.devicePixelRatio)); expect(data.accessibleNavigation, platformData.accessibleNavigation); expect(data.invertColors, platformData.invertColors); expect(data.disableAnimations, platformData.disableAnimations); @@ -200,48 +191,37 @@ void main() { expect(data.highContrast, platformData.highContrast); expect(data.alwaysUse24HourFormat, platformData.alwaysUse24HourFormat); expect(data.navigationMode, platformData.navigationMode); - expect(data.gestureSettings, DeviceGestureSettings.fromView(view)); - expect(data.displayFeatures, view.displayFeatures); + expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); + expect(data.displayFeatures, tester.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 + tester.platformDispatcher ..textScaleFactorTestValue = 123 ..platformBrightnessTestValue = Brightness.dark ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; - addTearDown(() => platformDispatcher.clearAllTestValues()); + addTearDown(() => tester.platformDispatcher.clearAllTestValues()); - final TestView view = TestView( - platformDispatcher: platformDispatcher, - physicalSize: const Size(300, 600), - devicePixelRatio: 3.0, - padding: const TestViewPadding(15), - viewPadding: const TestViewPadding(75), - viewInsets: const TestViewPadding(45), - systemGestureInsets: const TestViewPadding(9), - ); - - final MediaQueryData data = MediaQueryData.fromView(view); + final MediaQueryData data = MediaQueryData.fromView(tester.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.fromViewPadding(view.padding, view.devicePixelRatio)); - expect(data.viewPadding, EdgeInsets.fromViewPadding(view.viewPadding, view.devicePixelRatio)); - expect(data.viewInsets, EdgeInsets.fromViewPadding(view.viewInsets, view.devicePixelRatio)); - expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(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.size, tester.view.physicalSize / tester.view.devicePixelRatio); + expect(data.devicePixelRatio, tester.view.devicePixelRatio); + expect(data.textScaleFactor, tester.platformDispatcher.textScaleFactor); + expect(data.platformBrightness, tester.platformDispatcher.platformBrightness); + expect(data.padding, EdgeInsets.fromViewPadding(tester.view.padding, tester.view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromViewPadding(tester.view.viewPadding, tester.view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromViewPadding(tester.view.viewInsets, tester.view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(tester.view.systemGestureInsets, tester.view.devicePixelRatio)); + expect(data.accessibleNavigation, tester.platformDispatcher.accessibilityFeatures.accessibleNavigation); + expect(data.invertColors, tester.platformDispatcher.accessibilityFeatures.invertColors); + expect(data.disableAnimations, tester.platformDispatcher.accessibilityFeatures.disableAnimations); + expect(data.boldText, tester.platformDispatcher.accessibilityFeatures.boldText); + expect(data.highContrast, tester.platformDispatcher.accessibilityFeatures.highContrast); + expect(data.alwaysUse24HourFormat, tester.platformDispatcher.alwaysUse24HourFormat); expect(data.navigationMode, NavigationMode.traditional); - expect(data.gestureSettings, DeviceGestureSettings.fromView(view)); - expect(data.displayFeatures, view.displayFeatures); + expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); + expect(data.displayFeatures, tester.view.displayFeatures); }); testWidgets('MediaQuery.fromView injects a new MediaQuery with data from view, preserving platform-specific data', (WidgetTester tester) async { @@ -257,20 +237,11 @@ void main() { navigationMode: NavigationMode.directional, ); - final TestView view = TestView( - physicalSize: const Size(300, 600), - devicePixelRatio: 3.0, - padding: const TestViewPadding(15), - viewPadding: const TestViewPadding(75), - viewInsets: const TestViewPadding(45), - systemGestureInsets: const TestViewPadding(9), - ); - late MediaQueryData data; await tester.pumpWidget(MediaQuery( data: platformData, child: MediaQuery.fromView( - view: view, + view: tester.view, child: Builder( builder: (BuildContext context) { data = MediaQuery.of(context); @@ -281,14 +252,14 @@ void main() { )); expect(data, isNot(platformData)); - expect(data.size, view.physicalSize / view.devicePixelRatio); - expect(data.devicePixelRatio, view.devicePixelRatio); + expect(data.size, tester.view.physicalSize / tester.view.devicePixelRatio); + expect(data.devicePixelRatio, tester.view.devicePixelRatio); expect(data.textScaleFactor, platformData.textScaleFactor); expect(data.platformBrightness, platformData.platformBrightness); - expect(data.padding, EdgeInsets.fromViewPadding(view.padding, view.devicePixelRatio)); - expect(data.viewPadding, EdgeInsets.fromViewPadding(view.viewPadding, view.devicePixelRatio)); - expect(data.viewInsets, EdgeInsets.fromViewPadding(view.viewInsets, view.devicePixelRatio)); - expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(view.systemGestureInsets, view.devicePixelRatio)); + expect(data.padding, EdgeInsets.fromViewPadding(tester.view.padding, tester.view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromViewPadding(tester.view.viewPadding, tester.view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromViewPadding(tester.view.viewInsets, tester.view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(tester.view.systemGestureInsets, tester.view.devicePixelRatio)); expect(data.accessibleNavigation, platformData.accessibleNavigation); expect(data.invertColors, platformData.invertColors); expect(data.disableAnimations, platformData.disableAnimations); @@ -296,27 +267,16 @@ void main() { expect(data.highContrast, platformData.highContrast); expect(data.alwaysUse24HourFormat, platformData.alwaysUse24HourFormat); expect(data.navigationMode, platformData.navigationMode); - expect(data.gestureSettings, DeviceGestureSettings.fromView(view)); - expect(data.displayFeatures, view.displayFeatures); + expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); + expect(data.displayFeatures, tester.view.displayFeatures); }); testWidgets('MediaQuery.fromView injects a new MediaQuery with data from view when no surrounding MediaQuery exists', (WidgetTester tester) async { - final TestPlatformDispatcher platformDispatcher = TestPlatformDispatcher(platformDispatcher: tester.binding.platformDispatcher); - platformDispatcher + tester.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 TestViewPadding(15), - viewPadding: const TestViewPadding(75), - viewInsets: const TestViewPadding(45), - systemGestureInsets: const TestViewPadding(9), - ); + addTearDown(() => tester.platformDispatcher.clearAllTestValues()); late MediaQueryData data; MediaQueryData? outerData; @@ -326,7 +286,7 @@ void main() { builder: (BuildContext context) { outerData = MediaQuery.maybeOf(context); return MediaQuery.fromView( - view: view, + view: tester.view, child: Builder( builder: (BuildContext context) { data = MediaQuery.of(context); @@ -339,33 +299,34 @@ void main() { ); 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.fromViewPadding(view.padding, view.devicePixelRatio)); - expect(data.viewPadding, EdgeInsets.fromViewPadding(view.viewPadding, view.devicePixelRatio)); - expect(data.viewInsets, EdgeInsets.fromViewPadding(view.viewInsets, view.devicePixelRatio)); - expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(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.size, tester.view.physicalSize / tester.view.devicePixelRatio); + expect(data.devicePixelRatio, tester.view.devicePixelRatio); + expect(data.textScaleFactor, tester.platformDispatcher.textScaleFactor); + expect(data.platformBrightness, tester.platformDispatcher.platformBrightness); + expect(data.padding, EdgeInsets.fromViewPadding(tester.view.padding, tester.view.devicePixelRatio)); + expect(data.viewPadding, EdgeInsets.fromViewPadding(tester.view.viewPadding, tester.view.devicePixelRatio)); + expect(data.viewInsets, EdgeInsets.fromViewPadding(tester.view.viewInsets, tester.view.devicePixelRatio)); + expect(data.systemGestureInsets, EdgeInsets.fromViewPadding(tester.view.systemGestureInsets, tester.view.devicePixelRatio)); + expect(data.accessibleNavigation, tester.platformDispatcher.accessibilityFeatures.accessibleNavigation); + expect(data.invertColors, tester.platformDispatcher.accessibilityFeatures.invertColors); + expect(data.disableAnimations, tester.platformDispatcher.accessibilityFeatures.disableAnimations); + expect(data.boldText, tester.platformDispatcher.accessibilityFeatures.boldText); + expect(data.highContrast, tester.platformDispatcher.accessibilityFeatures.highContrast); + expect(data.alwaysUse24HourFormat, tester.platformDispatcher.alwaysUse24HourFormat); expect(data.navigationMode, NavigationMode.traditional); - expect(data.gestureSettings, DeviceGestureSettings.fromView(view)); - expect(data.displayFeatures, view.displayFeatures); + expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); + expect(data.displayFeatures, tester.view.displayFeatures); }); testWidgets('MediaQuery.fromView updates on notifications (no parent data)', (WidgetTester tester) async { - tester.binding.platformDispatcher + addTearDown(() => tester.platformDispatcher.clearAllTestValues()); + addTearDown(() => tester.view.reset()); + + tester.platformDispatcher ..textScaleFactorTestValue = 123 ..platformBrightnessTestValue = Brightness.dark ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; - addTearDown(() => tester.binding.platformDispatcher.clearAllTestValues()); - tester.binding.window.devicePixelRatioTestValue = 44; - addTearDown(() => tester.binding.window.clearAllTestValues()); + tester.view.devicePixelRatio = 44; late MediaQueryData data; MediaQueryData? outerData; @@ -376,7 +337,7 @@ void main() { builder: (BuildContext context) { outerData = MediaQuery.maybeOf(context); return MediaQuery.fromView( - view: tester.binding.window, + view: tester.view, child: Builder( builder: (BuildContext context) { rebuildCount++; @@ -393,38 +354,39 @@ void main() { expect(rebuildCount, 1); expect(data.textScaleFactor, 123); - tester.binding.platformDispatcher.textScaleFactorTestValue = 456; + tester.platformDispatcher.textScaleFactorTestValue = 456; await tester.pump(); expect(data.textScaleFactor, 456); expect(rebuildCount, 2); expect(data.platformBrightness, Brightness.dark); - tester.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; + tester.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(); + tester.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures(); await tester.pump(); expect(data.accessibleNavigation, false); expect(rebuildCount, 4); expect(data.devicePixelRatio, 44); - tester.binding.window.devicePixelRatioTestValue = 55; + tester.view.devicePixelRatio = 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 + addTearDown(() => tester.platformDispatcher.clearAllTestValues()); + addTearDown(() => tester.view.reset()); + + tester.platformDispatcher ..textScaleFactorTestValue = 123 ..platformBrightnessTestValue = Brightness.dark ..accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; - addTearDown(() => tester.binding.platformDispatcher.clearAllTestValues()); - tester.binding.window.devicePixelRatioTestValue = 44; - addTearDown(() => tester.binding.window.clearAllTestValues()); + tester.view.devicePixelRatio = 44; late MediaQueryData data; int rebuildCount = 0; @@ -436,7 +398,7 @@ void main() { accessibleNavigation: true, ), child: MediaQuery.fromView( - view: tester.binding.window, + view: tester.view, child: Builder( builder: (BuildContext context) { rebuildCount++; @@ -451,25 +413,25 @@ void main() { expect(rebuildCount, 1); expect(data.textScaleFactor, 44); - tester.binding.platformDispatcher.textScaleFactorTestValue = 456; + tester.platformDispatcher.textScaleFactorTestValue = 456; await tester.pump(); expect(data.textScaleFactor, 44); expect(rebuildCount, 1); expect(data.platformBrightness, Brightness.dark); - tester.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; + tester.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(); + tester.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures(); await tester.pump(); expect(data.accessibleNavigation, true); expect(rebuildCount, 1); expect(data.devicePixelRatio, 44); - tester.binding.window.devicePixelRatioTestValue = 55; + tester.view.devicePixelRatio = 55; await tester.pump(); expect(data.devicePixelRatio, 55); expect(rebuildCount, 2); @@ -487,7 +449,7 @@ void main() { return MediaQuery( data: MediaQueryData(textScaleFactor: textScaleFactor), child: MediaQuery.fromView( - view: tester.binding.window, + view: tester.view, child: Builder( builder: (BuildContext context) { rebuildCount++; @@ -513,7 +475,7 @@ void main() { }); testWidgets('MediaQueryData.copyWith defaults to source', (WidgetTester tester) async { - final MediaQueryData data = MediaQueryData.fromView(tester.binding.window); + final MediaQueryData data = MediaQueryData.fromView(tester.view); final MediaQueryData copied = data.copyWith(); expect(copied.size, data.size); expect(copied.devicePixelRatio, data.devicePixelRatio); @@ -552,7 +514,7 @@ void main() { ), ]; - final MediaQueryData data = MediaQueryData.fromView(tester.binding.window); + final MediaQueryData data = MediaQueryData.fromView(tester.view); final MediaQueryData copied = data.copyWith( size: customSize, devicePixelRatio: customDevicePixelRatio, @@ -1325,11 +1287,11 @@ void main() { expect(subScreenMediaQuery.displayFeatures, [cutoutDisplayFeature]); }); - testWidgets('MediaQueryData.gestureSettings is set from window.viewConfiguration', (WidgetTester tester) async { - tester.binding.window.gestureSettingsTestValue = const GestureSettings(physicalDoubleTapSlop: 100, physicalTouchSlop: 100); + testWidgets('MediaQueryData.gestureSettings is set from view.gestureSettings', (WidgetTester tester) async { + tester.view.gestureSettings = const GestureSettings(physicalDoubleTapSlop: 100, physicalTouchSlop: 100); + addTearDown(() => tester.view.resetGestureSettings()); - expect(MediaQueryData.fromView(tester.binding.window).gestureSettings.touchSlop, closeTo(33.33, 0.1)); // Repeating, of course - tester.binding.window.clearGestureSettingsTestValue(); + expect(MediaQueryData.fromView(tester.view).gestureSettings.touchSlop, closeTo(33.33, 0.1)); // Repeating, of course }); testWidgets('MediaQuery can be partially depended-on', (WidgetTester tester) async { @@ -1511,52 +1473,3 @@ Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, require 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 ViewPadding padding; - @override - final ViewPadding viewPadding; - @override - final ViewPadding viewInsets; - @override - final ViewPadding systemGestureInsets; - @override - final List displayFeatures = []; - @override - final GestureSettings gestureSettings = const GestureSettings(); - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -class TestViewPadding implements ViewPadding { - const TestViewPadding(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/slotted_render_object_widget_test.dart b/packages/flutter/test/widgets/slotted_render_object_widget_test.dart index b8816d7bfb..e5ba7a55fb 100644 --- a/packages/flutter/test/widgets/slotted_render_object_widget_test.dart +++ b/packages/flutter/test/widgets/slotted_render_object_widget_test.dart @@ -223,7 +223,7 @@ void main() { '_RenderDiagonal#00000 relayoutBoundary=up1\n' ' │ creator: _Diagonal ← Align ← Directionality ← MediaQuery ←\n' ' │ _MediaQueryFromView ← _ViewScope ← View-[GlobalObjectKey\n' - ' │ TestWindow#00000] ← [root]\n' + ' │ TestFlutterView#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' @@ -231,7 +231,7 @@ void main() { ' ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' │ creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n' ' │ MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' - ' │ View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' │ View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' ' │ constraints: BoxConstraints(unconstrained)\n' ' │ size: Size(80.0, 100.0)\n' @@ -240,7 +240,7 @@ void main() { ' └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n' ' MediaQuery ← _MediaQueryFromView ← _ViewScope ←\n' - ' View-[GlobalObjectKey TestWindow#00000] ← [root]\n' + ' View-[GlobalObjectKey TestFlutterView#00000] ← [root]\n' ' parentData: offset=Offset(80.0, 100.0) (can use size)\n' ' constraints: BoxConstraints(unconstrained)\n' ' size: Size(110.0, 120.0)\n' diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 712192241f..c18e31cfeb 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -182,17 +182,18 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// /// This constructor overrides the [debugPrint] global hook to point to /// [debugPrintOverride], which can be overridden by subclasses. - TestWidgetsFlutterBinding() : _window = TestWindow(window: ui.window) { + TestWidgetsFlutterBinding() : platformDispatcher = TestPlatformDispatcher( + platformDispatcher: PlatformDispatcher.instance, + ) { debugPrint = debugPrintOverride; debugDisableShadows = disableShadows; } @override - TestWindow get window => _window; - final TestWindow _window; + late final TestWindow window; @override - TestPlatformDispatcher get platformDispatcher => _window.platformDispatcher; + final TestPlatformDispatcher platformDispatcher; @override TestRestorationManager get restorationManager { @@ -346,6 +347,12 @@ abstract class TestWidgetsFlutterBinding extends BindingBase @override void initInstances() { + // This is intialized here because it's needed for the `super.initInstances` + // call. It can't be handled as a ctor initializer because it's dependent + // on `platformDispatcher`. It can't be handled in the ctor itself because + // the base class ctor is called first and calls `initInstances`. + window = TestWindow.fromPlatformDispatcher(platformDispatcher: platformDispatcher); + super.initInstances(); _instance = this; timeDilation = 1.0; // just in case the developer has artificially changed it for development diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index 2dbbfbb42b..325893e60a 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -14,6 +14,7 @@ import 'event_simulation.dart'; import 'finders.dart'; import 'test_async_utils.dart'; import 'test_pointer.dart'; +import 'window.dart'; /// The default drag touch slop used to break up a large drag into multiple /// smaller moves. @@ -234,6 +235,37 @@ abstract class WidgetController { /// A reference to the current instance of the binding. final WidgetsBinding binding; + /// The [TestPlatformDispatcher] that is being used in this test. + /// + /// This will be injected into the framework such that calls to + /// [WidgetsBinding.platformDispatcher] will use this. This allows + /// users to change platform specific properties for testing. + /// + /// See also: + /// + /// * [TestFlutterView] which allows changing view specific properties + /// for testing + /// * [view] and [viewOf] which are used to find + /// [TestFlutterView]s from the widget tree + TestPlatformDispatcher get platformDispatcher => binding.platformDispatcher as TestPlatformDispatcher; + + /// The [TestFlutterView] provided by default when testing with + /// [WidgetTester.pumpWidget]. + /// + /// If the test requires multiple views, it will need to use [viewOf] instead + /// to ensure that the view related to the widget being evaluated is the one + /// that gets updated. + /// + /// See also: + /// + /// * [viewOf], which can find a [TestFlutterView] related to a given finder. + /// This is how to modify view properties for testing when dealing with + /// multiple views. + TestFlutterView get view { + assert(platformDispatcher.views.length == 1, 'When testing with multiple views, use `viewOf` instead.'); + return platformDispatcher.views.single; + } + /// Provides access to a [SemanticsController] for testing anything related to /// the [Semantics] tree. /// @@ -257,6 +289,26 @@ abstract class WidgetController { // TODO(ianh): verify that the return values are of type T and throw // a good message otherwise, in all the generic methods below + /// Finds the [TestFlutterView] that is the closest ancestor of the widget + /// found by [finder]. + /// + /// [TestFlutterView] can be used to modify view specific properties for testing. + /// + /// See also: + /// + /// * [view] which returns the [TestFlutterView] used when only a single + /// view is being used. + TestFlutterView viewOf(Finder finder) { + final View view = firstWidget( + find.ancestor( + of: finder, + matching: find.byType(View), + ) + ); + + return view.view as TestFlutterView; + } + /// Checks if `finder` exists in the tree. bool any(Finder finder) { TestAsyncUtils.guardSync(); diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index 4a7a0c8ff4..83632feef4 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -2,532 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' as ui hide window; +import 'dart:ui' hide window; import 'package:flutter/foundation.dart'; -/// [SingletonFlutterWindow] that wraps another [SingletonFlutterWindow] and -/// allows faking of some properties for testing purposes. -/// -/// Tests for certain widgets, e.g., [MaterialApp], might require faking certain -/// properties of a [SingletonFlutterWindow]. [TestWindow] facilitates the -/// faking of these properties by overriding the properties of a real -/// [SingletonFlutterWindow] with desired fake values. The binding used within -/// tests, [TestWidgetsFlutterBinding], contains a [TestWindow] that is used by -/// all tests. -/// -/// ## Sample Code -/// -/// A test can utilize a [TestWindow] in the following way: -/// -/// ```dart -/// testWidgets('your test name here', (WidgetTester tester) async { -/// // Retrieve the TestWidgetsFlutterBinding. -/// final TestWidgetsFlutterBinding testBinding = tester.binding; -/// -/// // Fake the desired properties of the TestWindow. All code running -/// // within this test will perceive the following fake text scale -/// // factor as the real text scale factor of the window. -/// testBinding.window.textScaleFactorFakeValue = 2.5; -/// -/// // Test code that depends on text scale factor here. -/// }); -/// ``` -/// -/// The [TestWidgetsFlutterBinding] is recreated for each test and -/// therefore any fake values defined in one test will not persist -/// to the next. -/// -/// If a test needs to override a real [SingletonFlutterWindow] property and -/// then later return to using the real [SingletonFlutterWindow] property, -/// [TestWindow] provides methods to clear each individual test value, e.g., -/// [clearLocaleTestValue()]. -/// -/// To clear all fake test values in a [TestWindow], consider using -/// [clearAllTestValues()]. -/// -/// See also: -/// -/// * [TestPlatformDispatcher], which wraps a [PlatformDispatcher] for -/// testing purposes and is used by the [platformDispatcher] property of -/// this class. -class TestWindow implements ui.SingletonFlutterWindow { - /// Constructs a [TestWindow] that defers all behavior to the given - /// [dart:ui.SingletonFlutterWindow] unless explicitly overridden for test purposes. - TestWindow({ - required ui.SingletonFlutterWindow window, - }) : _window = window, - platformDispatcher = TestPlatformDispatcher(platformDispatcher: window.platformDispatcher); - - /// The [dart:ui.SingletonFlutterWindow] that is wrapped by this [TestWindow]. - final ui.SingletonFlutterWindow _window; - - @override - final TestPlatformDispatcher platformDispatcher; - - @override - double get devicePixelRatio => _devicePixelRatio ?? _window.devicePixelRatio; - double? _devicePixelRatio; - /// Hides the real device pixel ratio and reports the given [devicePixelRatio] - /// instead. - set devicePixelRatioTestValue(double devicePixelRatio) { // ignore: avoid_setters_without_getters - _devicePixelRatio = devicePixelRatio; - onMetricsChanged?.call(); - } - /// Deletes any existing test device pixel ratio and returns to using the real - /// device pixel ratio. - void clearDevicePixelRatioTestValue() { - _devicePixelRatio = null; - onMetricsChanged?.call(); - } - - @override - ui.Size get physicalSize => _physicalSizeTestValue ?? _window.physicalSize; - ui.Size? _physicalSizeTestValue; - /// Hides the real physical size and reports the given [physicalSizeTestValue] - /// instead. - set physicalSizeTestValue (ui.Size physicalSizeTestValue) { // ignore: avoid_setters_without_getters - _physicalSizeTestValue = physicalSizeTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test physical size and returns to using the real - /// physical size. - void clearPhysicalSizeTestValue() { - _physicalSizeTestValue = null; - onMetricsChanged?.call(); - } - - @override - ui.ViewPadding get viewInsets => _viewInsetsTestValue ?? _window.viewInsets; - ui.ViewPadding? _viewInsetsTestValue; - /// Hides the real view insets and reports the given [viewInsetsTestValue] - /// instead. - set viewInsetsTestValue(ui.ViewPadding viewInsetsTestValue) { // ignore: avoid_setters_without_getters - _viewInsetsTestValue = viewInsetsTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test view insets and returns to using the real view - /// insets. - void clearViewInsetsTestValue() { - _viewInsetsTestValue = null; - onMetricsChanged?.call(); - } - - @override - ui.ViewPadding get viewPadding => _viewPaddingTestValue ?? _window.padding; - ui.ViewPadding? _viewPaddingTestValue; - /// Hides the real view padding and reports the given [paddingTestValue] - /// instead. - set viewPaddingTestValue(ui.ViewPadding viewPaddingTestValue) { // ignore: avoid_setters_without_getters - _viewPaddingTestValue = viewPaddingTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test view padding and returns to using the real - /// viewPadding. - void clearViewPaddingTestValue() { - _viewPaddingTestValue = null; - onMetricsChanged?.call(); - } - - @override - ui.ViewPadding get padding => _paddingTestValue ?? _window.padding; - ui.ViewPadding? _paddingTestValue; - /// Hides the real padding and reports the given [paddingTestValue] instead. - set paddingTestValue(ui.ViewPadding paddingTestValue) { // ignore: avoid_setters_without_getters - _paddingTestValue = paddingTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test padding and returns to using the real padding. - void clearPaddingTestValue() { - _paddingTestValue = null; - onMetricsChanged?.call(); - } - - @override - ui.GestureSettings get gestureSettings => _gestureSettings ?? _window.gestureSettings; - ui.GestureSettings? _gestureSettings; - /// Hides the real gesture settings and reports the given [gestureSettingsTestValue] instead. - set gestureSettingsTestValue(ui.GestureSettings gestureSettingsTestValue) { // ignore: avoid_setters_without_getters - _gestureSettings = gestureSettingsTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test gesture settings and returns to using the real gesture settings. - void clearGestureSettingsTestValue() { - _gestureSettings = null; - onMetricsChanged?.call(); - } - - @override - List get displayFeatures => _displayFeaturesTestValue ?? _window.displayFeatures; - List? _displayFeaturesTestValue; - /// Hides the real displayFeatures and reports the given [displayFeaturesTestValue] instead. - set displayFeaturesTestValue(List displayFeaturesTestValue) { // ignore: avoid_setters_without_getters - _displayFeaturesTestValue = displayFeaturesTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test padding and returns to using the real padding. - void clearDisplayFeaturesTestValue() { - _displayFeaturesTestValue = null; - onMetricsChanged?.call(); - } - - @override - ui.ViewPadding get systemGestureInsets => _systemGestureInsetsTestValue ?? _window.systemGestureInsets; - ui.ViewPadding? _systemGestureInsetsTestValue; - /// Hides the real system gesture insets and reports the given [systemGestureInsetsTestValue] instead. - set systemGestureInsetsTestValue(ui.ViewPadding systemGestureInsetsTestValue) { // ignore: avoid_setters_without_getters - _systemGestureInsetsTestValue = systemGestureInsetsTestValue; - onMetricsChanged?.call(); - } - /// Deletes any existing test system gesture insets and returns to using the real system gesture insets. - void clearSystemGestureInsetsTestValue() { - _systemGestureInsetsTestValue = null; - onMetricsChanged?.call(); - } - - @override - ui.VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; - @override - set onMetricsChanged(ui.VoidCallback? callback) { - platformDispatcher.onMetricsChanged = callback; - } - - @override - ui.Locale get locale => platformDispatcher.locale; - /// Hides the real locale and reports the given [localeTestValue] instead. - @Deprecated( - 'Use platformDispatcher.localeTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set localeTestValue(ui.Locale localeTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.localeTestValue = localeTestValue; - } - @Deprecated( - 'Use platformDispatcher.clearLocaleTestValue() instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - /// Deletes any existing test locale and returns to using the real locale. - void clearLocaleTestValue() { - platformDispatcher.clearLocaleTestValue(); - } - - @override - List get locales => platformDispatcher.locales; - /// Hides the real locales and reports the given [localesTestValue] instead. - @Deprecated( - 'Use platformDispatcher.localesTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set localesTestValue(List localesTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.localesTestValue = localesTestValue; - } - /// Deletes any existing test locales and returns to using the real locales. - @Deprecated( - 'Use platformDispatcher.clearLocalesTestValue() instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearLocalesTestValue() { - platformDispatcher.clearLocalesTestValue(); - } - - @override - ui.VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; - @override - set onLocaleChanged(ui.VoidCallback? callback) { - platformDispatcher.onLocaleChanged = callback; - } - - @override - String get initialLifecycleState => platformDispatcher.initialLifecycleState; - /// Sets a faked initialLifecycleState for testing. - @Deprecated( - 'Use platformDispatcher.initialLifecycleStateTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set initialLifecycleStateTestValue(String state) { // ignore: avoid_setters_without_getters - platformDispatcher.initialLifecycleStateTestValue = state; - } - - @override - double get textScaleFactor => platformDispatcher.textScaleFactor; - /// Hides the real text scale factor and reports the given - /// [textScaleFactorTestValue] instead. - @Deprecated( - 'Use platformDispatcher.textScaleFactorTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set textScaleFactorTestValue(double textScaleFactorTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.textScaleFactorTestValue = textScaleFactorTestValue; - } - /// Deletes any existing test text scale factor and returns to using the real - /// text scale factor. - @Deprecated( - 'Use platformDispatcher.clearTextScaleFactorTestValue() instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearTextScaleFactorTestValue() { - platformDispatcher.clearTextScaleFactorTestValue(); - } - - @override - ui.Brightness get platformBrightness => platformDispatcher.platformBrightness; - @override - ui.VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; - @override - set onPlatformBrightnessChanged(ui.VoidCallback? callback) { - platformDispatcher.onPlatformBrightnessChanged = callback; - } - /// Hides the real text scale factor and reports the given - /// [platformBrightnessTestValue] instead. - @Deprecated( - 'Use platformDispatcher.platformBrightnessTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set platformBrightnessTestValue(ui.Brightness platformBrightnessTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.platformBrightnessTestValue = platformBrightnessTestValue; - } - /// Deletes any existing test platform brightness and returns to using the - /// real platform brightness. - @Deprecated( - 'Use platformDispatcher.clearPlatformBrightnessTestValue() instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearPlatformBrightnessTestValue() { - platformDispatcher.clearPlatformBrightnessTestValue(); - } - - @override - bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; - /// Hides the real clock format and reports the given - /// [alwaysUse24HourFormatTestValue] instead. - @Deprecated( - 'Use platformDispatcher.alwaysUse24HourFormatTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set alwaysUse24HourFormatTestValue(bool alwaysUse24HourFormatTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.alwaysUse24HourFormatTestValue = alwaysUse24HourFormatTestValue; - } - /// Deletes any existing test clock format and returns to using the real clock - /// format. - @Deprecated( - 'Use platformDispatcher.clearAlwaysUse24HourTestValue() instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearAlwaysUse24HourTestValue() { - platformDispatcher.clearAlwaysUse24HourTestValue(); - } - - @override - ui.VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; - @override - set onTextScaleFactorChanged(ui.VoidCallback? callback) { - platformDispatcher.onTextScaleFactorChanged = callback; - } - - @override - bool get nativeSpellCheckServiceDefined => platformDispatcher.nativeSpellCheckServiceDefined; - set nativeSpellCheckServiceDefinedTestValue(bool nativeSpellCheckServiceDefinedTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.nativeSpellCheckServiceDefinedTestValue = nativeSpellCheckServiceDefinedTestValue; - } - - @override - bool get brieflyShowPassword => platformDispatcher.brieflyShowPassword; - /// Hides the real [brieflyShowPassword] and reports the given - /// `brieflyShowPasswordTestValue` instead. - @Deprecated( - 'Use platformDispatcher.brieflyShowPasswordTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set brieflyShowPasswordTestValue(bool brieflyShowPasswordTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.brieflyShowPasswordTestValue = brieflyShowPasswordTestValue; - } - - @override - ui.FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; - @override - set onBeginFrame(ui.FrameCallback? callback) { - platformDispatcher.onBeginFrame = callback; - } - - @override - ui.VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; - @override - set onDrawFrame(ui.VoidCallback? callback) { - platformDispatcher.onDrawFrame = callback; - } - - @override - ui.TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; - @override - set onReportTimings(ui.TimingsCallback? callback) { - platformDispatcher.onReportTimings = callback; - } - - @override - ui.PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; - @override - set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { - platformDispatcher.onPointerDataPacket = callback; - } - - @override - String get defaultRouteName => platformDispatcher.defaultRouteName; - /// Hides the real default route name and reports the given - /// [defaultRouteNameTestValue] instead. - @Deprecated( - 'Use platformDispatcher.defaultRouteNameTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set defaultRouteNameTestValue(String defaultRouteNameTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.defaultRouteNameTestValue = defaultRouteNameTestValue; - } - /// Deletes any existing test default route name and returns to using the real - /// default route name. - @Deprecated( - 'Use platformDispatcher.clearDefaultRouteNameTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearDefaultRouteNameTestValue() { - platformDispatcher.clearDefaultRouteNameTestValue(); - } - - @override - void scheduleFrame() { - platformDispatcher.scheduleFrame(); - } - - @override - void render(ui.Scene scene) { - _window.render(scene); - } - - @override - bool get semanticsEnabled => platformDispatcher.semanticsEnabled; - /// Hides the real semantics enabled and reports the given - /// [semanticsEnabledTestValue] instead. - @Deprecated( - 'Use platformDispatcher.semanticsEnabledTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set semanticsEnabledTestValue(bool semanticsEnabledTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.semanticsEnabledTestValue = semanticsEnabledTestValue; - } - /// Deletes any existing test semantics enabled and returns to using the real - /// semantics enabled. - @Deprecated( - 'Use platformDispatcher.clearSemanticsEnabledTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearSemanticsEnabledTestValue() { - platformDispatcher.clearSemanticsEnabledTestValue(); - } - - @override - ui.VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; - @override - set onSemanticsEnabledChanged(ui.VoidCallback? callback) { - platformDispatcher.onSemanticsEnabledChanged = callback; - } - - @override - ui.SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; - @override - set onSemanticsAction(ui.SemanticsActionCallback? callback) { - platformDispatcher.onSemanticsAction = callback; - } - - @override - ui.AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; - /// Hides the real accessibility features and reports the given - /// [accessibilityFeaturesTestValue] instead. - /// - /// Consider using [FakeAccessibilityFeatures] to provide specific - /// values for the various accessibility features under test. - @Deprecated( - 'Use platformDispatcher.accessibilityFeaturesTestValue instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - set accessibilityFeaturesTestValue(ui.AccessibilityFeatures accessibilityFeaturesTestValue) { // ignore: avoid_setters_without_getters - platformDispatcher.accessibilityFeaturesTestValue = accessibilityFeaturesTestValue; - } - /// Deletes any existing test accessibility features and returns to using the - /// real accessibility features. - @Deprecated( - 'Use platformDispatcher.clearAccessibilityFeaturesTestValue() instead. ' - 'This feature was deprecated after v2.11.0-0.0.pre.' - ) - void clearAccessibilityFeaturesTestValue() { - platformDispatcher.clearAccessibilityFeaturesTestValue(); - } - - @override - ui.VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged; - @override - set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { - platformDispatcher.onAccessibilityFeaturesChanged = callback; - } - - @override - void updateSemantics(ui.SemanticsUpdate update) { - _window.updateSemantics(update); - } - - @override - void setIsolateDebugName(String name) { - platformDispatcher.setIsolateDebugName(name); - } - - @override - void sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - platformDispatcher.sendPlatformMessage(name, data, callback); - } - - @Deprecated( - 'Instead of calling this callback, use ServicesBinding.instance.channelBuffers.push. ' - 'This feature was deprecated after v2.1.0-10.0.pre.' - ) - @override - ui.PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; - @Deprecated( - 'Instead of setting this callback, use ServicesBinding.instance.defaultBinaryMessenger.setMessageHandler. ' - 'This feature was deprecated after v2.1.0-10.0.pre.' - ) - @override - set onPlatformMessage(ui.PlatformMessageCallback? callback) { - platformDispatcher.onPlatformMessage = callback; - } - - /// Delete any test value properties that have been set on this [TestWindow] - /// as well as its [platformDispatcher]. - /// - /// After calling this, the real [SingletonFlutterWindow] and - /// [ui.PlatformDispatcher] values are reported again. - /// - /// If desired, clearing of properties can be done on an individual basis, - /// e.g., [clearLocaleTestValue()]. - void clearAllTestValues() { - clearDevicePixelRatioTestValue(); - clearPaddingTestValue(); - clearGestureSettingsTestValue(); - clearDisplayFeaturesTestValue(); - clearPhysicalSizeTestValue(); - clearViewInsetsTestValue(); - platformDispatcher.clearAllTestValues(); - } - - /// This gives us some grace time when the dart:ui side adds something to - /// [SingletonFlutterWindow], and makes things easier when we do rolls to give - /// us time to catch up. - @override - dynamic noSuchMethod(Invocation invocation) { - return null; - } -} - /// Test version of [AccessibilityFeatures] in which specific features may /// be set to arbitrary values. /// @@ -536,7 +14,7 @@ class TestWindow implements ui.SingletonFlutterWindow { /// constant. @immutable // ignore: avoid_implementing_value_types -class FakeAccessibilityFeatures implements ui.AccessibilityFeatures { +class FakeAccessibilityFeatures implements AccessibilityFeatures { /// Creates a test instance of [AccessibilityFeatures]. /// /// By default, all features are disabled. @@ -606,38 +84,96 @@ class FakeAccessibilityFeatures implements ui.AccessibilityFeatures { } } +/// Used to fake insets and padding for [TestFlutterView]s. +/// +/// See also: +/// +/// * [TestFlutterView.padding], [TestFlutterView.systemGestureInsets], +/// [TestFlutterView.viewInsets], and [TestFlutterView.viewPadding] for test +/// properties that make use of [FakeViewPadding]. +@immutable +class FakeViewPadding implements ViewPadding { + /// Instantiates a new [FakeViewPadding] object for faking insets and padding + /// during tests. + const FakeViewPadding({ + this.left = 0.0, + this.top = 0.0, + this.right = 0.0, + this.bottom = 0.0, + }); + + FakeViewPadding._wrap(ViewPadding base) : + left = base.left, + top = base.top, + right = base.right, + bottom = base.bottom; + + @override + final double left; + + @override + final double top; + + @override + final double right; + + @override + final double bottom; +} + /// [PlatformDispatcher] that wraps another [PlatformDispatcher] and /// allows faking of some properties for testing purposes. /// /// See also: /// +/// * [TestFlutterView], which wraps a [FlutterView] for testing and +/// mocking purposes. /// * [TestWindow], which wraps a [SingletonFlutterWindow] for /// testing and mocking purposes. -class TestPlatformDispatcher implements ui.PlatformDispatcher { +class TestPlatformDispatcher implements PlatformDispatcher { /// Constructs a [TestPlatformDispatcher] that defers all behavior to the given - /// [dart:ui.PlatformDispatcher] unless explicitly overridden for test purposes. + /// [PlatformDispatcher] unless explicitly overridden for test purposes. TestPlatformDispatcher({ - required ui.PlatformDispatcher platformDispatcher, - }) : _platformDispatcher = platformDispatcher; + required PlatformDispatcher platformDispatcher, + }) : _platformDispatcher = platformDispatcher { + _updateViews(); + _platformDispatcher.onMetricsChanged = _handleMetricsChanged; + } - /// The [dart:ui.PlatformDispatcher] that is wrapped by this [TestPlatformDispatcher]. - final ui.PlatformDispatcher _platformDispatcher; + /// The [PlatformDispatcher] that is wrapped by this [TestPlatformDispatcher]. + final PlatformDispatcher _platformDispatcher; @override - ui.VoidCallback? get onMetricsChanged => _platformDispatcher.onMetricsChanged; + TestFlutterView? get implicitView { + return _platformDispatcher.implicitView != null + ? _testViews[_platformDispatcher.implicitView!.viewId]! + : null; + } + + final Map _testViews = {}; + @override - set onMetricsChanged(ui.VoidCallback? callback) { - _platformDispatcher.onMetricsChanged = callback; + VoidCallback? get onMetricsChanged => _platformDispatcher.onMetricsChanged; + VoidCallback? _onMetricsChanged; + @override + set onMetricsChanged(VoidCallback? callback) { + _onMetricsChanged = callback; + } + + void _handleMetricsChanged() { + _updateViews(); + _onMetricsChanged?.call(); } @override - ui.Locale get locale => _localeTestValue ?? _platformDispatcher.locale; - ui.Locale? _localeTestValue; + Locale get locale => _localeTestValue ?? _platformDispatcher.locale; + Locale? _localeTestValue; /// Hides the real locale and reports the given [localeTestValue] instead. - set localeTestValue(ui.Locale localeTestValue) { // ignore: avoid_setters_without_getters + set localeTestValue(Locale localeTestValue) { // ignore: avoid_setters_without_getters _localeTestValue = localeTestValue; onLocaleChanged?.call(); } + /// Deletes any existing test locale and returns to using the real locale. void clearLocaleTestValue() { _localeTestValue = null; @@ -645,13 +181,14 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { } @override - List get locales => _localesTestValue ?? _platformDispatcher.locales; - List? _localesTestValue; + List get locales => _localesTestValue ?? _platformDispatcher.locales; + List? _localesTestValue; /// Hides the real locales and reports the given [localesTestValue] instead. - set localesTestValue(List localesTestValue) { // ignore: avoid_setters_without_getters + set localesTestValue(List localesTestValue) { // ignore: avoid_setters_without_getters _localesTestValue = localesTestValue; onLocaleChanged?.call(); } + /// Deletes any existing test locales and returns to using the real locales. void clearLocalesTestValue() { _localesTestValue = null; @@ -659,9 +196,9 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { } @override - ui.VoidCallback? get onLocaleChanged => _platformDispatcher.onLocaleChanged; + VoidCallback? get onLocaleChanged => _platformDispatcher.onLocaleChanged; @override - set onLocaleChanged(ui.VoidCallback? callback) { + set onLocaleChanged(VoidCallback? callback) { _platformDispatcher.onLocaleChanged = callback; } @@ -673,6 +210,11 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { _initialLifecycleStateTestValue = state; } + /// Resets [initialLifecycleState] to the default value for the platform. + void resetInitialLifecycleState() { + _initialLifecycleStateTestValue = ''; + } + @override double get textScaleFactor => _textScaleFactorTestValue ?? _platformDispatcher.textScaleFactor; double? _textScaleFactorTestValue; @@ -682,6 +224,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { _textScaleFactorTestValue = textScaleFactorTestValue; onTextScaleFactorChanged?.call(); } + /// Deletes any existing test text scale factor and returns to using the real /// text scale factor. void clearTextScaleFactorTestValue() { @@ -690,20 +233,21 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { } @override - ui.Brightness get platformBrightness => _platformBrightnessTestValue ?? _platformDispatcher.platformBrightness; - ui.Brightness? _platformBrightnessTestValue; + Brightness get platformBrightness => _platformBrightnessTestValue ?? _platformDispatcher.platformBrightness; + Brightness? _platformBrightnessTestValue; @override - ui.VoidCallback? get onPlatformBrightnessChanged => _platformDispatcher.onPlatformBrightnessChanged; + VoidCallback? get onPlatformBrightnessChanged => _platformDispatcher.onPlatformBrightnessChanged; @override - set onPlatformBrightnessChanged(ui.VoidCallback? callback) { + set onPlatformBrightnessChanged(VoidCallback? callback) { _platformDispatcher.onPlatformBrightnessChanged = callback; } /// Hides the real text scale factor and reports the given /// [platformBrightnessTestValue] instead. - set platformBrightnessTestValue(ui.Brightness platformBrightnessTestValue) { // ignore: avoid_setters_without_getters + set platformBrightnessTestValue(Brightness platformBrightnessTestValue) { // ignore: avoid_setters_without_getters _platformBrightnessTestValue = platformBrightnessTestValue; onPlatformBrightnessChanged?.call(); } + /// Deletes any existing test platform brightness and returns to using the /// real platform brightness. void clearPlatformBrightnessTestValue() { @@ -719,6 +263,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { set alwaysUse24HourFormatTestValue(bool alwaysUse24HourFormatTestValue) { // ignore: avoid_setters_without_getters _alwaysUse24HourFormatTestValue = alwaysUse24HourFormatTestValue; } + /// Deletes any existing test clock format and returns to using the real clock /// format. void clearAlwaysUse24HourTestValue() { @@ -726,9 +271,9 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { } @override - ui.VoidCallback? get onTextScaleFactorChanged => _platformDispatcher.onTextScaleFactorChanged; + VoidCallback? get onTextScaleFactorChanged => _platformDispatcher.onTextScaleFactorChanged; @override - set onTextScaleFactorChanged(ui.VoidCallback? callback) { + set onTextScaleFactorChanged(VoidCallback? callback) { _platformDispatcher.onTextScaleFactorChanged = callback; } @@ -738,6 +283,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { set nativeSpellCheckServiceDefinedTestValue(bool nativeSpellCheckServiceDefinedTestValue) { // ignore: avoid_setters_without_getters _nativeSpellCheckServiceDefinedTestValue = nativeSpellCheckServiceDefinedTestValue; } + /// Deletes existing value that determines whether or not a native spell check /// service is defined and returns to the real value. void clearNativeSpellCheckServiceDefined() { @@ -753,31 +299,36 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { _brieflyShowPasswordTestValue = brieflyShowPasswordTestValue; } + /// Resets [brieflyShowPassword] to the default value for the platform. + void resetBrieflyShowPassword() { + _brieflyShowPasswordTestValue = null; + } + @override - ui.FrameCallback? get onBeginFrame => _platformDispatcher.onBeginFrame; + FrameCallback? get onBeginFrame => _platformDispatcher.onBeginFrame; @override - set onBeginFrame(ui.FrameCallback? callback) { + set onBeginFrame(FrameCallback? callback) { _platformDispatcher.onBeginFrame = callback; } @override - ui.VoidCallback? get onDrawFrame => _platformDispatcher.onDrawFrame; + VoidCallback? get onDrawFrame => _platformDispatcher.onDrawFrame; @override - set onDrawFrame(ui.VoidCallback? callback) { + set onDrawFrame(VoidCallback? callback) { _platformDispatcher.onDrawFrame = callback; } @override - ui.TimingsCallback? get onReportTimings => _platformDispatcher.onReportTimings; + TimingsCallback? get onReportTimings => _platformDispatcher.onReportTimings; @override - set onReportTimings(ui.TimingsCallback? callback) { + set onReportTimings(TimingsCallback? callback) { _platformDispatcher.onReportTimings = callback; } @override - ui.PointerDataPacketCallback? get onPointerDataPacket => _platformDispatcher.onPointerDataPacket; + PointerDataPacketCallback? get onPointerDataPacket => _platformDispatcher.onPointerDataPacket; @override - set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { + set onPointerDataPacket(PointerDataPacketCallback? callback) { _platformDispatcher.onPointerDataPacket = callback; } @@ -789,6 +340,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { set defaultRouteNameTestValue(String defaultRouteNameTestValue) { // ignore: avoid_setters_without_getters _defaultRouteNameTestValue = defaultRouteNameTestValue; } + /// Deletes any existing test default route name and returns to using the real /// default route name. void clearDefaultRouteNameTestValue() { @@ -809,6 +361,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { _semanticsEnabledTestValue = semanticsEnabledTestValue; onSemanticsEnabledChanged?.call(); } + /// Deletes any existing test semantics enabled and returns to using the real /// semantics enabled. void clearSemanticsEnabledTestValue() { @@ -817,31 +370,32 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { } @override - ui.VoidCallback? get onSemanticsEnabledChanged => _platformDispatcher.onSemanticsEnabledChanged; + VoidCallback? get onSemanticsEnabledChanged => _platformDispatcher.onSemanticsEnabledChanged; @override - set onSemanticsEnabledChanged(ui.VoidCallback? callback) { + set onSemanticsEnabledChanged(VoidCallback? callback) { _platformDispatcher.onSemanticsEnabledChanged = callback; } @override - ui.SemanticsActionCallback? get onSemanticsAction => _platformDispatcher.onSemanticsAction; + SemanticsActionCallback? get onSemanticsAction => _platformDispatcher.onSemanticsAction; @override - set onSemanticsAction(ui.SemanticsActionCallback? callback) { + set onSemanticsAction(SemanticsActionCallback? callback) { _platformDispatcher.onSemanticsAction = callback; } @override - ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeaturesTestValue ?? _platformDispatcher.accessibilityFeatures; - ui.AccessibilityFeatures? _accessibilityFeaturesTestValue; + AccessibilityFeatures get accessibilityFeatures => _accessibilityFeaturesTestValue ?? _platformDispatcher.accessibilityFeatures; + AccessibilityFeatures? _accessibilityFeaturesTestValue; /// Hides the real accessibility features and reports the given /// [accessibilityFeaturesTestValue] instead. /// /// Consider using [FakeAccessibilityFeatures] to provide specific /// values for the various accessibility features under test. - set accessibilityFeaturesTestValue(ui.AccessibilityFeatures accessibilityFeaturesTestValue) { // ignore: avoid_setters_without_getters + set accessibilityFeaturesTestValue(AccessibilityFeatures accessibilityFeaturesTestValue) { // ignore: avoid_setters_without_getters _accessibilityFeaturesTestValue = accessibilityFeaturesTestValue; onAccessibilityFeaturesChanged?.call(); } + /// Deletes any existing test accessibility features and returns to using the /// real accessibility features. void clearAccessibilityFeaturesTestValue() { @@ -850,9 +404,9 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { } @override - ui.VoidCallback? get onAccessibilityFeaturesChanged => _platformDispatcher.onAccessibilityFeaturesChanged; + VoidCallback? get onAccessibilityFeaturesChanged => _platformDispatcher.onAccessibilityFeaturesChanged; @override - set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { + set onAccessibilityFeaturesChanged(VoidCallback? callback) { _platformDispatcher.onAccessibilityFeaturesChanged = callback; } @@ -865,7 +419,7 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { void sendPlatformMessage( String name, ByteData? data, - ui.PlatformMessageResponseCallback? callback, + PlatformMessageResponseCallback? callback, ) { _platformDispatcher.sendPlatformMessage(name, data, callback); } @@ -875,22 +429,22 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { 'This feature was deprecated after v2.1.0-10.0.pre.' ) @override - ui.PlatformMessageCallback? get onPlatformMessage => _platformDispatcher.onPlatformMessage; + PlatformMessageCallback? get onPlatformMessage => _platformDispatcher.onPlatformMessage; @Deprecated( 'Instead of setting this callback, use ServicesBinding.instance.defaultBinaryMessenger.setMessageHandler. ' 'This feature was deprecated after v2.1.0-10.0.pre.' ) @override - set onPlatformMessage(ui.PlatformMessageCallback? callback) { + set onPlatformMessage(PlatformMessageCallback? callback) { _platformDispatcher.onPlatformMessage = callback; } /// Delete any test value properties that have been set on this [TestPlatformDispatcher] - /// and return to reporting the real [ui.PlatformDispatcher] values for all + /// and return to reporting the real [PlatformDispatcher] values for all /// [PlatformDispatcher] properties. /// /// If desired, clearing of properties can be done on an individual basis, - /// e.g., [clearLocaleTestValue()]. + /// e.g., [clearLocaleTestValue]. void clearAllTestValues() { clearAccessibilityFeaturesTestValue(); clearAlwaysUse24HourTestValue(); @@ -901,42 +455,53 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { clearSemanticsEnabledTestValue(); clearTextScaleFactorTestValue(); clearNativeSpellCheckServiceDefined(); + resetBrieflyShowPassword(); + resetInitialLifecycleState(); } @override - ui.VoidCallback? get onFrameDataChanged => _platformDispatcher.onFrameDataChanged; + VoidCallback? get onFrameDataChanged => _platformDispatcher.onFrameDataChanged; @override - set onFrameDataChanged(ui.VoidCallback? value) { + set onFrameDataChanged(VoidCallback? value) { _platformDispatcher.onFrameDataChanged = value; } @override - ui.KeyDataCallback? get onKeyData => _platformDispatcher.onKeyData; + KeyDataCallback? get onKeyData => _platformDispatcher.onKeyData; @override - set onKeyData(ui.KeyDataCallback? onKeyData) { + set onKeyData(KeyDataCallback? onKeyData) { _platformDispatcher.onKeyData = onKeyData; } @override - ui.VoidCallback? get onPlatformConfigurationChanged => _platformDispatcher.onPlatformConfigurationChanged; + VoidCallback? get onPlatformConfigurationChanged => _platformDispatcher.onPlatformConfigurationChanged; @override - set onPlatformConfigurationChanged(ui.VoidCallback? onPlatformConfigurationChanged) { + set onPlatformConfigurationChanged(VoidCallback? onPlatformConfigurationChanged) { _platformDispatcher.onPlatformConfigurationChanged = onPlatformConfigurationChanged; } @override - ui.Locale? computePlatformResolvedLocale(List supportedLocales) => _platformDispatcher.computePlatformResolvedLocale(supportedLocales); - - @override - ui.FrameData get frameData => _platformDispatcher.frameData; + Locale? computePlatformResolvedLocale(List supportedLocales) => _platformDispatcher.computePlatformResolvedLocale(supportedLocales); @override ByteData? getPersistentIsolateData() => _platformDispatcher.getPersistentIsolateData(); @override - Iterable get views => _platformDispatcher.views; + Iterable get views => _testViews.values; + + void _updateViews() { + final List extraKeys = [..._testViews.keys]; + for (final FlutterView view in _platformDispatcher.views) { + extraKeys.remove(view.viewId); + if (!_testViews.containsKey(view.viewId)) { + _testViews[view.viewId] = TestFlutterView(view: view, platformDispatcher: this); + } + } + + extraKeys.forEach(_testViews.remove); + } /// This gives us some grace time when the dart:ui side adds something to /// [PlatformDispatcher], and makes things easier when we do rolls to give @@ -946,3 +511,883 @@ class TestPlatformDispatcher implements ui.PlatformDispatcher { return null; } } + +/// A [FlutterView] that wraps another [FlutterView] and allows faking of +/// some properties for testing purposes. +/// +/// This class should not be instantiated manually, as +/// it requires a backing [FlutterView] that must be produced from +/// a [PlatformDispatcher]. +/// +/// See also: +/// +/// * [WidgetTester.view] which allows for accessing the [TestFlutterView] +/// for single view applications or widget testing. +/// * [WidgetTester.viewOf] which allows for accessing the appropriate +/// [TestFlutterView] in a given situation for multi-view applications. +/// * [TestPlatformDispatcher], which allows for faking of platform specific +/// functionality. +class TestFlutterView implements FlutterView { + /// Constructs a [TestFlutterView] that defers all behavior to the given + /// [FlutterView] unless explicitly overridden for testing. + TestFlutterView({ + required FlutterView view, + required TestPlatformDispatcher platformDispatcher, + }) : _view = view, _platformDispatcher = platformDispatcher; + + /// The [FlutterView] backing this [TestFlutterView]. + final FlutterView _view; + + @override + TestPlatformDispatcher get platformDispatcher => _platformDispatcher; + final TestPlatformDispatcher _platformDispatcher; + + @override + Object get viewId => _view.viewId; + + /// The device pixel ratio to use for this test. + /// + /// Defaults to the value provided by [FlutterView.devicePixelRatio]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FlutterView.devicePixelRatio] for the standard implementation + /// * [resetDevicePixelRatio] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + double get devicePixelRatio => _devicePixelRatio ?? _view.devicePixelRatio; + double? _devicePixelRatio; + set devicePixelRatio(double value) { + _devicePixelRatio = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [devicePixelRatio] for this test view to the default value for this view. + void resetDevicePixelRatio() { + _devicePixelRatio = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The display features to use for this test. + /// + /// Defaults to the value provided by [FlutterView.displayFeatures]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FlutterView.displayFeatures] for the standard implementation + /// * [resetDisplayFeatures] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + List get displayFeatures => _displayFeatures ?? _view.displayFeatures; + List? _displayFeatures; + set displayFeatures(List value) { + _displayFeatures = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [displayFeatures] to the default values for this view. + void resetDisplayFeatures() { + _displayFeatures = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The padding to use for this test. + /// + /// Defaults to the value provided by [FlutterView.padding]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FakeViewPadding] which is used to set this value for testing + /// * [FlutterView.padding] for the standard implementation. + /// * [resetPadding] to reset this value specifically. + /// * [reset] to reset all test values for this view. + @override + FakeViewPadding get padding => _padding ?? FakeViewPadding._wrap(_view.padding); + FakeViewPadding? _padding; + set padding(FakeViewPadding value) { + _padding = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [padding] to the default value for this view. + void resetPadding() { + _padding = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The physical geometry to use for this test. + /// + /// Defaults to the value provided by [FlutterView.physicalGeometry]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// This property and [physicalSize] are dependent on one another. If both + /// properties are set through their test setters, the final result will be + /// that [physicalGeometry] determines the location and [physicalSize] + /// determines the size of the [physicalGeometry] [Rect]. If only + /// [physicalSize] is set, the final result is that the default value of + /// [physicalGeometry] determines the location and [physicalSize] determines + /// the size of the [physicalGeometry] [Rect]. If only [physicalGeometry] + /// is set, it will determine both the location and size of the + /// [physicalGeometry] [Rect]. + /// + /// See also: + /// + /// * [FlutterView.physicalGeometry] for the standard implementation + /// * [resetPhysicalGeometry] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + Rect get physicalGeometry { + Rect value = _physicalGeometry ?? _view.physicalGeometry; + if (_physicalSize != null) { + value = value.topLeft & _physicalSize!; + } + return value; + } + Rect? _physicalGeometry; + set physicalGeometry(Rect value) { + _physicalGeometry = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [physicalGeometry] to the default value for this view. + /// + /// This will also reset [physicalSize] as the values are dependent + /// on one another. + void resetPhysicalGeometry() { + _physicalGeometry = null; + _physicalSize = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The physical size to use for this test. + /// + /// Defaults to the value provided by [FlutterView.physicalSize]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// This property and [physicalGeometry] are dependent on one another. If both + /// properties are set through their test setters, the final result will be + /// that [physicalGeometry] determines the location and [physicalSize] + /// determines the size of the [physicalGeometry] [Rect]. If only + /// [physicalSize] is set, the final result is that the default value of + /// [physicalGeometry] determines the location and [physicalSize] determines + /// the size of the [physicalGeometry] [Rect]. If only [physicalGeometry] + /// is set, it will determine both the location and size of the + /// [physicalGeometry] [Rect]. + /// + /// See also: + /// + /// * [FlutterView.physicalSize] for the standard implementation + /// * [resetPhysicalSize] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + Size get physicalSize { + // This has to be able to default to `_view.physicalSize` as web_ui handles + // `physicalSize` and `physicalGeometry` differently than dart:ui, where + // the values are both based off of `physicalGeometry`. + return _physicalSize ?? _physicalGeometry?.size ?? _view.physicalSize; + } + Size? _physicalSize; + set physicalSize(Size value) { + _physicalSize = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [physicalSize] to the default value for this view. + /// + /// This will also reset [physicalGeometry] as the values are dependent + /// on one another. + void resetPhysicalSize() { + resetPhysicalGeometry(); + } + + /// The system gesture insets to use for this test. + /// + /// Defaults to the value provided by [FlutterView.systemGestureInsets]. + /// This can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FakeViewPadding] which is used to set this value for testing + /// * [FlutterView.systemGestureInsets] for the standard implementation + /// * [resetSystemGestureInsets] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + FakeViewPadding get systemGestureInsets => _systemGestureInsets ?? FakeViewPadding._wrap(_view.systemGestureInsets); + FakeViewPadding? _systemGestureInsets; + set systemGestureInsets(FakeViewPadding value) { + _systemGestureInsets = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [systemGestureInsets] to the default value for this view. + void resetSystemGestureInsets() { + _systemGestureInsets = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The view insets to use for this test. + /// + /// Defaults to the value provided by [FlutterView.viewInsets]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FakeViewPadding] which is used to set this value for testing + /// * [FlutterView.viewInsets] for the standard implementation + /// * [resetViewInsets] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + FakeViewPadding get viewInsets => _viewInsets ?? FakeViewPadding._wrap(_view.viewInsets); + FakeViewPadding? _viewInsets; + set viewInsets(FakeViewPadding value) { + _viewInsets = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [viewInsets] to the default value for this view. + void resetViewInsets() { + _viewInsets = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The view padding to use for this test. + /// + /// Defaults to the value provided by [FlutterView.viewPadding]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FakeViewPadding] which is used to set this value for testing + /// * [FlutterView.viewPadding] for the standard implementation + /// * [resetViewPadding] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + FakeViewPadding get viewPadding => _viewPadding ?? FakeViewPadding._wrap(_view.viewPadding); + FakeViewPadding? _viewPadding; + set viewPadding(FakeViewPadding value) { + _viewPadding = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [viewPadding] to the default value for this view. + void resetViewPadding() { + _viewPadding = null; + platformDispatcher.onMetricsChanged?.call(); + } + + /// The gesture settings to use for this test. + /// + /// Defaults to the value provided by [FlutterView.gestureSettings]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FlutterView.gestureSettings] for the standard implementation + /// * [resetGestureSettings] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + GestureSettings get gestureSettings => _gestureSettings ?? _view.gestureSettings; + GestureSettings? _gestureSettings; + set gestureSettings(GestureSettings value) { + _gestureSettings = value; + platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [gestureSettings] to the default value for this view. + void resetGestureSettings() { + _gestureSettings = null; + platformDispatcher.onMetricsChanged?.call(); + } + + @override + void render(Scene scene) { + _view.render(scene); + } + + @override + void updateSemantics(SemanticsUpdate update) { + _view.updateSemantics(update); + } + + /// Resets all test values to the defaults for this view. + /// + /// See also: + /// + /// * [resetDevicePixelRatio] to reset [devicePixelRatio] specifically + /// * [resetDisplayFeatures] to reset [displayFeatures] specifically + /// * [resetPadding] to reset [padding] specifically + /// * [resetPhysicalGeometry] to reset [physicalGeometry] specifically + /// * [resetPhysicalSize] to reset [physicalSize] specifically + /// * [resetSystemGestureInsets] to reset [systemGestureInsets] specifically + /// * [resetViewInsets] to reset [viewInsets] specifically + /// * [resetViewPadding] to reset [viewPadding] specifically + /// * [resetGestureSettings] to reset [gestureSettings] specifically + void reset() { + resetDevicePixelRatio(); + resetDisplayFeatures(); + resetPadding(); + resetPhysicalGeometry(); + // Skipping resetPhysicalSize because resetPhysicalGeometry resets both values. + resetSystemGestureInsets(); + resetViewInsets(); + resetViewPadding(); + resetGestureSettings(); + } + + /// This gives us some grace time when the dart:ui side adds something to + /// [FlutterView], and makes things easier when we do rolls to give + /// us time to catch up. + @override + dynamic noSuchMethod(Invocation invocation) { + return null; + } +} + +/// [SingletonFlutterWindow] that wraps another [SingletonFlutterWindow] and +/// allows faking of some properties for testing purposes. +/// +/// Tests for certain widgets, e.g., [MaterialApp], might require faking certain +/// properties of a [SingletonFlutterWindow]. [TestWindow] facilitates the +/// faking of these properties by overriding the properties of a real +/// [SingletonFlutterWindow] with desired fake values. The binding used within +/// tests, [TestWidgetsFlutterBinding], contains a [TestWindow] that is used by +/// all tests. +/// +/// ## Sample Code +/// +/// A test can utilize a [TestWindow] in the following way: +/// +/// ```dart +/// testWidgets('your test name here', (WidgetTester tester) async { +/// // Retrieve the TestWidgetsFlutterBinding. +/// final TestWidgetsFlutterBinding testBinding = tester.binding; +/// +/// // Fake the desired properties of the TestWindow. All code running +/// // within this test will perceive the following fake text scale +/// // factor as the real text scale factor of the window. +/// testBinding.window.textScaleFactorFakeValue = 2.5; +/// +/// // Test code that depends on text scale factor here. +/// }); +/// ``` +/// +/// The [TestWidgetsFlutterBinding] is recreated for each test and +/// therefore any fake values defined in one test will not persist +/// to the next. +/// +/// If a test needs to override a real [SingletonFlutterWindow] property and +/// then later return to using the real [SingletonFlutterWindow] property, +/// [TestWindow] provides methods to clear each individual test value, e.g., +/// [clearLocaleTestValue()]. +/// +/// To clear all fake test values in a [TestWindow], consider using +/// [clearAllTestValues()]. +/// +/// See also: +/// +/// * [TestPlatformDispatcher], which wraps a [PlatformDispatcher] for +/// testing purposes and is used by the [platformDispatcher] property of +/// this class. +class TestWindow implements SingletonFlutterWindow { + /// Constructs a [TestWindow] that defers all behavior to the given + /// [SingletonFlutterWindow] unless explicitly overridden for test purposes. + TestWindow({ + required SingletonFlutterWindow window, + }) : platformDispatcher = TestPlatformDispatcher(platformDispatcher: window.platformDispatcher); + + /// Constructs a [TestWindow] that defers all behavior to the given + /// [TestPlatformDispatcher] and its [TestPlatformDispatcher.implicitView]. + /// + /// This class will not work when multiple views are present. If multiple view + /// support is needed use [WidgetTester.platformDispatcher] and + /// [WidgetTester.viewOf]. + /// + /// See also: + /// + /// * [TestPlatformDispatcher] which allows faking of platform-wide values for + /// testing purposes. + /// * [TestFlutterView] which allows faking of view-specific values for + /// testing purposes. + TestWindow.fromPlatformDispatcher({ + required this.platformDispatcher, + }); + + @override + final TestPlatformDispatcher platformDispatcher; + + TestFlutterView get _view => platformDispatcher.implicitView!; + + @override + double get devicePixelRatio => _view.devicePixelRatio; + /// Hides the real device pixel ratio and reports the given [devicePixelRatio] + /// instead. + // ignore: avoid_setters_without_getters + set devicePixelRatioTestValue(double devicePixelRatio) { + _view.devicePixelRatio = devicePixelRatio; + } + + /// Deletes any existing test device pixel ratio and returns to using the real + /// device pixel ratio. + void clearDevicePixelRatioTestValue() { + _view.resetDevicePixelRatio(); + } + + @override + Size get physicalSize => _view.physicalSize; + /// Hides the real physical size and reports the given [physicalSizeTestValue] + /// instead. + // ignore: avoid_setters_without_getters + set physicalSizeTestValue (Size physicalSizeTestValue) { + _view.physicalSize = physicalSizeTestValue; + } + + /// Deletes any existing test physical size and returns to using the real + /// physical size. + void clearPhysicalSizeTestValue() { + _view.resetPhysicalSize(); + } + + @override + ViewPadding get viewInsets => _view.viewInsets; + /// Hides the real view insets and reports the given [viewInsetsTestValue] + /// instead. + /// + /// Use [FakeViewPadding] to set this value for testing. + // ignore: avoid_setters_without_getters + set viewInsetsTestValue(ViewPadding value) { + _view.viewInsets = value is FakeViewPadding ? value : FakeViewPadding._wrap(value); + } + + /// Deletes any existing test view insets and returns to using the real view + /// insets. + void clearViewInsetsTestValue() { + _view.resetViewInsets(); + } + + @override + ViewPadding get viewPadding => _view.viewPadding; + /// Hides the real view padding and reports the given [paddingTestValue] + /// instead. + /// + /// Use [FakeViewPadding] to set this value for testing. + // ignore: avoid_setters_without_getters + set viewPaddingTestValue(ViewPadding value) { + _view.viewPadding = value is FakeViewPadding ? value : FakeViewPadding._wrap(value); + } + + /// Deletes any existing test view padding and returns to using the real + /// viewPadding. + void clearViewPaddingTestValue() { + _view.resetViewPadding(); + } + + @override + ViewPadding get padding => _view.padding; + /// Hides the real padding and reports the given [paddingTestValue] instead. + /// + /// Use [FakeViewPadding] to set this value for testing. + // ignore: avoid_setters_without_getters + set paddingTestValue(ViewPadding value) { + _view.padding = value is FakeViewPadding ? value : FakeViewPadding._wrap(value); + } + + /// Deletes any existing test padding and returns to using the real padding. + void clearPaddingTestValue() { + _view.resetPadding(); + } + + @override + GestureSettings get gestureSettings => _view.gestureSettings; + /// Hides the real gesture settings and reports the given [gestureSettingsTestValue] instead. + // ignore: avoid_setters_without_getters + set gestureSettingsTestValue(GestureSettings gestureSettingsTestValue) { + _view.gestureSettings = gestureSettingsTestValue; + } + + /// Deletes any existing test gesture settings and returns to using the real gesture settings. + void clearGestureSettingsTestValue() { + _view.resetGestureSettings(); + } + + @override + List get displayFeatures => _view.displayFeatures; + /// Hides the real displayFeatures and reports the given [displayFeaturesTestValue] instead. + // ignore: avoid_setters_without_getters + set displayFeaturesTestValue(List displayFeaturesTestValue) { + _view.displayFeatures = displayFeaturesTestValue; + } + + /// Deletes any existing test padding and returns to using the real padding. + void clearDisplayFeaturesTestValue() { + _view.resetDisplayFeatures(); + } + + @override + ViewPadding get systemGestureInsets => _view.systemGestureInsets; + /// Hides the real system gesture insets and reports the given + /// [systemGestureInsetsTestValue] instead. + /// + /// Use [FakeViewPadding] to set this value for testing. + set systemGestureInsetsTestValue(ViewPadding value) { // ignore: avoid_setters_without_getters + _view.systemGestureInsets = value is FakeViewPadding ? value : FakeViewPadding._wrap(value); + } + + /// Deletes any existing test system gesture insets and returns to using the real system gesture insets. + void clearSystemGestureInsetsTestValue() { + _view.resetSystemGestureInsets(); + } + + @override + VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; + @override + set onMetricsChanged(VoidCallback? callback) { + platformDispatcher.onMetricsChanged = callback; + } + + @override + Locale get locale => platformDispatcher.locale; + /// Hides the real locale and reports the given [localeTestValue] instead. + @Deprecated( + 'Use platformDispatcher.localeTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set localeTestValue(Locale localeTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.localeTestValue = localeTestValue; + } + @Deprecated( + 'Use platformDispatcher.clearLocaleTestValue() instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + /// Deletes any existing test locale and returns to using the real locale. + void clearLocaleTestValue() { + platformDispatcher.clearLocaleTestValue(); + } + + @override + List get locales => platformDispatcher.locales; + /// Hides the real locales and reports the given [localesTestValue] instead. + @Deprecated( + 'Use platformDispatcher.localesTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set localesTestValue(List localesTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.localesTestValue = localesTestValue; + } + /// Deletes any existing test locales and returns to using the real locales. + @Deprecated( + 'Use platformDispatcher.clearLocalesTestValue() instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearLocalesTestValue() { + platformDispatcher.clearLocalesTestValue(); + } + + @override + VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; + @override + set onLocaleChanged(VoidCallback? callback) { + platformDispatcher.onLocaleChanged = callback; + } + + @override + String get initialLifecycleState => platformDispatcher.initialLifecycleState; + /// Sets a faked initialLifecycleState for testing. + @Deprecated( + 'Use platformDispatcher.initialLifecycleStateTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set initialLifecycleStateTestValue(String state) { // ignore: avoid_setters_without_getters + platformDispatcher.initialLifecycleStateTestValue = state; + } + + @override + double get textScaleFactor => platformDispatcher.textScaleFactor; + /// Hides the real text scale factor and reports the given + /// [textScaleFactorTestValue] instead. + @Deprecated( + 'Use platformDispatcher.textScaleFactorTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set textScaleFactorTestValue(double textScaleFactorTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.textScaleFactorTestValue = textScaleFactorTestValue; + } + /// Deletes any existing test text scale factor and returns to using the real + /// text scale factor. + @Deprecated( + 'Use platformDispatcher.clearTextScaleFactorTestValue() instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearTextScaleFactorTestValue() { + platformDispatcher.clearTextScaleFactorTestValue(); + } + + @override + Brightness get platformBrightness => platformDispatcher.platformBrightness; + @override + VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; + @override + set onPlatformBrightnessChanged(VoidCallback? callback) { + platformDispatcher.onPlatformBrightnessChanged = callback; + } + /// Hides the real text scale factor and reports the given + /// [platformBrightnessTestValue] instead. + @Deprecated( + 'Use platformDispatcher.platformBrightnessTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set platformBrightnessTestValue(Brightness platformBrightnessTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.platformBrightnessTestValue = platformBrightnessTestValue; + } + /// Deletes any existing test platform brightness and returns to using the + /// real platform brightness. + @Deprecated( + 'Use platformDispatcher.clearPlatformBrightnessTestValue() instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearPlatformBrightnessTestValue() { + platformDispatcher.clearPlatformBrightnessTestValue(); + } + + @override + bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; + /// Hides the real clock format and reports the given + /// [alwaysUse24HourFormatTestValue] instead. + @Deprecated( + 'Use platformDispatcher.alwaysUse24HourFormatTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set alwaysUse24HourFormatTestValue(bool alwaysUse24HourFormatTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.alwaysUse24HourFormatTestValue = alwaysUse24HourFormatTestValue; + } + /// Deletes any existing test clock format and returns to using the real clock + /// format. + @Deprecated( + 'Use platformDispatcher.clearAlwaysUse24HourTestValue() instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearAlwaysUse24HourTestValue() { + platformDispatcher.clearAlwaysUse24HourTestValue(); + } + + @override + VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; + @override + set onTextScaleFactorChanged(VoidCallback? callback) { + platformDispatcher.onTextScaleFactorChanged = callback; + } + + @override + bool get nativeSpellCheckServiceDefined => platformDispatcher.nativeSpellCheckServiceDefined; + set nativeSpellCheckServiceDefinedTestValue(bool nativeSpellCheckServiceDefinedTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.nativeSpellCheckServiceDefinedTestValue = nativeSpellCheckServiceDefinedTestValue; + } + + @override + bool get brieflyShowPassword => platformDispatcher.brieflyShowPassword; + /// Hides the real [brieflyShowPassword] and reports the given + /// `brieflyShowPasswordTestValue` instead. + @Deprecated( + 'Use platformDispatcher.brieflyShowPasswordTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set brieflyShowPasswordTestValue(bool brieflyShowPasswordTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.brieflyShowPasswordTestValue = brieflyShowPasswordTestValue; + } + + @override + FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; + @override + set onBeginFrame(FrameCallback? callback) { + platformDispatcher.onBeginFrame = callback; + } + + @override + VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; + @override + set onDrawFrame(VoidCallback? callback) { + platformDispatcher.onDrawFrame = callback; + } + + @override + TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; + @override + set onReportTimings(TimingsCallback? callback) { + platformDispatcher.onReportTimings = callback; + } + + @override + PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; + @override + set onPointerDataPacket(PointerDataPacketCallback? callback) { + platformDispatcher.onPointerDataPacket = callback; + } + + @override + String get defaultRouteName => platformDispatcher.defaultRouteName; + /// Hides the real default route name and reports the given + /// [defaultRouteNameTestValue] instead. + @Deprecated( + 'Use platformDispatcher.defaultRouteNameTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set defaultRouteNameTestValue(String defaultRouteNameTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.defaultRouteNameTestValue = defaultRouteNameTestValue; + } + /// Deletes any existing test default route name and returns to using the real + /// default route name. + @Deprecated( + 'Use platformDispatcher.clearDefaultRouteNameTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearDefaultRouteNameTestValue() { + platformDispatcher.clearDefaultRouteNameTestValue(); + } + + @override + void scheduleFrame() { + platformDispatcher.scheduleFrame(); + } + + @override + void render(Scene scene) { + _view.render(scene); + } + + @override + bool get semanticsEnabled => platformDispatcher.semanticsEnabled; + /// Hides the real semantics enabled and reports the given + /// [semanticsEnabledTestValue] instead. + @Deprecated( + 'Use platformDispatcher.semanticsEnabledTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set semanticsEnabledTestValue(bool semanticsEnabledTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.semanticsEnabledTestValue = semanticsEnabledTestValue; + } + /// Deletes any existing test semantics enabled and returns to using the real + /// semantics enabled. + @Deprecated( + 'Use platformDispatcher.clearSemanticsEnabledTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearSemanticsEnabledTestValue() { + platformDispatcher.clearSemanticsEnabledTestValue(); + } + + @override + VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; + @override + set onSemanticsEnabledChanged(VoidCallback? callback) { + platformDispatcher.onSemanticsEnabledChanged = callback; + } + + @override + SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; + @override + set onSemanticsAction(SemanticsActionCallback? callback) { + platformDispatcher.onSemanticsAction = callback; + } + + @override + AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; + /// Hides the real accessibility features and reports the given + /// [accessibilityFeaturesTestValue] instead. + /// + /// Consider using [FakeAccessibilityFeatures] to provide specific + /// values for the various accessibility features under test. + @Deprecated( + 'Use platformDispatcher.accessibilityFeaturesTestValue instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + set accessibilityFeaturesTestValue(AccessibilityFeatures accessibilityFeaturesTestValue) { // ignore: avoid_setters_without_getters + platformDispatcher.accessibilityFeaturesTestValue = accessibilityFeaturesTestValue; + } + /// Deletes any existing test accessibility features and returns to using the + /// real accessibility features. + @Deprecated( + 'Use platformDispatcher.clearAccessibilityFeaturesTestValue() instead. ' + 'This feature was deprecated after v2.11.0-0.0.pre.' + ) + void clearAccessibilityFeaturesTestValue() { + platformDispatcher.clearAccessibilityFeaturesTestValue(); + } + + @override + VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged; + @override + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + platformDispatcher.onAccessibilityFeaturesChanged = callback; + } + + @override + void updateSemantics(SemanticsUpdate update) { + _view.updateSemantics(update); + } + + @override + void setIsolateDebugName(String name) { + platformDispatcher.setIsolateDebugName(name); + } + + @override + void sendPlatformMessage( + String name, + ByteData? data, + PlatformMessageResponseCallback? callback, + ) { + platformDispatcher.sendPlatformMessage(name, data, callback); + } + + @Deprecated( + 'Instead of calling this callback, use ServicesBinding.instance.channelBuffers.push. ' + 'This feature was deprecated after v2.1.0-10.0.pre.' + ) + @override + PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; + @Deprecated( + 'Instead of setting this callback, use ServicesBinding.instance.defaultBinaryMessenger.setMessageHandler. ' + 'This feature was deprecated after v2.1.0-10.0.pre.' + ) + @override + set onPlatformMessage(PlatformMessageCallback? callback) { + platformDispatcher.onPlatformMessage = callback; + } + + /// Delete any test value properties that have been set on this [TestWindow] + /// as well as its [platformDispatcher]. + /// + /// After calling this, the real [SingletonFlutterWindow] and + /// [PlatformDispatcher] values are reported again. + /// + /// If desired, clearing of properties can be done on an individual basis, + /// e.g., [clearLocaleTestValue]. + void clearAllTestValues() { + clearDevicePixelRatioTestValue(); + clearPaddingTestValue(); + clearGestureSettingsTestValue(); + clearDisplayFeaturesTestValue(); + clearPhysicalSizeTestValue(); + clearViewInsetsTestValue(); + platformDispatcher.clearAllTestValues(); + } + + /// This gives us some grace time when the dart:ui side adds something to + /// [SingletonFlutterWindow], and makes things easier when we do rolls to give + /// us time to catch up. + @override + dynamic noSuchMethod(Invocation invocation) { + return null; + } +} diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index b82ad818d1..9e73da96fd 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -711,6 +711,25 @@ void main() { }); }); + testWidgets('platformDispatcher exposes the platformDispatcher from binding', (WidgetTester tester) async { + expect(tester.platformDispatcher, tester.binding.platformDispatcher); + }); + + testWidgets('view exposes the implicitView from platformDispatcher', (WidgetTester tester) async { + expect(tester.view, tester.platformDispatcher.implicitView); + }); + + testWidgets('viewOf finds a view when the view is implicit', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp( + home: Center( + child: Text('Test'), + ) + )); + + expect(() => tester.viewOf(find.text('Test')), isNot(throwsA(anything))); + expect(tester.viewOf(find.text('Test')), isA()); + }); + group('SemanticsController', () { group('find', () { testWidgets('throws when there are no semantics', (WidgetTester tester) async { diff --git a/packages/flutter_test/test/platform_dispatcher_test.dart b/packages/flutter_test/test/platform_dispatcher_test.dart index 971c3b14ce..5940f3adf0 100644 --- a/packages/flutter_test/test/platform_dispatcher_test.dart +++ b/packages/flutter_test/test/platform_dispatcher_test.dart @@ -7,15 +7,17 @@ import 'dart:ui' show AccessibilityFeatures, Brightness, Locale, PlatformDispatc import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver; import 'package:flutter_test/flutter_test.dart'; +import 'utils/fake_and_mock_utils.dart'; + void main() { - test('TestWindow can handle new methods without breaking', () { + test('TestPlatformDispatcher can handle new methods without breaking', () { final dynamic testPlatformDispatcher = TestPlatformDispatcher(platformDispatcher: PlatformDispatcher.instance); // ignore: avoid_dynamic_calls expect(testPlatformDispatcher.someNewProperty, null); }); - testWidgets('TestWindow can fake locale', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake locale', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: PlatformDispatcher.instance.locale, fakeValue: const Locale('fake_language_code'), @@ -28,8 +30,8 @@ void main() { ); }); - testWidgets('TestWindow can fake locales', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty>( + testWidgets('TestPlatformDispatcher can fake locales', (WidgetTester tester) async { + verifyPropertyFaked>( tester: tester, realValue: PlatformDispatcher.instance.locales, fakeValue: [const Locale('fake_language_code')], @@ -42,8 +44,8 @@ void main() { ); }); - testWidgets('TestWindow can fake text scale factor', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake text scale factor', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: PlatformDispatcher.instance.textScaleFactor, fakeValue: 2.5, @@ -56,8 +58,8 @@ void main() { ); }); - testWidgets('TestWindow can fake clock format', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake clock format', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: PlatformDispatcher.instance.alwaysUse24HourFormat, fakeValue: !PlatformDispatcher.instance.alwaysUse24HourFormat, @@ -70,8 +72,8 @@ void main() { ); }); - testWidgets('TestWindow can fake brieflyShowPassword', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake brieflyShowPassword', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: PlatformDispatcher.instance.brieflyShowPassword, fakeValue: !PlatformDispatcher.instance.brieflyShowPassword, @@ -82,8 +84,8 @@ void main() { ); }); - testWidgets('TestWindow can fake default route name', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake default route name', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: PlatformDispatcher.instance.defaultRouteName, fakeValue: 'fake_route', @@ -96,8 +98,8 @@ void main() { ); }); - testWidgets('TestWindow can fake accessibility features', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake accessibility features', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: PlatformDispatcher.instance.accessibilityFeatures, fakeValue: const FakeAccessibilityFeatures(), @@ -110,8 +112,8 @@ void main() { ); }); - testWidgets('TestWindow can fake platform brightness', (WidgetTester tester) async { - verifyThatTestPlatformDispatcherCanFakeProperty( + testWidgets('TestPlatformDispatcher can fake platform brightness', (WidgetTester tester) async { + verifyPropertyFaked( tester: tester, realValue: Brightness.light, fakeValue: Brightness.dark, @@ -124,7 +126,7 @@ void main() { ); }); - testWidgets('TestWindow can clear out fake properties all at once', (WidgetTester tester) async { + testWidgets('TestPlatformDispatcher can clear out fake properties all at once', (WidgetTester tester) async { final Locale originalLocale = PlatformDispatcher.instance.locale; final double originalTextScaleFactor = PlatformDispatcher.instance.textScaleFactor; final TestPlatformDispatcher testPlatformDispatcher = retrieveTestBinding(tester).platformDispatcher; @@ -141,7 +143,7 @@ void main() { expect(WidgetsBinding.instance.platformDispatcher.textScaleFactor, originalTextScaleFactor); }); - testWidgets('TestWindow sends fake locales when WidgetsBindingObserver notifiers are called', (WidgetTester tester) async { + testWidgets('TestPlatformDispatcher sends fake locales when WidgetsBindingObserver notifiers are called', (WidgetTester tester) async { final List defaultLocales = WidgetsBinding.instance.platformDispatcher.locales; final TestObserver observer = TestObserver(); retrieveTestBinding(tester).addObserver(observer); @@ -152,36 +154,8 @@ void main() { }); } -void verifyThatTestPlatformDispatcherCanFakeProperty({ - required WidgetTester tester, - required PlatformDispatcherPropertyType? realValue, - required PlatformDispatcherPropertyType fakeValue, - required PlatformDispatcherPropertyType? Function() propertyRetriever, - required Function(TestWidgetsFlutterBinding, PlatformDispatcherPropertyType fakeValue) propertyFaker, -}) { - PlatformDispatcherPropertyType? propertyBeforeFaking; - PlatformDispatcherPropertyType? propertyAfterFaking; - - propertyBeforeFaking = propertyRetriever(); - - propertyFaker(retrieveTestBinding(tester), fakeValue); - - propertyAfterFaking = propertyRetriever(); - - expect(propertyBeforeFaking, realValue); - expect(propertyAfterFaking, fakeValue); -} - -TestWidgetsFlutterBinding retrieveTestBinding(WidgetTester tester) { - final WidgetsBinding binding = tester.binding; - assert(binding is TestWidgetsFlutterBinding); - final TestWidgetsFlutterBinding testBinding = binding as TestWidgetsFlutterBinding; - return testBinding; -} - class TestObserver with WidgetsBindingObserver { List? locales; - Locale? locale; @override void didChangeLocales(List? locales) { diff --git a/packages/flutter_test/test/utils/fake_and_mock_utils.dart b/packages/flutter_test/test/utils/fake_and_mock_utils.dart new file mode 100644 index 0000000000..db2b7ba438 --- /dev/null +++ b/packages/flutter_test/test/utils/fake_and_mock_utils.dart @@ -0,0 +1,114 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +TestWidgetsFlutterBinding retrieveTestBinding(WidgetTester tester) { + final WidgetsBinding binding = tester.binding; + assert(binding is TestWidgetsFlutterBinding); + final TestWidgetsFlutterBinding testBinding = binding as TestWidgetsFlutterBinding; + return testBinding; +} + +void verifyPropertyFaked({ + required WidgetTester tester, + required TProperty realValue, + required TProperty fakeValue, + required TProperty Function() propertyRetriever, + required Function(TestWidgetsFlutterBinding, TProperty fakeValue) propertyFaker, + Matcher Function(TProperty) matcher = equals, +}) { + TProperty propertyBeforeFaking; + TProperty propertyAfterFaking; + + propertyBeforeFaking = propertyRetriever(); + + propertyFaker(retrieveTestBinding(tester), fakeValue); + + propertyAfterFaking = propertyRetriever(); + + expect( + realValue == fakeValue, + isFalse, + reason: 'Since the real value and fake value are equal, we cannot validate ' + 'that a property has been faked. Choose a different fake value to test.', + ); + expect(propertyBeforeFaking, matcher(realValue)); + expect(propertyAfterFaking, matcher(fakeValue)); +} + +void verifyPropertyReset({ + required WidgetTester tester, + required TProperty fakeValue, + required TProperty Function() propertyRetriever, + required Function() propertyResetter, + required Function(TProperty fakeValue) propertyFaker, + Matcher Function(TProperty) matcher = equals, +}) { + TProperty propertyBeforeFaking; + TProperty propertyAfterFaking; + TProperty propertyAfterReset; + + propertyBeforeFaking = propertyRetriever(); + + propertyFaker(fakeValue); + + propertyAfterFaking = propertyRetriever(); + + propertyResetter(); + + propertyAfterReset = propertyRetriever(); + + expect(propertyAfterFaking, matcher(fakeValue)); + expect(propertyAfterReset, matcher(propertyBeforeFaking)); +} + +Matcher matchesViewPadding(ViewPadding expected) => _FakeViewPaddingMatcher(expected); + +class _FakeViewPaddingMatcher extends Matcher { + _FakeViewPaddingMatcher(this.expected); + + final ViewPadding expected; + + @override + Description describe(Description description) { + description.add('two ViewPadding instances match'); + return description; + } + + @override + Description describeMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) { + assert(item is ViewPadding, 'Can only match against implementations of ViewPadding.'); + final ViewPadding actual = item as ViewPadding; + + if (actual.left != expected.left) { + mismatchDescription.add('actual.left (${actual.left}) did not match expected.left (${expected.left})'); + } + if (actual.top != expected.top) { + mismatchDescription.add('actual.top (${actual.top}) did not match expected.top (${expected.top})'); + } + if (actual.right != expected.right) { + mismatchDescription.add('actual.right (${actual.right}) did not match expected.right (${expected.right})'); + } + if (actual.bottom != expected.bottom) { + mismatchDescription.add('actual.bottom (${actual.bottom}) did not match expected.bottom (${expected.bottom})'); + } + + return mismatchDescription; + } + + @override + bool matches(dynamic item, Map matchState) { + assert(item is ViewPadding, 'Can only match against implementations of ViewPadding.'); + final ViewPadding actual = item as ViewPadding; + + return actual.left == expected.left && + actual.top == expected.top && + actual.right == expected.right && + actual.bottom == expected.bottom; + } +} diff --git a/packages/flutter_test/test/view_test.dart b/packages/flutter_test/test/view_test.dart new file mode 100644 index 0000000000..721f0f67f9 --- /dev/null +++ b/packages/flutter_test/test/view_test.dart @@ -0,0 +1,445 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils/fake_and_mock_utils.dart'; + +void main() { + group('TestFlutterView', () { + FlutterView trueImplicitView() => PlatformDispatcher.instance.implicitView!; + FlutterView boundImplicitView() => WidgetsBinding.instance.platformDispatcher.implicitView!; + + tearDown(() { + final TestFlutterView view = (WidgetsBinding.instance as TestWidgetsFlutterBinding).platformDispatcher.views.single; + view.reset(); + }); + + testWidgets('can handle new methods without breaking', (WidgetTester tester) async { + final dynamic testView = tester.view; + // ignore: avoid_dynamic_calls + expect(testView.someNewProperty, null); + }); + + testWidgets('can fake devicePixelRatio', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().devicePixelRatio, + fakeValue: 2.5, + propertyRetriever: () => boundImplicitView().devicePixelRatio, + propertyFaker: (_, double fakeValue) { + tester.view.devicePixelRatio = fakeValue; + }, + ); + }); + + testWidgets('can reset devicePixelRatio', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: 2.5, + propertyRetriever: () => boundImplicitView().devicePixelRatio, + propertyResetter: () { + tester.view.resetDevicePixelRatio(); + }, + propertyFaker: (double fakeValue) { + tester.view.devicePixelRatio = fakeValue; + }, + ); + }); + + testWidgets('can fake displayFeatures', (WidgetTester tester) async { + verifyPropertyFaked>( + tester: tester, + realValue: trueImplicitView().displayFeatures, + fakeValue: [const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 500, 30), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)], + propertyRetriever: () => boundImplicitView().displayFeatures, + propertyFaker: (_, List fakeValue) { + tester.view.displayFeatures = fakeValue; + }, + ); + }); + + testWidgets('can reset displayFeatures', (WidgetTester tester) async { + verifyPropertyReset>( + tester: tester, + fakeValue: [const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 500, 30), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)], + propertyRetriever: () => boundImplicitView().displayFeatures, + propertyResetter: () { + tester.view.resetDisplayFeatures(); + }, + propertyFaker: (List fakeValue) { + tester.view.displayFeatures = fakeValue; + }, + ); + }); + + testWidgets('can fake padding', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().padding, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().padding, + propertyFaker: (_, ViewPadding fakeValue) { + tester.view.padding = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can reset padding', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().padding, + propertyResetter: () { + tester.view.resetPadding(); + }, + propertyFaker: (ViewPadding fakeValue) { + tester.view.padding = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can fake physicalGeometry', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().physicalGeometry, + fakeValue: const Rect.fromLTWH(0, 0, 550, 850), + propertyRetriever: () => boundImplicitView().physicalGeometry, + propertyFaker: (_, Rect fakeValue) { + tester.view.physicalGeometry = fakeValue; + }, + ); + }); + + testWidgets('can reset physicalGeometry', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const Rect.fromLTWH(0, 0, 35, 475), + propertyRetriever: () => boundImplicitView().physicalGeometry, + propertyResetter: () { + tester.view.resetPhysicalGeometry(); + }, + propertyFaker: (Rect fakeValue) { + tester.view.physicalGeometry = fakeValue; + }, + ); + }); + + testWidgets('updating physicalGeometry also updates physicalSize', (WidgetTester tester) async { + const Rect testGeometry = Rect.fromLTWH(0, 0, 450, 575); + tester.view.physicalGeometry = testGeometry; + + expect(tester.view.physicalSize, testGeometry.size); + }); + + testWidgets('can fake physicalSize', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().physicalSize, + fakeValue: const Size(50, 50), + propertyRetriever: () => boundImplicitView().physicalSize, + propertyFaker: (_, Size fakeValue) { + tester.view.physicalSize = fakeValue; + }, + ); + }); + + testWidgets('can reset physicalSize', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const Size(50, 50), + propertyRetriever: () => boundImplicitView().physicalSize, + propertyResetter: () { + tester.view.resetPhysicalSize(); + }, + propertyFaker: (Size fakeValue) { + tester.view.physicalSize = fakeValue; + }, + ); + }); + + testWidgets('updating physicalSize also updates physicalGeometry', (WidgetTester tester) async { + const Rect testGeometry = Rect.fromLTWH(0, 0, 450, 575); + const Size testSize = Size(50, 50); + const Rect expectedGeometry = Rect.fromLTWH(0, 0, 50, 50); + + tester.view.physicalGeometry = testGeometry; + tester.view.physicalSize = testSize; + + expect(tester.view.physicalGeometry, expectedGeometry); + }); + + testWidgets('can fake systemGestureInsets', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().systemGestureInsets, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().systemGestureInsets, + propertyFaker: (_, ViewPadding fakeValue) { + tester.view.systemGestureInsets = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can reset systemGestureInsets', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().systemGestureInsets, + propertyResetter: () { + tester.view.resetSystemGestureInsets(); + }, + propertyFaker: (ViewPadding fakeValue) { + tester.view.systemGestureInsets = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can fake viewInsets', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().viewInsets, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().viewInsets, + propertyFaker: (_, ViewPadding fakeValue) { + tester.view.viewInsets = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can reset viewInsets', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().viewInsets, + propertyResetter: () { + tester.view.resetViewInsets(); + }, + propertyFaker: (ViewPadding fakeValue) { + tester.view.viewInsets = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can fake viewPadding', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().viewPadding, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().viewPadding, + propertyFaker: (_, ViewPadding fakeValue) { + tester.view.viewPadding = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can reset viewPadding', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const FakeViewPadding(), + propertyRetriever: () => boundImplicitView().viewPadding, + propertyResetter: () { + tester.view.resetViewPadding(); + }, + propertyFaker: (ViewPadding fakeValue) { + tester.view.viewPadding = fakeValue as FakeViewPadding; + }, + matcher: matchesViewPadding + ); + }); + + testWidgets('can clear out fake properties all at once', (WidgetTester tester) async { + final FlutterViewSnapshot initial = FlutterViewSnapshot(tester.view); + + tester.view.devicePixelRatio = 7; + tester.view.displayFeatures = [const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 20, 300), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)]; + tester.view.padding = const FakeViewPadding(); + tester.view.physicalGeometry = const Rect.fromLTWH(0, 0, 505, 805); + tester.view.systemGestureInsets = const FakeViewPadding(); + tester.view.viewInsets = const FakeViewPadding(); + tester.view.viewPadding = const FakeViewPadding(); + tester.view.gestureSettings = const GestureSettings(physicalTouchSlop: 4, physicalDoubleTapSlop: 5); + + final FlutterViewSnapshot faked = FlutterViewSnapshot(tester.view); + + tester.view.reset(); + + final FlutterViewSnapshot reset = FlutterViewSnapshot(tester.view); + + expect(initial, isNot(matchesSnapshot(faked))); + expect(initial, matchesSnapshot(reset)); + }); + + testWidgets('render is passed through to backing FlutterView', (WidgetTester tester) async { + final Scene expectedScene = SceneBuilder().build(); + final _FakeFlutterView backingView = _FakeFlutterView(); + final TestFlutterView view = TestFlutterView( + view: backingView, + platformDispatcher: tester.binding.platformDispatcher, + ); + + view.render(expectedScene); + + expect(backingView.lastRenderedScene, isNotNull); + expect(backingView.lastRenderedScene, expectedScene); + }); + + testWidgets('updateSemantics is passed through to backing FlutterView', (WidgetTester tester) async { + final SemanticsUpdate expectedUpdate = SemanticsUpdateBuilder().build(); + final _FakeFlutterView backingView = _FakeFlutterView(); + final TestFlutterView view = TestFlutterView( + view: backingView, + platformDispatcher: tester.binding.platformDispatcher, + ); + + view.updateSemantics(expectedUpdate); + + expect(backingView.lastSemanticsUpdate, isNotNull); + expect(backingView.lastSemanticsUpdate, expectedUpdate); + }); + }); +} + + +Matcher matchesSnapshot(FlutterViewSnapshot expected) => _FlutterViewSnapshotMatcher(expected); + +class _FlutterViewSnapshotMatcher extends Matcher { + _FlutterViewSnapshotMatcher(this.expected); + + final FlutterViewSnapshot expected; + + @override + Description describe(Description description) { + description.add('snapshot of a FlutterView matches'); + return description; + } + + @override + Description describeMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) { + assert(item is FlutterViewSnapshot, 'Can only match against snapshots of FlutterView.'); + final FlutterViewSnapshot actual = item as FlutterViewSnapshot; + + if (actual.devicePixelRatio != expected.devicePixelRatio) { + mismatchDescription.add('actual.devicePixelRatio (${actual.devicePixelRatio}) did not match expected.devicePixelRatio (${expected.devicePixelRatio})'); + } + if (!actual.displayFeatures.equals(expected.displayFeatures)) { + mismatchDescription.add('actual.displayFeatures did not match expected.devicePixelRatio'); + mismatchDescription.addAll('Actual: [', ',', ']', actual.displayFeatures); + mismatchDescription.addAll('Expected: [', ',', ']', expected.displayFeatures); + } + if (actual.gestureSettings != expected.gestureSettings) { + mismatchDescription.add('actual.gestureSettings (${actual.gestureSettings}) did not match expected.gestureSettings (${expected.gestureSettings})'); + } + + final Matcher paddingMatcher = matchesViewPadding(expected.padding); + if (!paddingMatcher.matches(actual.padding, matchState)) { + mismatchDescription.add('actual.padding (${actual.padding}) did not match expected.padding (${expected.padding})'); + paddingMatcher.describeMismatch(actual.padding, mismatchDescription, matchState, verbose); + } + + if (actual.physicalGeometry != expected.physicalGeometry) { + mismatchDescription.add('actual.physicalGeometry (${actual.physicalGeometry}) did not match expected.physicalGeometry (${expected.physicalGeometry})'); + } + if (actual.physicalSize != expected.physicalSize) { + mismatchDescription.add('actual.physicalSize (${actual.physicalSize}) did not match expected.physicalSize (${expected.physicalSize})'); + } + + final Matcher systemGestureInsetsMatcher = matchesViewPadding(expected.systemGestureInsets); + if (!systemGestureInsetsMatcher.matches(actual.systemGestureInsets, matchState)) { + mismatchDescription.add('actual.systemGestureInsets (${actual.systemGestureInsets}) did not match expected.systemGestureInsets (${expected.systemGestureInsets})'); + systemGestureInsetsMatcher.describeMismatch(actual.systemGestureInsets, mismatchDescription, matchState, verbose); + } + + if (actual.viewId != expected.viewId) { + mismatchDescription.add('actual.viewId (${actual.viewId}) did not match expected.viewId (${expected.viewId})'); + } + + final Matcher viewInsetsMatcher = matchesViewPadding(expected.viewInsets); + if (!viewInsetsMatcher.matches(actual.viewInsets, matchState)) { + mismatchDescription.add('actual.viewInsets (${actual.viewInsets}) did not match expected.viewInsets (${expected.viewInsets})'); + viewInsetsMatcher.describeMismatch(actual.viewInsets, mismatchDescription, matchState, verbose); + } + + final Matcher viewPaddingMatcher = matchesViewPadding(expected.viewPadding); + if (!viewPaddingMatcher.matches(actual.viewPadding, matchState)) { + mismatchDescription.add('actual.viewPadding (${actual.viewPadding}) did not match expected.devicePixelRatio (${expected.viewPadding})'); + viewPaddingMatcher.describeMismatch(actual.viewPadding, mismatchDescription, matchState, verbose); + } + + return mismatchDescription; + } + + @override + bool matches(dynamic item, Map matchState) { + assert(item is FlutterViewSnapshot, 'Can only match against snapshots of FlutterView.'); + final FlutterViewSnapshot actual = item as FlutterViewSnapshot; + + return actual.devicePixelRatio == expected.devicePixelRatio && + actual.displayFeatures.equals(expected.displayFeatures) && + actual.gestureSettings == expected.gestureSettings && + matchesViewPadding(expected.padding).matches(actual.padding, matchState) && + actual.physicalGeometry == expected.physicalGeometry && + actual.physicalSize == expected.physicalSize && + matchesViewPadding(expected.systemGestureInsets).matches(actual.padding, matchState) && + actual.viewId == expected.viewId && + matchesViewPadding(expected.viewInsets).matches(actual.viewInsets, matchState) && + matchesViewPadding(expected.viewPadding).matches(actual.viewPadding, matchState); + } +} + +class FlutterViewSnapshot { + FlutterViewSnapshot(FlutterView view) : + devicePixelRatio = view.devicePixelRatio, + displayFeatures = [...view.displayFeatures], + gestureSettings = view.gestureSettings, + padding = view.padding, + physicalGeometry = view.physicalGeometry, + physicalSize = view.physicalSize, + systemGestureInsets = view.systemGestureInsets, + viewId = view.viewId, + viewInsets = view.viewInsets, + viewPadding = view.viewPadding; + + final double devicePixelRatio; + final List displayFeatures; + final GestureSettings gestureSettings; + final ViewPadding padding; + final Rect physicalGeometry; + final Size physicalSize; + final ViewPadding systemGestureInsets; + final Object viewId; + final ViewPadding viewInsets; + final ViewPadding viewPadding; +} + +class _FakeFlutterView implements FlutterView { + SemanticsUpdate? lastSemanticsUpdate; + Scene? lastRenderedScene; + + @override + void updateSemantics(SemanticsUpdate update) { + lastSemanticsUpdate = update; + } + + @override + void render(Scene scene) { + lastRenderedScene = scene; + } + + @override + dynamic noSuchMethod(Invocation invocation) { + return null; + } +} diff --git a/packages/flutter_test/test/window_test.dart b/packages/flutter_test/test/window_test.dart index 4385868f3b..bb7380877b 100644 --- a/packages/flutter_test/test/window_test.dart +++ b/packages/flutter_test/test/window_test.dart @@ -3,13 +3,19 @@ // found in the LICENSE file. import 'dart:ui' as ui show window; -import 'dart:ui' show AccessibilityFeatures, Brightness, Locale, PlatformDispatcher, SemanticsUpdate, SingletonFlutterWindow, Size, ViewPadding; +import 'dart:ui'; -import 'package:flutter/semantics.dart' show SemanticsUpdateBuilder; import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver; import 'package:flutter_test/flutter_test.dart'; +import 'utils/fake_and_mock_utils.dart'; + void main() { + tearDown(() { + final TestWindow window = WidgetsBinding.instance.window as TestWindow; + window.clearAllTestValues(); + }); + test('TestWindow can handle new methods without breaking', () { final dynamic testWindow = TestWindow(window: ui.window); // ignore: avoid_dynamic_calls @@ -17,7 +23,7 @@ void main() { }); testWidgets('TestWindow can fake device pixel ratio', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.devicePixelRatio, fakeValue: 2.5, @@ -31,7 +37,7 @@ void main() { }); testWidgets('TestWindow can fake physical size', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.physicalSize, fakeValue: const Size(50, 50), @@ -45,7 +51,7 @@ void main() { }); testWidgets('TestWindow can fake view insets', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.viewInsets, fakeValue: const FakeViewPadding(), @@ -55,11 +61,12 @@ void main() { propertyFaker: (TestWidgetsFlutterBinding binding, ViewPadding fakeValue) { binding.window.viewInsetsTestValue = fakeValue; }, + matcher: matchesViewPadding, ); }); testWidgets('TestWindow can fake padding', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.padding, fakeValue: const FakeViewPadding(), @@ -69,11 +76,12 @@ void main() { propertyFaker: (TestWidgetsFlutterBinding binding, ViewPadding fakeValue) { binding.window.paddingTestValue = fakeValue; }, + matcher: matchesViewPadding ); }); testWidgets('TestWindow can fake locale', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.locale, fakeValue: const Locale('fake_language_code'), @@ -87,7 +95,7 @@ void main() { }); testWidgets('TestWindow can fake locales', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty>( + verifyPropertyFaked>( tester: tester, realValue: ui.window.locales, fakeValue: [const Locale('fake_language_code')], @@ -101,7 +109,7 @@ void main() { }); testWidgets('TestWindow can fake text scale factor', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.textScaleFactor, fakeValue: 2.5, @@ -115,7 +123,7 @@ void main() { }); testWidgets('TestWindow can fake clock format', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.alwaysUse24HourFormat, fakeValue: !ui.window.alwaysUse24HourFormat, @@ -129,7 +137,7 @@ void main() { }); testWidgets('TestWindow can fake brieflyShowPassword', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.brieflyShowPassword, fakeValue: !ui.window.brieflyShowPassword, @@ -141,7 +149,7 @@ void main() { }); testWidgets('TestWindow can fake default route name', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.defaultRouteName, fakeValue: 'fake_route', @@ -155,7 +163,7 @@ void main() { }); testWidgets('TestWindow can fake accessibility features', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: ui.window.accessibilityFeatures, fakeValue: const FakeAccessibilityFeatures(), @@ -169,7 +177,7 @@ void main() { }); testWidgets('TestWindow can fake platform brightness', (WidgetTester tester) async { - verifyThatTestWindowCanFakeProperty( + verifyPropertyFaked( tester: tester, realValue: Brightness.light, fakeValue: Brightness.dark, @@ -209,63 +217,27 @@ void main() { retrieveTestBinding(tester).window.localesTestValue = defaultLocales; }); -test('Window test', () { - final FakeSingletonWindow fakeWindow = FakeSingletonWindow(); - final TestWindow testWindow = TestWindow(window: fakeWindow); - final SemanticsUpdate update = SemanticsUpdateBuilder().build(); - testWindow.updateSemantics(update); - expect(fakeWindow.lastUpdate, update); + testWidgets('Updates to window also update tester.view', (WidgetTester tester) async { + tester.binding.window.devicePixelRatioTestValue = 7; + tester.binding.window.displayFeaturesTestValue = [const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 20, 300), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)]; + tester.binding.window.paddingTestValue = const FakeViewPadding(); + tester.binding.window.physicalSizeTestValue = const Size(505, 805); + tester.binding.window.systemGestureInsetsTestValue = const FakeViewPadding(); + tester.binding.window.viewInsetsTestValue = const FakeViewPadding(); + tester.binding.window.viewPaddingTestValue = const FakeViewPadding(); + tester.binding.window.gestureSettingsTestValue = const GestureSettings(physicalTouchSlop: 4, physicalDoubleTapSlop: 5); + + expect(tester.binding.window.devicePixelRatio, tester.view.devicePixelRatio); + expect(tester.binding.window.displayFeatures, tester.view.displayFeatures); + expect(tester.binding.window.padding, tester.view.padding); + expect(tester.binding.window.physicalSize, tester.view.physicalSize); + expect(tester.binding.window.systemGestureInsets, tester.view.systemGestureInsets); + expect(tester.binding.window.viewInsets, tester.view.viewInsets); + expect(tester.binding.window.viewPadding, tester.view.viewPadding); + expect(tester.binding.window.gestureSettings, tester.view.gestureSettings); }); } -void verifyThatTestWindowCanFakeProperty({ - required WidgetTester tester, - required WindowPropertyType? realValue, - required WindowPropertyType fakeValue, - required WindowPropertyType? Function() propertyRetriever, - required Function(TestWidgetsFlutterBinding, WindowPropertyType fakeValue) propertyFaker, -}) { - WindowPropertyType? propertyBeforeFaking; - WindowPropertyType? propertyAfterFaking; - - propertyBeforeFaking = propertyRetriever(); - - propertyFaker(retrieveTestBinding(tester), fakeValue); - - propertyAfterFaking = propertyRetriever(); - - expect(propertyBeforeFaking, realValue); - expect(propertyAfterFaking, fakeValue); -} - -TestWidgetsFlutterBinding retrieveTestBinding(WidgetTester tester) { - final WidgetsBinding binding = tester.binding; - assert(binding is TestWidgetsFlutterBinding); - final TestWidgetsFlutterBinding testBinding = binding as TestWidgetsFlutterBinding; - return testBinding; -} - -class FakeViewPadding implements ViewPadding { - const FakeViewPadding({ - this.left = 0.0, - this.top = 0.0, - this.right = 0.0, - this.bottom = 0.0, - }); - - @override - final double left; - - @override - final double top; - - @override - final double right; - - @override - final double bottom; -} - class TestObserver with WidgetsBindingObserver { List? locales; Locale? locale; @@ -275,15 +247,3 @@ class TestObserver with WidgetsBindingObserver { this.locales = locales; } } - -class FakeSingletonWindow extends Fake implements SingletonFlutterWindow { - SemanticsUpdate? lastUpdate; - - @override - PlatformDispatcher get platformDispatcher => PlatformDispatcher.instance; - - @override - void updateSemantics(SemanticsUpdate update) { - lastUpdate = update; - } -}