Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/widgets/autocomplete.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ abstract class AutocompleteField<QueryT extends AutocompleteQuery, ResultT exten

class _AutocompleteFieldState<QueryT extends AutocompleteQuery, ResultT extends AutocompleteResult> extends State<AutocompleteField<QueryT, ResultT>> with PerAccountStoreAwareStateMixin<AutocompleteField<QueryT, ResultT>> {
AutocompleteView<QueryT, ResultT>? _viewModel;
final ScrollController _scrollController = ScrollController();

void _initViewModel(QueryT query) {
_viewModel = widget.initViewModel(context, query)
Expand All @@ -63,6 +64,10 @@ class _AutocompleteFieldState<QueryT extends AutocompleteQuery, ResultT extends
} else {
assert(_viewModel!.acceptsQuery(newQuery));
_viewModel!.query = newQuery;
// Reset scroll when query content changes
if (_scrollController.hasClients) {
_scrollController.jumpTo(0);
}
}
}
}
Expand Down Expand Up @@ -96,6 +101,7 @@ class _AutocompleteFieldState<QueryT extends AutocompleteQuery, ResultT extends
void dispose() {
widget.controller.removeListener(_handleControllerChange);
_viewModel?.dispose(); // removes our listener
_scrollController.dispose();
super.dispose();
}

Expand Down Expand Up @@ -138,6 +144,7 @@ class _AutocompleteFieldState<QueryT extends AutocompleteQuery, ResultT extends
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300), // TODO not hard-coded
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: _resultsToDisplay.length,
Expand Down
67 changes: 67 additions & 0 deletions test/widgets/autocomplete_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,73 @@ void main() {

debugNetworkImageHttpClientProvider = null;
});

testWidgets('scroll position resets when query changes', (tester) async {
final composeInputFinder = await setupToComposeInput(tester);
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
// Add many emoji starting with 's' to ensure the list is scrollable.
// The autocomplete box has maxHeight of 300, so we need enough items.
store.setServerEmojiData(ServerEmojiData(codeToNames: {
'1f600': ['sa_first_item'],
'1f601': ['sb_second'],
'1f602': ['sc_third'],
'1f603': ['sd_fourth'],
'1f604': ['se_fifth'],
'1f605': ['sf_sixth'],
'1f606': ['sg_seventh'],
'1f607': ['sh_eighth'],
'1f608': ['si_ninth'],
'1f609': ['sj_tenth'],
'1f60a': ['sk_eleventh'],
'1f60b': ['sl_twelfth'],
'1f60c': ['sm_thirteenth'],
'1f60d': ['sn_fourteenth'],
'1f60e': ['so_fifteenth'],
'1f60f': ['sp_sixteenth'],
'1f610': ['sq_seventeenth'],
'1f611': ['sr_eighteenth'],
'1f612': ['ss_nineteenth'],
'1f613': ['sz_last_item'],
}));

// Enter an emoji query to show many options.
// TODO(#226): Remove this extra edit when this bug is fixed.
await tester.enterText(composeInputFinder, 'hi :');
await tester.enterText(composeInputFinder, 'hi :s');
await tester.pump();
await tester.pump();

// Verify the first item is visible and the last is not (needs scrolling).
check(find.text('sa_first_item')).findsOne();
check(find.text('sz_last_item')).findsNothing();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "last is not" part isn't implemented, it looks like.

Added check that the last item is not visible initially


// Scroll until the last item is visible.
// We need to scroll to see items beyond the maxHeight constraint.
final scrollable = find.descendant(
of: find.byType(ListView),
matching: find.byType(Scrollable)).first;
await tester.dragUntilVisible(
find.text('sz_last_item'),
scrollable,
const Offset(0, -50),
);
await tester.pump();

// The last item should now be visible.
check(find.text('sz_last_item')).findsOne();
// The first item should no longer be visible.
check(find.text('sa_first_item')).findsNothing();

Copy link
Author

@AshutoshKhadse23 AshutoshKhadse23 Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check that the first item is not visible here.

Added check that the first item is not visible after scrolling to the bottom

// Change the query; scroll should reset to top.
await tester.enterText(composeInputFinder, 'hi :sa');
await tester.pump();
await tester.pump();

// After query change, the first matching item should be visible at top.
check(find.text('sa_first_item')).findsOne();

debugNetworkImageHttpClientProvider = null;
});
});

group('TopicAutocomplete', () {
Expand Down
Loading