forked from eatvio/eatvio_app
refactor(chat): message dedup
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/core/models/pagination_model.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
@@ -43,6 +44,39 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
ChatRoom buildChatRoom(String chatId, {int unread = 0}) {
|
||||
return ChatRoom(
|
||||
id: chatId,
|
||||
lastMessage: null,
|
||||
users: const Users(
|
||||
fromChatUser: ChatUser(id: 1, name: 'User 1', profilePhotoPath: null, profilePhotoUrl: ''),
|
||||
toChatUser: ChatUser(id: 2, name: 'User 2', profilePhotoPath: null, profilePhotoUrl: ''),
|
||||
),
|
||||
unreadMessagesCount: unread,
|
||||
chosenPlan: null,
|
||||
);
|
||||
}
|
||||
|
||||
ChannelReadEvent socketEvent({required int id, required int authorId, required String roomId}) {
|
||||
return _FakeChannelReadEvent(jsonEncode(<String, dynamic>{
|
||||
'id': id,
|
||||
'data': 'socket message',
|
||||
'media_data': null,
|
||||
'author_id': authorId,
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
'read_by_to_user': false,
|
||||
'is_author_message_auth_user': false,
|
||||
'room_id': roomId,
|
||||
}));
|
||||
}
|
||||
|
||||
Future<StreamController<ChannelReadEvent>> connectToSocketStream() async {
|
||||
final controller = StreamController<ChannelReadEvent>.broadcast();
|
||||
when(mockWsManager.newMessagesStream).thenAnswer((_) => controller.stream);
|
||||
await chatsCubit.connectToWebSocket();
|
||||
return controller;
|
||||
}
|
||||
|
||||
test('начальное состояние - initial', () {
|
||||
expect(chatsCubit.state, isA<ChatsState>());
|
||||
expect(chatsCubit.state.state, BaseState.initial);
|
||||
@@ -180,6 +214,76 @@ void main() {
|
||||
verify(mockWsManager.newMessagesStream).called(1);
|
||||
});
|
||||
|
||||
test('_handleNewMessageViaSocket - собственное сообщение обновляет lastMessage без непрочитанного', () async {
|
||||
const chatId = 'chat-id';
|
||||
chatsCubit.emit(ChatsState(state: BaseState.success, chatRooms: [buildChatRoom(chatId)]));
|
||||
when(mockWsManager.userId).thenReturn(42);
|
||||
|
||||
final controller = await connectToSocketStream();
|
||||
controller.add(socketEvent(id: 100, authorId: 42, roomId: chatId));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
final room = chatsCubit.state.chatRooms.firstWhere((r) => r.id == chatId);
|
||||
expect(room.lastMessage?.id, 100);
|
||||
expect(room.unreadMessagesCount, 0);
|
||||
expect(chatsCubit.state.chatsWithUnreadMessagesCount, 0);
|
||||
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('_handleNewMessageViaSocket - чужое сообщение увеличивает непрочитанное', () async {
|
||||
const chatId = 'chat-id';
|
||||
chatsCubit.emit(ChatsState(state: BaseState.success, chatRooms: [buildChatRoom(chatId)]));
|
||||
when(mockWsManager.userId).thenReturn(42);
|
||||
|
||||
final controller = await connectToSocketStream();
|
||||
controller.add(socketEvent(id: 200, authorId: 7, roomId: chatId));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
final room = chatsCubit.state.chatRooms.firstWhere((r) => r.id == chatId);
|
||||
expect(room.lastMessage?.id, 200);
|
||||
expect(room.unreadMessagesCount, 1);
|
||||
expect(chatsCubit.state.chatsWithUnreadMessagesCount, 1);
|
||||
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('_handleNewMessageViaSocket - повторная доставка того же id не двоит непрочитанное', () async {
|
||||
const chatId = 'chat-id';
|
||||
chatsCubit.emit(ChatsState(state: BaseState.success, chatRooms: [buildChatRoom(chatId)]));
|
||||
when(mockWsManager.userId).thenReturn(42);
|
||||
|
||||
final controller = await connectToSocketStream();
|
||||
controller.add(socketEvent(id: 300, authorId: 7, roomId: chatId));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
controller.add(socketEvent(id: 300, authorId: 7, roomId: chatId));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
final room = chatsCubit.state.chatRooms.firstWhere((r) => r.id == chatId);
|
||||
expect(room.unreadMessagesCount, 1);
|
||||
expect(chatsCubit.state.chatsWithUnreadMessagesCount, 1);
|
||||
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('_handleNewMessageViaSocket - чужое сообщение в непрочитанном чате не поднимает счётчик чатов', () async {
|
||||
const chatId = 'chat-id';
|
||||
chatsCubit.emit(
|
||||
ChatsState(state: BaseState.success, chatRooms: [buildChatRoom(chatId, unread: 2)], chatsWithUnreadMessagesCount: 1),
|
||||
);
|
||||
when(mockWsManager.userId).thenReturn(42);
|
||||
|
||||
final controller = await connectToSocketStream();
|
||||
controller.add(socketEvent(id: 400, authorId: 7, roomId: chatId));
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
final room = chatsCubit.state.chatRooms.firstWhere((r) => r.id == chatId);
|
||||
expect(room.unreadMessagesCount, 3);
|
||||
expect(chatsCubit.state.chatsWithUnreadMessagesCount, 1);
|
||||
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('close - закрывает cubit и отписывается от подписок', () async {
|
||||
final mockSubscription1 = MockStreamSubscription();
|
||||
|
||||
@@ -476,3 +580,12 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Облегчённый фейк события канала: ChatsCubit использует только event.data,
|
||||
/// поэтому реальный ChannelReadEvent (требует Channel) не нужен.
|
||||
class _FakeChannelReadEvent extends Fake implements ChannelReadEvent {
|
||||
_FakeChannelReadEvent(this.data);
|
||||
|
||||
@override
|
||||
final dynamic data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user