@@ -177,6 +177,67 @@ void main() {
177177
178178 debugNetworkImageHttpClientProvider = null ;
179179 });
180+
181+ testWidgets ('user options appear in the correct rendering order and do not scroll down' , (tester) async {
182+ final List <User > users = [
183+ eg.user (userId: 1 , fullName: 'Aaditya' , avatarUrl: 'user1.png' ),
184+ eg.user (userId: 2 , fullName: 'Alya' , avatarUrl: 'user2.png' ),
185+ eg.user (userId: 3 , fullName: 'Aman' , avatarUrl: 'user3.png' ),
186+ eg.user (userId: 4 , fullName: 'Anders' , avatarUrl: 'user4.png' ),
187+ eg.user (userId: 5 , fullName: 'Anthony' , avatarUrl: 'user5.png' ),
188+ eg.user (userId: 6 , fullName: 'Apoorva' , avatarUrl: 'user6.png' ),
189+ eg.user (userId: 7 , fullName: 'Asif' , avatarUrl: 'user7.png' ),
190+ eg.user (userId: 8 , fullName: 'Asim' , avatarUrl: 'user8.png' )];
191+
192+ final composeInputFinder = await setupToComposeInput (tester, users: users);
193+ final store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
194+
195+ // Options are filtered correctly for query
196+ // TODO(#226): Remove this extra edit when this bug is fixed.
197+ await tester.enterText (composeInputFinder, 'hello @' );
198+ await tester.enterText (composeInputFinder, 'hello @A' );
199+ await tester.pump ();
200+ // Only first seven users render initially, 8th user has to be accessed by scrolling up
201+ final List <double > positions = [];
202+ for (int i = 0 ;i < 7 ;i++ ) {
203+ final user = users[i];
204+ checkUserShown (user, store, expected: true );
205+ positions.add (tester.getTopLeft (find.text (user.fullName)).dy);
206+ }
207+ for (int i = 7 ; i < users.length;i++ ) {
208+ final user = users[i];
209+ checkUserShown (user, store, expected: false );
210+ }
211+ final listViewFinder = find.byType (ListView );
212+
213+ final initialScrollOffset = tester.getTopLeft (listViewFinder).dy;
214+ await tester.drag (listViewFinder, const Offset (0 , - 50 ));
215+ await tester.pumpAndSettle ();
216+ final scrollOffsetAfterDragDown = tester.getTopLeft (listViewFinder).dy;
217+
218+ check (
219+ because: 'ListView should not scroll down because it is already at the bottom' ,
220+ scrollOffsetAfterDragDown
221+ ).equals (initialScrollOffset);
222+
223+ for (int i = 0 ;i < 6 ;i++ ) {
224+ check (
225+ because: """${users [i + 1 ]} should appear above
226+ ${users [i ]} because of reverse order""" ,
227+ positions[i]).isGreaterThan (positions[i+ 1 ]);
228+ }
229+
230+ await tester.drag (listViewFinder, const Offset (0 , 200 )); // Should be capped at prev position
231+ await tester.pump ();
232+
233+ checkUserShown (users.last, store, expected: true );
234+ checkUserShown (users.first, store, expected: false );
235+
236+ // 8th user should be above 7th user
237+ check (because: "8th user should be above 7th user" ,
238+ tester.getTopLeft (find.text (users.last.fullName)).dy).isLessThan (tester.getTopLeft (find.text (users[users.length - 2 ].fullName)).dy);
239+ debugNetworkImageHttpClientProvider = null ;
240+ });
180241 });
181242
182243 group ('emoji' , () {
@@ -247,6 +308,98 @@ void main() {
247308 debugNetworkImageHttpClientProvider = null ;
248309 });
249310
311+ testWidgets ('emoji options appear in the correct rendering order and do not scroll down' , (tester) async {
312+ final composeInputFinder = await setupToComposeInput (tester);
313+ final store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
314+
315+ store.setServerEmojiData (
316+ ServerEmojiData (
317+ codeToNames: {
318+ '1f4a4' : ['zzz' , 'sleepy' ], // Unicode emoji for "zzz"
319+ '1f52a' : ['biohazard' ],
320+ '1f92a' : ['zany_face' ],
321+ '1f993' : ['zebra' ],
322+ '0030-fe0f-20e3' : ['zero' ],
323+ '1f9d0' : ['zombie' ],
324+ }));
325+
326+ await store.handleEvent (
327+ RealmEmojiUpdateEvent (
328+ id: 1 ,
329+ realmEmoji: {
330+ '1' : eg.realmEmojiItem (emojiCode: '1' , emojiName: 'buzzing' )}));
331+
332+ const zulipOptionLabel = 'zulip' ;
333+ const zanyFaceOptionLabel = 'zany_face' ;
334+ const zebraOptionLabel = 'zebra' ;
335+ const zzzOptionLabel = 'zzz, sleepy' ;
336+ const unicodeGlyph = '💤' ;
337+ const zombieOptionLabel = 'zombie' ;
338+ const zeroOptionLabel = 'zero' ;
339+ const buzzingOptionLabel = 'buzzing' ;
340+ const biohazardOptionLabel = 'biohazard' ;
341+
342+ // Adjust the order so the best match appears last
343+ final emojiSequence = [
344+ zulipOptionLabel,
345+ zzzOptionLabel,
346+ unicodeGlyph,
347+ zanyFaceOptionLabel,
348+ zebraOptionLabel,
349+ zeroOptionLabel,
350+ zombieOptionLabel,
351+ buzzingOptionLabel,
352+ // biohazardOptionLabel, this won't be rendered in the list initally since it is the 7th option.
353+ ];
354+
355+ // Enter a query; options appear, of all three emoji types.
356+ // TODO(#226): Remove this extra edit when this bug is fixed.
357+ await tester.enterText (composeInputFinder, 'hi :' );
358+ await tester.enterText (composeInputFinder, 'hi :z' );
359+ await tester.pump ();
360+
361+ final positions = emojiSequence.map ((icon) {
362+ final finder = find.text (icon);
363+ check (because: "Each emoji option should be rendered" , finder).findsOne ();
364+ return tester.getTopLeft (finder).dy;
365+ }).toList ();
366+
367+ for (int i = 0 ; i < positions.length - 1 ; i++ ) {
368+ check (because: "${emojiSequence [i + 1 ]} should appear above ${emojiSequence [i ]} because of reverse order" ,
369+ positions[i]).isGreaterThan (positions[i + 1 ]);
370+ }
371+
372+ final listViewFinder = find.byType (ListView );
373+
374+ final initialScrollOffset = tester.getTopLeft (listViewFinder).dy;
375+ await tester.drag (listViewFinder, const Offset (0 , - 50 ));
376+ await tester.pumpAndSettle ();
377+ final scrollOffsetAfterDragDown = tester.getTopLeft (listViewFinder).dy;
378+
379+ check (
380+ because: "ListView should not scroll down because it is already at the bottom" ,
381+ scrollOffsetAfterDragDown
382+ ).equals (initialScrollOffset);
383+
384+ final biohazardFinder = find.text (biohazardOptionLabel);
385+ check (
386+ because: "The biohazard emoji should not be visible before scrolling up"
387+ ,biohazardFinder
388+ ).findsNothing ();
389+
390+ // Scroll up
391+ await tester.drag (listViewFinder, const Offset (0 , 50 ));
392+ await tester.pump ();
393+
394+ check (because: "The biohazard emoji should be visible after scrolling up" ,biohazardFinder).findsOne ();
395+
396+ final firstEmojiPositionAfterScrollUp = tester.getTopLeft (find.text (emojiSequence[0 ])).dy;
397+ check (because: "Scrolling up should reveal other emoji matches" ,firstEmojiPositionAfterScrollUp).isGreaterOrEqual (positions[0 ]);
398+
399+ debugNetworkImageHttpClientProvider = null ;
400+
401+ });
402+
250403 testWidgets ('text emoji means just show text' , (tester) async {
251404 final composeInputFinder = await setupToComposeInput (tester);
252405 final store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
0 commit comments