@@ -145,6 +145,7 @@ void main() {
145145 }
146146
147147 late MessageListScrollController controller;
148+ late MessageListScrollPosition position;
148149
149150 Future <void > prepare (WidgetTester tester, {
150151 bool reuseController = false ,
@@ -158,6 +159,7 @@ void main() {
158159 child: buildList (controller: controller,
159160 topHeight: topHeight, bottomHeight: bottomHeight)));
160161 await tester.pump ();
162+ position = controller.position;
161163 }
162164
163165 // The `skipOffstage: false` produces more informative output
@@ -310,6 +312,71 @@ void main() {
310312 // … and check the scroll position is preserved, not reset to initial.
311313 check (tester.getRect (findTop)).bottom.equals (400 );
312314 });
315+
316+ group ('scrollToEnd' , () {
317+ testWidgets ('short -> slow' , (tester) async {
318+ await prepare (tester, topHeight: 300 , bottomHeight: 600 );
319+ await tester.drag (findBottom, Offset (0 , 300 ));
320+ await tester.pump ();
321+ check (position.extentAfter).equals (300 );
322+
323+ // Start scrolling to end, from just a short distance up.
324+ position.scrollToEnd ();
325+ await tester.pump ();
326+ check (position.extentAfter).equals (300 );
327+ check (position.activity).isA <DrivenScrollActivity >();
328+
329+ // The scrolling moves at a stately pace; …
330+ await tester.pump (Duration (milliseconds: 100 ));
331+ check (position.extentAfter).equals (200 );
332+
333+ await tester.pump (Duration (milliseconds: 100 ));
334+ check (position.extentAfter).equals (100 );
335+
336+ // … then upon reaching the end, …
337+ await tester.pump (Duration (milliseconds: 100 ));
338+ check (position.extentAfter).equals (0 );
339+
340+ // … goes idle on the next frame, …
341+ await tester.pump (Duration (milliseconds: 1 ));
342+ check (position.activity).isA <IdleScrollActivity >();
343+ // … without moving any farther.
344+ check (position.extentAfter).equals (0 );
345+ });
346+
347+ testWidgets ('long -> bounded speed' , (tester) async {
348+ const referenceSpeed = 8000.0 ;
349+ const seconds = 10 ;
350+ const distance = seconds * referenceSpeed;
351+ await prepare (tester, topHeight: distance + 1000 , bottomHeight: 300 );
352+ await tester.drag (findBottom, Offset (0 , distance));
353+ await tester.pump ();
354+ check (position.extentAfter).equals (distance);
355+
356+ // Start scrolling to end.
357+ position.scrollToEnd ();
358+ await tester.pump ();
359+ check (position.activity).isA <DrivenScrollActivity >();
360+
361+ // Let it scroll, plotting the trajectory.
362+ final log = < double > [];
363+ for (int i = 0 ; i < seconds; i++ ) {
364+ log.add (position.extentAfter);
365+ await tester.pump (const Duration (seconds: 1 ));
366+ }
367+ log.add (position.extentAfter);
368+ check (log).deepEquals (List .generate (seconds + 1 ,
369+ (i) => distance - referenceSpeed * i));
370+
371+ // Having reached the end, …
372+ check (position.extentAfter).equals (0 );
373+ // … it goes idle on the next frame, …
374+ await tester.pump (Duration (milliseconds: 1 ));
375+ check (position.activity).isA <IdleScrollActivity >();
376+ // … without moving any farther.
377+ check (position.extentAfter).equals (0 );
378+ });
379+ });
313380 });
314381}
315382
0 commit comments