@@ -908,4 +908,132 @@ void main() {
908908 },
909909 );
910910 });
911+
912+ group ('reloadChannel' , () {
913+ final mockChannel = MockChannel ();
914+ tearDownAll (mockChannel.dispose);
915+
916+ // Mutable backing so the mocked `truncate` and `query` can model the
917+ // real `ChannelClientState.messages` lifecycle: truncate clears it,
918+ // a `query` response is merged on top of whatever's there. With the
919+ // truncate-before-query fix the merge target is empty; without it,
920+ // the previously loaded window leaks past the reload (the original bug).
921+ var stateMessages = < Message > [];
922+
923+ List <Message > _generateMessages (int count, {required String prefix}) =>
924+ List .generate (
925+ count,
926+ (i) => Message (
927+ id: '$prefix -$i ' ,
928+ createdAt: DateTime (2024 ).add (Duration (seconds: i)),
929+ user: User (id: 'otherUserId' ),
930+ ),
931+ );
932+
933+ setUp (() {
934+ when (() => mockChannel.cid).thenReturn ('test:channel' );
935+ when (() => mockChannel.state.messages).thenAnswer ((_) => stateMessages);
936+ when (() => mockChannel.state.isUpToDate).thenReturn (true );
937+ when (() => mockChannel.state.unreadCount).thenReturn (0 );
938+ when (() => mockChannel.state.truncate ()).thenAnswer ((_) {
939+ stateMessages = [];
940+ });
941+ when (
942+ () => mockChannel.query (
943+ preferOffline: any (named: 'preferOffline' ),
944+ messagesPagination: any (named: 'messagesPagination' ),
945+ ),
946+ ).thenAnswer ((_) async {
947+ // Model `Channel.query` merging the new page onto the existing
948+ // window — dedupe by id is unnecessary here because the test
949+ // primes disjoint old/new sets.
950+ final newMessages = _generateMessages (30 , prefix: 'new' );
951+ stateMessages = [...stateMessages, ...newMessages];
952+ return ChannelState (messages: newMessages);
953+ });
954+ });
955+
956+ tearDown (() {
957+ stateMessages = < Message > [];
958+ reset (mockChannel);
959+ });
960+
961+ Future <StreamChannelState > _pumpStreamChannel (WidgetTester tester) async {
962+ StreamChannelState ? channelState;
963+ await tester.pumpWidget (
964+ MaterialApp (
965+ home: Scaffold (
966+ body: StreamChannel (
967+ channel: mockChannel,
968+ child: Builder (
969+ builder: (context) {
970+ channelState = StreamChannel .of (context);
971+ return const Text ('Channel Content' );
972+ },
973+ ),
974+ ),
975+ ),
976+ ),
977+ );
978+ await tester.pumpAndSettle ();
979+ return channelState! ;
980+ }
981+
982+ testWidgets (
983+ 'truncates the existing window before querying the latest messages' ,
984+ (tester) async {
985+ final streamChannel = await _pumpStreamChannel (tester);
986+
987+ await streamChannel.reloadChannel ();
988+
989+ verifyInOrder ([
990+ () => mockChannel.state.truncate (),
991+ () => mockChannel.query (
992+ preferOffline: any (named: 'preferOffline' ),
993+ messagesPagination: any (named: 'messagesPagination' ),
994+ ),
995+ ]);
996+ },
997+ );
998+
999+ testWidgets (
1000+ 'queries with no around-anchor (loads the latest page)' ,
1001+ (tester) async {
1002+ final streamChannel = await _pumpStreamChannel (tester);
1003+
1004+ await streamChannel.reloadChannel ();
1005+
1006+ final captured = verify (
1007+ () => mockChannel.query (
1008+ preferOffline: any (named: 'preferOffline' ),
1009+ messagesPagination: captureAny (named: 'messagesPagination' ),
1010+ ),
1011+ ).captured.single as PaginationParams ;
1012+
1013+ expect (captured.idAround, isNull);
1014+ expect (captured.createdAtAround, isNull);
1015+ },
1016+ );
1017+
1018+ testWidgets (
1019+ 'state contains only the latest page after reloading (drops the '
1020+ 'previously loaded window)' ,
1021+ (tester) async {
1022+ // Seed the around-Y window that a prior `loadChannelAtMessage`
1023+ // would have produced.
1024+ stateMessages = _generateMessages (30 , prefix: 'old' );
1025+
1026+ final streamChannel = await _pumpStreamChannel (tester);
1027+ await streamChannel.reloadChannel ();
1028+
1029+ // Without the truncate the merge would land us on 60 messages
1030+ // (old 30 + new 30). The fix yields just the latest page.
1031+ expect (stateMessages, hasLength (30 ));
1032+ expect (
1033+ stateMessages.map ((m) => m.id),
1034+ everyElement (startsWith ('new-' )),
1035+ );
1036+ },
1037+ );
1038+ });
9111039}
0 commit comments