// Version: 1.0.0 | Created: 2026-07-04 // Deterministic test for the chat timeline's scroll-to-latest button. // Playwright cannot drive scrolling on the Flutter CanvasKit surface, so the // jump-to-latest behaviour is verified here against the real widget tree. import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:matrix/matrix.dart'; import 'package:m8chat_app/core/network/matrix_client.dart'; import 'package:m8chat_app/features/chat/domain/message_model.dart'; import 'package:m8chat_app/features/chat/presentation/chat_controller.dart'; import 'package:m8chat_app/features/chat/presentation/chat_screen.dart'; void main() { testWidgets( 'jump-to-latest button appears when scrolled up and returns to bottom', (tester) async { const roomId = '!scrolltest:server'; final base = DateTime(2026, 7, 4, 8); // Index 0 is the newest message (the timeline stream is newest-first). final messages = [ for (var i = 40; i >= 1; i--) MessageModel( eventId: '\$e$i', roomId: roomId, senderId: '@me:server', senderDisplayName: 'Me', timestamp: base.add(Duration(seconds: i)), type: MessageType.text, body: 'Message $i', isMine: true, ), ]; await tester.pumpWidget( ProviderScope( overrides: [ matrixClientProvider.overrideWithValue(Client('test-client')), chatTimelineProvider( roomId, ).overrideWith((ref) => Stream.value(messages)), ], child: const MaterialApp(home: ChatScreen(roomId: roomId)), ), ); await tester.pumpAndSettle(); final listView = find.byType(ListView); expect(listView, findsOneWidget); double offset() => tester.state(find.byType(Scrollable).first) .position .pixels; // Starts pinned to the newest message (offset 0 in a reverse list). expect(offset(), 0); // Scroll up toward older messages (reverse list: drag content down). await tester.drag(listView, const Offset(0, 900)); await tester.pumpAndSettle(); expect( offset(), greaterThan(400), reason: 'should have scrolled well past the button threshold', ); // The jump-to-latest button is now shown; tap it. final jumpButton = find.byIcon(Icons.arrow_downward); expect(jumpButton, findsOneWidget); await tester.tap(jumpButton); await tester.pumpAndSettle(); // Back at the newest message. expect(offset(), 0); }, ); }