diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index bee7226ab3..1a7bc932e4 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -2312,6 +2312,10 @@ abstract class WidgetController { /// If `scrollable` is `null`, a [Finder] that looks for a [Scrollable] is /// used instead. /// + /// If `continuous` is `true`, the gesture will be reused to simulate the effect + /// of actual finger scrolling, which is useful when used alongside listeners + /// like [GestureDetector.onTap]. The default is `false`. + /// /// Throws a [StateError] if `finder` is not found after `maxScrolls` scrolls. /// /// This is different from [ensureVisible] in that this allows looking for @@ -2328,6 +2332,7 @@ abstract class WidgetController { finders.FinderBase? scrollable, int maxScrolls = 50, Duration duration = const Duration(milliseconds: 50), + bool continuous = false, }) { assert(maxScrolls > 0); scrollable ??= finders.find.byType(Scrollable); @@ -2349,6 +2354,7 @@ abstract class WidgetController { moveStep, maxIteration: maxScrolls, duration: duration, + continuous: continuous, ); }); } @@ -2370,13 +2376,21 @@ abstract class WidgetController { Offset moveStep, { int maxIteration = 50, Duration duration = const Duration(milliseconds: 50), + bool continuous = false, }) { return TestAsyncUtils.guard(() async { + TestGesture? gesture; while (maxIteration > 0 && finder.evaluate().isEmpty) { - await drag(view, moveStep); + if (continuous) { + gesture ??= await startGesture(getCenter(view, warnIfMissed: true)); + await gesture.moveBy(moveStep); + } else { + await drag(view, moveStep); + } await pump(duration); maxIteration -= 1; } + await gesture?.up(); await Scrollable.ensureVisible(element(finder)); }); } diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index 0b926c83f5..44f6c56ffe 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -612,6 +612,44 @@ void main() { }); }); + // Regression test for https://github.com/flutter/flutter/issues/143921. + testWidgets('WidgetTester.scrollUntilVisible should work together with onTap', ( + WidgetTester tester, + ) async { + const int itemCount = 20; + + Widget buildFrame(bool hasOnTap) { + return MaterialApp( + home: Scaffold( + body: ListView.builder( + key: ValueKey(hasOnTap), // Trigger a rebuild. + itemCount: itemCount, + itemBuilder: (BuildContext context, int index) { + return ListTile(onTap: hasOnTap ? () {} : null, title: Text('$index')); + }, + ), + ), + ); + } + + final Finder target = find.text('${itemCount - 1}'); + final Finder scrollable = find.byType(Scrollable); + + // Scroll without onTap. + await tester.pumpWidget(buildFrame(false)); + await tester.pumpAndSettle(); + expect(target, findsNothing); + await tester.scrollUntilVisible(target, 20, scrollable: scrollable, continuous: true); + expect(target, findsOneWidget); + + // Scroll with onTap. + await tester.pumpWidget(buildFrame(true)); + await tester.pumpAndSettle(); + expect(target, findsNothing); + await tester.scrollUntilVisible(target, 20, scrollable: scrollable, continuous: true); + expect(target, findsOneWidget); + }); + testWidgets('platformDispatcher exposes the platformDispatcher from binding', ( WidgetTester tester, ) async {