diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index 7b88e08f890..09b70a70aae 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -677,6 +677,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker SemanticsHandle ensureSemantics() { return binding.pipelineOwner.ensureSemantics(); } + + /// Given a widget `W` specified by [finder] and a [Scrollable] widget `S` in + /// its ancestry tree, this scrolls `S` so as to make `W` visible. + /// + /// Shorthand for `Scrollable.ensureVisible(tester.element(finder))` + Future ensureVisible(Finder finder) => Scrollable.ensureVisible(element(finder)); } typedef _TickerDisposeCallback = void Function(_TestTicker ticker); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index da81a086566..07f248d68a0 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -616,6 +616,30 @@ void main() { semanticsHandle.dispose(); }); }); + + group('ensureVisible', () { + testWidgets('scrolls to make widget visible', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ListView.builder( + itemCount: 20, + shrinkWrap: true, + itemBuilder: (BuildContext context, int i) => ListTile(title: Text('Item $i')), + ), + ), + ), + ); + + // Make sure widget isn't on screen + expect(find.text('Item 15', skipOffstage: true), findsNothing); + + await tester.ensureVisible(find.text('Item 15', skipOffstage: false)); + await tester.pumpAndSettle(); + + expect(find.text('Item 15', skipOffstage: true), findsOneWidget); + }); + }); } class FakeMatcher extends AsyncMatcher {