Files
m8chat-app2/test/history_pagination_test.dart
help4bis e4aa5277d7 feat(chat): load older messages on scroll-to-top (history pagination) (v1.11.0+15)
Repository keeps one live Timeline per room and re-yields on its
onUpdate callback (fires for both live events AND requestHistory),
not just onSync which never fires for pagination. loadMoreMessages
now paginates that same instance; added canLoadMore.

Timeline widget requests older history when scrolled within 400px of
the oldest end, guarded against re-entry, with a top loading spinner.

Verified: widget test drives a drag-to-top and asserts loadMoreMessages
fires; live smoke test confirms the onUpdate delivery path (reused by
history) still shows new messages after the refactor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:44:38 +10:00

105 lines
3.2 KiB
Dart

// Version: 1.0.0 | Created: 2026-07-04
// Verifies the chat timeline requests older history when scrolled near the
// top (oldest end) of the reverse list. Driven through the real widget tree
// with a fake repository, since Playwright cannot scroll the CanvasKit surface.
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/data/chat_repository.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';
class _FakeChatRepository extends ChatRepository {
_FakeChatRepository() : super(client: Client('test-client'));
int loadMoreCalls = 0;
bool _hasMore = true;
@override
bool canLoadMore(String roomId) => _hasMore;
@override
Future<void> loadMoreMessages(String roomId) async {
loadMoreCalls++;
_hasMore = false; // one page then exhausted
}
}
void main() {
testWidgets('scrolling to the top requests older history once', (
tester,
) async {
const roomId = '!paginate:server';
final base = DateTime(2026, 7, 4, 8);
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,
),
];
final repo = _FakeChatRepository();
await tester.pumpWidget(
ProviderScope(
overrides: [
matrixClientProvider.overrideWithValue(Client('test-client')),
chatRepositoryProvider.overrideWithValue(repo),
chatTimelineProvider(
roomId,
).overrideWith((ref) => Stream.value(messages)),
],
child: const MaterialApp(home: ChatScreen(roomId: roomId)),
),
);
await tester.pumpAndSettle();
expect(repo.loadMoreCalls, 0);
// Confirm the override is actually in effect.
final container = ProviderScope.containerOf(
tester.element(find.byType(ChatScreen)),
);
expect(identical(container.read(chatRepositoryProvider), repo), isTrue);
double offset() => tester
.state<ScrollableState>(find.byType(Scrollable).first)
.position
.pixels;
double maxExtent() => tester
.state<ScrollableState>(find.byType(Scrollable).first)
.position
.maxScrollExtent;
// Scroll up toward the oldest end (reverse list: drag content down).
await tester.drag(find.byType(ListView), const Offset(0, 3000));
await tester.pumpAndSettle();
expect(
offset(),
greaterThan(maxExtent() - 400),
reason:
'drag should have reached the oldest end (offset=${offset()}, '
'max=${maxExtent()})',
);
expect(
repo.loadMoreCalls,
greaterThan(0),
reason: 'reaching the top should request older history',
);
});
}