Fix scrollUntilVisible in WidgetTester (#159582)

Fixes #143921.

The single `moveBy` event may be consumed when `onTap` is present.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Flop 2025-01-29 17:30:42 +08:00 committed by GitHub
parent bcd4ecedea
commit b8de503ded
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 1 deletions

View File

@ -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<Element>? 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<void>(() 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));
});
}

View File

@ -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<bool>(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 {