forked from eatvio/eatvio_app
1041 lines
32 KiB
Dart
1041 lines
32 KiB
Dart
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';
|
||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
|
||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:mockito/annotations.dart';
|
||
import 'package:mockito/mockito.dart';
|
||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||
import 'chats_cubit_test.mocks.dart';
|
||
|
||
@GenerateNiceMocks([
|
||
MockSpec<ChatRepository>(),
|
||
MockSpec<ProfileRepository>(),
|
||
MockSpec<WsManager>(),
|
||
MockSpec<StreamSubscription>(),
|
||
])
|
||
void main() {
|
||
TestWidgetsFlutterBinding.ensureInitialized();
|
||
group('ChatsCubit', () {
|
||
late ChatsCubit chatsCubit;
|
||
late MockChatRepository mockChatRepository;
|
||
late MockProfileRepository mockProfileRepository;
|
||
late MockWsManager mockWsManager;
|
||
|
||
const testChatRoomsPagination = ChatRoomPaginationModel(
|
||
data: [],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 0,
|
||
),
|
||
);
|
||
|
||
setUp(() {
|
||
mockChatRepository = MockChatRepository();
|
||
mockProfileRepository = MockProfileRepository();
|
||
mockWsManager = MockWsManager();
|
||
chatsCubit = ChatsCubit(
|
||
chatRepository: mockChatRepository,
|
||
wsManager: mockWsManager,
|
||
profileRepository: mockProfileRepository,
|
||
);
|
||
});
|
||
|
||
tearDown(() async {
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
if (!chatsCubit.isClosed) {
|
||
await chatsCubit.close();
|
||
}
|
||
});
|
||
|
||
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,
|
||
String data = 'socket message',
|
||
}) {
|
||
return _FakeChannelReadEvent(
|
||
jsonEncode(<String, dynamic>{
|
||
'id': id,
|
||
'data': data,
|
||
'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);
|
||
});
|
||
|
||
test(
|
||
'getChatRoomsWithLoading - загружает комнаты с loading состоянием',
|
||
() async {
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null),
|
||
).thenAnswer((_) async => testChatRoomsPagination);
|
||
|
||
await chatsCubit.getChatRoomsWithLoading();
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
expect(chatsCubit.state.state, BaseState.success);
|
||
verify(mockChatRepository.getChatRooms(query: null)).called(1);
|
||
},
|
||
);
|
||
|
||
test('getChatRoomsWithLoading - обрабатывает ошибку', () async {
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null),
|
||
).thenThrow(Exception('Network error'));
|
||
|
||
await chatsCubit.getChatRoomsWithLoading();
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
expect(chatsCubit.state.state, BaseState.error);
|
||
expect(chatsCubit.state.errorMessage, isNotNull);
|
||
});
|
||
|
||
test('getChatRooms - загружает комнаты без loading состояния', () async {
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null),
|
||
).thenAnswer((_) async => testChatRoomsPagination);
|
||
|
||
await chatsCubit.getChatRooms();
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
expect(chatsCubit.state.state, BaseState.success);
|
||
verify(mockChatRepository.getChatRooms(query: null)).called(1);
|
||
});
|
||
|
||
test('getChatRooms - загружает комнаты с запросом', () async {
|
||
const query = 'test query';
|
||
when(
|
||
mockChatRepository.getChatRooms(query: query),
|
||
).thenAnswer((_) async => testChatRoomsPagination);
|
||
|
||
await chatsCubit.getChatRooms(query: query);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
expect(chatsCubit.state.state, BaseState.success);
|
||
verify(mockChatRepository.getChatRooms(query: query)).called(1);
|
||
});
|
||
|
||
test('loadMoreChatRooms - загружает больше комнат', () async {
|
||
// Create pagination with lastPage > 1 so loadMoreChatRooms doesn't return early
|
||
const paginationPage1 = ChatRoomPaginationModel(
|
||
data: [],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 3, // Set to 3 so we can load more
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 30,
|
||
),
|
||
);
|
||
|
||
const paginationPage2 = ChatRoomPaginationModel(
|
||
data: [],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 2,
|
||
from: null,
|
||
lastPage: 3,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 30,
|
||
),
|
||
);
|
||
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null, page: 1),
|
||
).thenAnswer((_) async => paginationPage1);
|
||
|
||
// Сначала загружаем первую страницу
|
||
await chatsCubit.getChatRooms();
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
// Затем загружаем следующую страницу
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null, page: 2),
|
||
).thenAnswer((_) async => paginationPage2);
|
||
|
||
await chatsCubit.loadMoreChatRooms();
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
verify(mockChatRepository.getChatRooms(query: null, page: 2)).called(1);
|
||
});
|
||
|
||
test('loadMoreChatRooms - не загружает если уже загружается', () async {
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null),
|
||
).thenAnswer((_) async => testChatRoomsPagination);
|
||
|
||
await chatsCubit.getChatRooms();
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
// Устанавливаем isLoadingMoreChats в true
|
||
chatsCubit.emit(chatsCubit.state.copyWith(isLoadingMoreChats: true));
|
||
|
||
await chatsCubit.loadMoreChatRooms();
|
||
|
||
// Не должно вызывать репозиторий
|
||
verifyNever(mockChatRepository.getChatRooms(query: null, page: 2));
|
||
});
|
||
|
||
test(
|
||
'reduceUnreadMessagesCount - уменьшает количество непрочитанных сообщений',
|
||
() {
|
||
const chatId = 'chat-id';
|
||
const count = 2;
|
||
|
||
const chatRoom = ChatRoom(
|
||
id: chatId,
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 5,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 1,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: pagination.data,
|
||
chatsWithUnreadMessagesCount: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.reduceUnreadMessagesCount(chatId: chatId, count: count);
|
||
|
||
final updatedState = chatsCubit.state;
|
||
final updatedRoom = updatedState.chatRooms.firstWhere(
|
||
(r) => r.id == chatId,
|
||
);
|
||
expect(updatedRoom.unreadMessagesCount, 3);
|
||
},
|
||
);
|
||
|
||
test('connectToWebSocket - подключается к WebSocket', () async {
|
||
const mockStream = Stream<ChannelReadEvent>.empty();
|
||
when(mockWsManager.newMessagesStream).thenAnswer((_) => mockStream);
|
||
|
||
await chatsCubit.connectToWebSocket();
|
||
|
||
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(
|
||
'_handleNewMessageViaSocket - чужой старт видеозвонка показывает входящий вызов',
|
||
() async {
|
||
const chatId = 'chat-id';
|
||
final presented = <String>[];
|
||
chatsCubit.presentIncomingVideoCall = (payload) async {
|
||
presented.add(payload.conversationUuid);
|
||
};
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: [buildChatRoom(chatId)],
|
||
),
|
||
);
|
||
when(mockWsManager.userId).thenReturn(42);
|
||
|
||
final controller = await connectToSocketStream();
|
||
controller.add(
|
||
socketEvent(
|
||
id: 500,
|
||
authorId: 7,
|
||
roomId: chatId,
|
||
data: 'Анна начал(а) видеозвонок.',
|
||
),
|
||
);
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
expect(presented, [chatId]);
|
||
|
||
await controller.close();
|
||
},
|
||
);
|
||
|
||
test(
|
||
'_handleNewMessageViaSocket - свой старт видеозвонка не показывает входящий вызов',
|
||
() async {
|
||
const chatId = 'chat-id';
|
||
final presented = <String>[];
|
||
chatsCubit.presentIncomingVideoCall = (payload) async {
|
||
presented.add(payload.conversationUuid);
|
||
};
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: [buildChatRoom(chatId)],
|
||
),
|
||
);
|
||
when(mockWsManager.userId).thenReturn(42);
|
||
|
||
final controller = await connectToSocketStream();
|
||
controller.add(
|
||
socketEvent(
|
||
id: 501,
|
||
authorId: 42,
|
||
roomId: chatId,
|
||
data: 'Анна начал(а) видеозвонок.',
|
||
),
|
||
);
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
expect(presented, isEmpty);
|
||
|
||
await controller.close();
|
||
},
|
||
);
|
||
|
||
test(
|
||
'_handleNewMessageViaSocket - завершение видеозвонка закрывает только этот чат',
|
||
() async {
|
||
const chatId = 'chat-id';
|
||
final dismissed = <String?>[];
|
||
chatsCubit.dismissIncomingVideoCall = (uuid) async {
|
||
dismissed.add(uuid);
|
||
};
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: [buildChatRoom(chatId)],
|
||
),
|
||
);
|
||
when(mockWsManager.userId).thenReturn(42);
|
||
|
||
final controller = await connectToSocketStream();
|
||
controller.add(
|
||
socketEvent(
|
||
id: 502,
|
||
authorId: 7,
|
||
roomId: chatId,
|
||
data: 'Анна завершил(а) видеозвонок.',
|
||
),
|
||
);
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
expect(dismissed, [chatId]);
|
||
|
||
await controller.close();
|
||
},
|
||
);
|
||
|
||
test('close - закрывает cubit и отписывается от подписок', () async {
|
||
final mockSubscription1 = MockStreamSubscription();
|
||
|
||
when(mockSubscription1.cancel()).thenAnswer((_) async => Future.value());
|
||
|
||
// Set newIncomingMessagesSubscription
|
||
chatsCubit.newIncomingMessagesSubscription = mockSubscription1;
|
||
|
||
await chatsCubit.close();
|
||
|
||
// Verify that cancel was called on newIncomingMessagesSubscription
|
||
verify(mockSubscription1.cancel()).called(1);
|
||
// newLastMessageSubscription is cancelled in super.close() which we can't easily verify
|
||
// but the test verifies that the method completes without error
|
||
});
|
||
|
||
test(
|
||
'reduceUnreadMessagesCount - уменьшает до 0 и уменьшает chatsWithUnreadMessagesCount',
|
||
() {
|
||
const chatId = 'chat-id';
|
||
const count = 5;
|
||
|
||
const chatRoom = ChatRoom(
|
||
id: chatId,
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 5,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 1,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: pagination.data,
|
||
chatsWithUnreadMessagesCount: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.reduceUnreadMessagesCount(chatId: chatId, count: count);
|
||
|
||
final updatedState = chatsCubit.state;
|
||
final updatedRoom = updatedState.chatRooms.firstWhere(
|
||
(r) => r.id == chatId,
|
||
);
|
||
expect(updatedRoom.unreadMessagesCount, 0);
|
||
expect(updatedState.chatsWithUnreadMessagesCount, 0);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'reduceUnreadMessagesCount - не уменьшает chatsWithUnreadMessagesCount если unreadMessagesCount > 0',
|
||
() {
|
||
const chatId = 'chat-id';
|
||
const count = 2;
|
||
|
||
const chatRoom = ChatRoom(
|
||
id: chatId,
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 5,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 1,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: pagination.data,
|
||
chatsWithUnreadMessagesCount: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.reduceUnreadMessagesCount(chatId: chatId, count: count);
|
||
|
||
final updatedState = chatsCubit.state;
|
||
expect(updatedState.chatsWithUnreadMessagesCount, 1);
|
||
},
|
||
);
|
||
|
||
test('loadMoreChatRooms - не загружает если lastPage == page', () async {
|
||
const paginationPage1 = ChatRoomPaginationModel(
|
||
data: [],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 10,
|
||
),
|
||
);
|
||
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null, page: 1),
|
||
).thenAnswer((_) async => paginationPage1);
|
||
|
||
await chatsCubit.getChatRooms();
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
// lastPage = meta.lastPage + 1 = 2, page = 2, поэтому не должно загружать
|
||
await chatsCubit.loadMoreChatRooms();
|
||
|
||
verifyNever(mockChatRepository.getChatRooms(query: null, page: 2));
|
||
});
|
||
|
||
test('loadMoreChatRooms - обрабатывает ошибку', () async {
|
||
const paginationPage1 = ChatRoomPaginationModel(
|
||
data: [],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 3,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 30,
|
||
),
|
||
);
|
||
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null, page: 1),
|
||
).thenAnswer((_) async => paginationPage1);
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null, page: 2),
|
||
).thenThrow(Exception('Ошибка загрузки'));
|
||
|
||
await chatsCubit.getChatRooms();
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
await chatsCubit.loadMoreChatRooms();
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
expect(chatsCubit.state.state, BaseState.error);
|
||
expect(chatsCubit.state.errorMessage, isNotNull);
|
||
expect(chatsCubit.state.isLoadingMoreChats, false);
|
||
});
|
||
|
||
test('getChatRooms - обрабатывает ошибку', () async {
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null),
|
||
).thenThrow(Exception('Network error'));
|
||
|
||
await chatsCubit.getChatRooms();
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
expect(chatsCubit.state.state, BaseState.error);
|
||
expect(chatsCubit.state.errorMessage, isNotNull);
|
||
});
|
||
|
||
test('_setNewLastMessage - обновляет последнее сообщение в чате', () async {
|
||
const chatId = 'chat-id';
|
||
const chatRoom = ChatRoom(
|
||
id: chatId,
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 0,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.emit(
|
||
ChatsState(state: BaseState.success, chatRooms: pagination.data),
|
||
);
|
||
|
||
final newMessage = ChatMessageModel(
|
||
id: 1,
|
||
data: 'Test message',
|
||
mediaData: null,
|
||
urlsMediaInfo: null,
|
||
authorId: 1,
|
||
updatedAt: DateTime.now(),
|
||
isRead: false,
|
||
isCurrentUserAuthor: true,
|
||
roomId: chatId,
|
||
type: ChatMessageType.text,
|
||
);
|
||
|
||
// Используем рефлексию для вызова приватного метода через тестовый доступ
|
||
// Или проверяем через публичный API, если есть
|
||
// В данном случае, _setNewLastMessage вызывается через newUserMessagesStream
|
||
final streamController = StreamController<ChatMessageModel>();
|
||
when(
|
||
mockChatRepository.newUserMessagesStream,
|
||
).thenAnswer((_) => streamController.stream);
|
||
|
||
// Пересоздаем cubit чтобы подписаться на stream
|
||
await chatsCubit.close();
|
||
chatsCubit = ChatsCubit(
|
||
chatRepository: mockChatRepository,
|
||
wsManager: mockWsManager,
|
||
profileRepository: mockProfileRepository,
|
||
);
|
||
chatsCubit.emit(
|
||
ChatsState(state: BaseState.success, chatRooms: pagination.data),
|
||
);
|
||
|
||
streamController.add(newMessage);
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
final updatedState = chatsCubit.state;
|
||
final updatedRoom = updatedState.chatRooms.firstWhere(
|
||
(r) => r.id == chatId,
|
||
);
|
||
expect(updatedRoom.lastMessage, isNotNull);
|
||
expect(updatedRoom.lastMessage?.id, 1);
|
||
|
||
await streamController.close();
|
||
});
|
||
|
||
test(
|
||
'_setNewLastMessage - обновляет последнее сообщение через newUserMessagesStream',
|
||
() async {
|
||
const chatId = 'chat-id';
|
||
const chatRoom = ChatRoom(
|
||
id: chatId,
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 0,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 0,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
final newMessage = ChatMessageModel(
|
||
id: 1,
|
||
data: 'Test message',
|
||
mediaData: null,
|
||
urlsMediaInfo: null,
|
||
authorId: 1,
|
||
updatedAt: DateTime.now(),
|
||
isRead: false,
|
||
isCurrentUserAuthor: true,
|
||
roomId: chatId,
|
||
type: ChatMessageType.text,
|
||
);
|
||
|
||
final streamController = StreamController<ChatMessageModel>();
|
||
when(
|
||
mockChatRepository.newUserMessagesStream,
|
||
).thenAnswer((_) => streamController.stream);
|
||
|
||
// Пересоздаем cubit чтобы подписаться на stream
|
||
await chatsCubit.close();
|
||
chatsCubit = ChatsCubit(
|
||
chatRepository: mockChatRepository,
|
||
wsManager: mockWsManager,
|
||
profileRepository: mockProfileRepository,
|
||
);
|
||
chatsCubit.emit(
|
||
ChatsState(state: BaseState.success, chatRooms: pagination.data),
|
||
);
|
||
|
||
streamController.add(newMessage);
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
final updatedState = chatsCubit.state;
|
||
final updatedRoom = updatedState.chatRooms.firstWhere(
|
||
(r) => r.id == chatId,
|
||
);
|
||
expect(updatedRoom.lastMessage, isNotNull);
|
||
expect(updatedRoom.lastMessage?.id, 1);
|
||
|
||
await streamController.close();
|
||
},
|
||
);
|
||
|
||
test(
|
||
'reduceUnreadMessagesCount - обрабатывает чат который не существует',
|
||
() {
|
||
const chatId = 'non-existent-chat';
|
||
const count = 2;
|
||
|
||
const chatRoom = ChatRoom(
|
||
id: 'other-chat-id',
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 5,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 1,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: pagination.data,
|
||
chatsWithUnreadMessagesCount: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.reduceUnreadMessagesCount(chatId: chatId, count: count);
|
||
|
||
final updatedState = chatsCubit.state;
|
||
// Чат не должен измениться
|
||
expect(updatedState.chatRooms.length, 1);
|
||
expect(updatedState.chatRooms.first.unreadMessagesCount, 5);
|
||
expect(updatedState.chatsWithUnreadMessagesCount, 1);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'reduceUnreadMessagesCount - обрабатывает уменьшение больше чем unreadMessagesCount',
|
||
() {
|
||
const chatId = 'chat-id';
|
||
const count = 10; // Больше чем unreadMessagesCount
|
||
|
||
const chatRoom = ChatRoom(
|
||
id: chatId,
|
||
lastMessage: null,
|
||
users: Users(
|
||
fromChatUser: ChatUser(
|
||
id: 1,
|
||
name: 'User 1',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
toChatUser: ChatUser(
|
||
id: 2,
|
||
name: 'User 2',
|
||
profilePhotoPath: null,
|
||
profilePhotoUrl: '',
|
||
),
|
||
),
|
||
unreadMessagesCount: 5,
|
||
chosenPlan: null,
|
||
);
|
||
|
||
const pagination = ChatRoomPaginationModel(
|
||
data: [chatRoom],
|
||
unreadChatsCount: 1,
|
||
meta: Meta(
|
||
currentPage: 1,
|
||
from: null,
|
||
lastPage: 1,
|
||
links: [],
|
||
perPage: 10,
|
||
to: null,
|
||
total: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.emit(
|
||
ChatsState(
|
||
state: BaseState.success,
|
||
chatRooms: pagination.data,
|
||
chatsWithUnreadMessagesCount: 1,
|
||
),
|
||
);
|
||
|
||
chatsCubit.reduceUnreadMessagesCount(chatId: chatId, count: count);
|
||
|
||
final updatedState = chatsCubit.state;
|
||
final updatedRoom = updatedState.chatRooms.firstWhere(
|
||
(r) => r.id == chatId,
|
||
);
|
||
expect(updatedRoom.unreadMessagesCount, 0);
|
||
expect(updatedState.chatsWithUnreadMessagesCount, 0);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'connectToWebSocket - обновляет список чатов после реконнекта',
|
||
() async {
|
||
final reconnectController = StreamController<void>.broadcast();
|
||
|
||
when(
|
||
mockWsManager.reconnected,
|
||
).thenAnswer((_) => reconnectController.stream);
|
||
when(mockWsManager.newMessagesStream).thenReturn(null);
|
||
when(
|
||
mockChatRepository.getChatRooms(query: null),
|
||
).thenAnswer((_) async => testChatRoomsPagination);
|
||
|
||
await chatsCubit.connectToWebSocket();
|
||
|
||
reconnectController.add(null);
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
verify(mockChatRepository.getChatRooms(query: null)).called(1);
|
||
|
||
await reconnectController.close();
|
||
},
|
||
);
|
||
});
|
||
}
|
||
|
||
/// Облегчённый фейк события канала: ChatsCubit использует только event.data,
|
||
/// поэтому реальный ChannelReadEvent (требует Channel) не нужен.
|
||
class _FakeChannelReadEvent extends Fake implements ChannelReadEvent {
|
||
_FakeChannelReadEvent(this.data);
|
||
|
||
@override
|
||
final dynamic data;
|
||
}
|