forked from eatvio/eatvio_app
feat(chat_messages): fixed data race for chat mesages state
This commit is contained in:
@@ -200,7 +200,7 @@ void main() {
|
||||
|
||||
test('markMessagesAsRead - отмечает сообщения как прочитанные', () async {
|
||||
const chatId = 'chat-id';
|
||||
final messagesIds = [(id: 1, index: 0)];
|
||||
final messagesIds = [1];
|
||||
|
||||
// Сначала инициализируем чат
|
||||
when(mockChatRepository.getChatRoom(any)).thenAnswer((_) async => testChatRoom);
|
||||
@@ -1443,5 +1443,218 @@ void main() {
|
||||
|
||||
verifyNever(mockChatRepository.getChatMessages(any, any));
|
||||
});
|
||||
|
||||
test('addIncomingMessage - добавляет входящее сообщение в начало списка', () async {
|
||||
const uuid = 'room-uuid';
|
||||
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => testChatRoom);
|
||||
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
|
||||
when(mockWsManager.client).thenReturn(null);
|
||||
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
|
||||
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);
|
||||
when(mockWsManager.directChatStream).thenReturn(null);
|
||||
when(mockWsManager.clientTypingStream).thenReturn(null);
|
||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => throw Exception('No plan'));
|
||||
|
||||
await chatRoomCubit.initChat(uuid);
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
final incoming = ChatMessageModel(
|
||||
id: 99,
|
||||
data: 'Incoming socket message',
|
||||
mediaData: null,
|
||||
authorId: 2,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: false,
|
||||
isCurrentUserAuthor: false,
|
||||
roomId: 'chat-id',
|
||||
);
|
||||
|
||||
chatRoomCubit.addIncomingMessage(incoming);
|
||||
|
||||
final state = chatRoomCubit.state as ChatRoomStateLoaded;
|
||||
expect(state.chatMessages.first.id, 99);
|
||||
expect(state.chatMessages.length, 2);
|
||||
});
|
||||
|
||||
test('addIncomingMessage - не добавляет дубликат по id', () async {
|
||||
const uuid = 'room-uuid';
|
||||
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => testChatRoom);
|
||||
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
|
||||
when(mockWsManager.client).thenReturn(null);
|
||||
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
|
||||
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);
|
||||
when(mockWsManager.directChatStream).thenReturn(null);
|
||||
when(mockWsManager.clientTypingStream).thenReturn(null);
|
||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => throw Exception('No plan'));
|
||||
|
||||
await chatRoomCubit.initChat(uuid);
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
final incoming = ChatMessageModel(
|
||||
id: 1,
|
||||
data: 'Duplicate of existing',
|
||||
mediaData: null,
|
||||
authorId: 2,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: false,
|
||||
isCurrentUserAuthor: false,
|
||||
roomId: 'chat-id',
|
||||
);
|
||||
|
||||
chatRoomCubit.addIncomingMessage(incoming);
|
||||
|
||||
final state = chatRoomCubit.state as ChatRoomStateLoaded;
|
||||
expect(state.chatMessages.length, 1);
|
||||
expect(state.chatMessages.first.data, 'Test message');
|
||||
});
|
||||
|
||||
test('addIncomingMessage - игнорирует собственные сообщения', () async {
|
||||
const uuid = 'room-uuid';
|
||||
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => testChatRoom);
|
||||
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
|
||||
when(mockWsManager.client).thenReturn(null);
|
||||
when(mockWsManager.userId).thenReturn(1);
|
||||
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
|
||||
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);
|
||||
when(mockWsManager.directChatStream).thenReturn(null);
|
||||
when(mockWsManager.clientTypingStream).thenReturn(null);
|
||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => throw Exception('No plan'));
|
||||
|
||||
await chatRoomCubit.initChat(uuid);
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
chatRoomCubit.addIncomingMessage(
|
||||
ChatMessageModel(
|
||||
id: 77,
|
||||
data: 'Own echo',
|
||||
mediaData: null,
|
||||
authorId: 1,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: true,
|
||||
isCurrentUserAuthor: true,
|
||||
roomId: 'chat-id',
|
||||
),
|
||||
);
|
||||
|
||||
final state = chatRoomCubit.state as ChatRoomStateLoaded;
|
||||
expect(state.chatMessages.length, 1);
|
||||
expect(state.chatMessages.any((message) => message.id == 77), isFalse);
|
||||
});
|
||||
|
||||
test('updateChosenPlanState - не затирает сообщения, пришедшие во время запроса', () async {
|
||||
const uuid = 'room-uuid';
|
||||
const planId = 123;
|
||||
final chatRoomWithPlan = testChatRoom.copyWith(chosenPlanId: planId);
|
||||
const testPlan = PlanModel(
|
||||
id: 1,
|
||||
slug: 'test-plan',
|
||||
name: 'Test Plan',
|
||||
image: 'https://example.com/image.jpg',
|
||||
duration: 7,
|
||||
state: 'active',
|
||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
||||
details: 'Test details',
|
||||
packetsCount: 3,
|
||||
author: Author(authorSlug: 'author', authorName: 'Author Name', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false),
|
||||
totalLikes: 10,
|
||||
totalViews: 100,
|
||||
totalComments: 5,
|
||||
isFavorite: false,
|
||||
minPriceOfOptions: 1000,
|
||||
maxPriceOfOptions: 2000,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
final chosenPlan = ChosenPlanModel(
|
||||
id: planId,
|
||||
optionType: 'options1',
|
||||
startDate: DateTime.now(),
|
||||
chosenPlanState: ChosenPlanStateEnum.stateStarted,
|
||||
chatId: 'chat-1',
|
||||
consultantId: 1,
|
||||
plan: testPlan,
|
||||
);
|
||||
final updatedPlan = chosenPlan.copyWith(chosenPlanState: ChosenPlanStateEnum.statePaid);
|
||||
final planCompleter = Completer<ChosenPlanModel>();
|
||||
var getChosenPlanCalls = 0;
|
||||
|
||||
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => chatRoomWithPlan);
|
||||
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
|
||||
when(mockChatRepository.getChosenPlan(id: planId)).thenAnswer((_) {
|
||||
getChosenPlanCalls++;
|
||||
if (getChosenPlanCalls == 1) {
|
||||
return Future.value(chosenPlan);
|
||||
}
|
||||
return planCompleter.future;
|
||||
});
|
||||
when(mockWsManager.client).thenReturn(null);
|
||||
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
|
||||
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);
|
||||
when(mockWsManager.directChatStream).thenReturn(null);
|
||||
when(mockWsManager.clientTypingStream).thenReturn(null);
|
||||
|
||||
await chatRoomCubit.initChat(uuid);
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
final updateFuture = chatRoomCubit.updateChosenPlanState();
|
||||
chatRoomCubit.addIncomingMessage(
|
||||
ChatMessageModel(
|
||||
id: 55,
|
||||
data: 'Arrived during plan poll',
|
||||
mediaData: null,
|
||||
authorId: 2,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: false,
|
||||
isCurrentUserAuthor: false,
|
||||
roomId: 'chat-id',
|
||||
),
|
||||
);
|
||||
|
||||
planCompleter.complete(updatedPlan);
|
||||
await updateFuture;
|
||||
|
||||
final state = chatRoomCubit.state as ChatRoomStateLoaded;
|
||||
expect(state.chatMessages.any((message) => message.id == 55), isTrue);
|
||||
expect(state.chatRoom.chosenPlan?.chosenPlanState, ChosenPlanStateEnum.statePaid);
|
||||
});
|
||||
|
||||
test('markMessagesAsRead - не затирает сообщения, пришедшие во время запроса', () async {
|
||||
const uuid = 'room-uuid';
|
||||
const chatId = 'chat-id';
|
||||
final markCompleter = Completer<void>();
|
||||
|
||||
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => testChatRoom);
|
||||
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
|
||||
when(mockWsManager.client).thenReturn(null);
|
||||
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
|
||||
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);
|
||||
when(mockWsManager.directChatStream).thenReturn(null);
|
||||
when(mockWsManager.clientTypingStream).thenReturn(null);
|
||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => throw Exception('No plan'));
|
||||
when(mockChatRepository.markMessagesAsRead(chatId, [1])).thenAnswer((_) => markCompleter.future);
|
||||
|
||||
await chatRoomCubit.initChat(uuid);
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
final markFuture = chatRoomCubit.markMessagesAsRead(chatId, [1]);
|
||||
chatRoomCubit.addIncomingMessage(
|
||||
ChatMessageModel(
|
||||
id: 88,
|
||||
data: 'Arrived during mark as read',
|
||||
mediaData: null,
|
||||
authorId: 2,
|
||||
updatedAt: DateTime.now(),
|
||||
isRead: false,
|
||||
isCurrentUserAuthor: false,
|
||||
roomId: chatId,
|
||||
),
|
||||
);
|
||||
|
||||
markCompleter.complete();
|
||||
await markFuture;
|
||||
|
||||
final state = chatRoomCubit.state as ChatRoomStateLoaded;
|
||||
expect(state.chatMessages.any((message) => message.id == 88), isTrue);
|
||||
expect(state.chatMessages.firstWhere((message) => message.id == 1).isRead, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user