forked from eatvio/eatvio_app
fix(consultations): open chat via consultation chat_id
Use chat_id from the consultations payload for direct navigation and keep the chat lookup as a fallback for legacy rows without chat_id. - validate chat id in ChatRepository and make it nullable - guard repeated taps while opening chat - handle nullable chat id in ProfileCubit and EmptyChatScreen related to #1106
This commit is contained in:
@@ -526,11 +526,11 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test('getChatByUserSlug - возвращает 404 при отсутствии чата', () async {
|
||||
test('getChatByUserSlug - возвращает null, если в ответе нет id', () async {
|
||||
const slug = 'user-slug';
|
||||
final response = HttpResponse(
|
||||
{},
|
||||
Response(requestOptions: RequestOptions(path: ''), statusCode: 404),
|
||||
Response(requestOptions: RequestOptions(path: ''), statusCode: 200),
|
||||
);
|
||||
|
||||
when(
|
||||
@@ -539,7 +539,7 @@ void main() {
|
||||
|
||||
final result = await chatRepository.getChatByUserSlug(slug);
|
||||
|
||||
expect(result, '404');
|
||||
expect(result, isNull);
|
||||
verify(mockChatService.getChatByUserSlug(slug: slug)).called(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -156,6 +156,55 @@ void main() {
|
||||
});
|
||||
|
||||
group('ConsultationModel', () {
|
||||
test('fromJson parses chat_id', () {
|
||||
final json = {
|
||||
'id': 1,
|
||||
'chat_id': 'direct-chat-uuid',
|
||||
'last_state': 'started',
|
||||
'plan_title': 'Test Plan',
|
||||
'user_name': 'User 1',
|
||||
'user_slug': 'user-1',
|
||||
'consultant_payment': 1000.0,
|
||||
'duration': 30,
|
||||
'current_diary_day': 5,
|
||||
'user_profile_photo_url': 'photo.jpg',
|
||||
'consultation_option_price': 2000,
|
||||
'start_date': '2024-01-01T00:00:00Z',
|
||||
'end_date': '2024-01-31T00:00:00Z',
|
||||
'consultant_id': 1,
|
||||
'diaries': [],
|
||||
'personal_diet_id': null,
|
||||
};
|
||||
|
||||
final model = ConsultationModel.fromJson(json);
|
||||
|
||||
expect(model.chatId, 'direct-chat-uuid');
|
||||
});
|
||||
|
||||
test('fromJson allows missing chat_id', () {
|
||||
final json = {
|
||||
'id': 1,
|
||||
'last_state': 'started',
|
||||
'plan_title': 'Test Plan',
|
||||
'user_name': 'User 1',
|
||||
'user_slug': 'user-1',
|
||||
'consultant_payment': 1000.0,
|
||||
'duration': 30,
|
||||
'current_diary_day': 5,
|
||||
'user_profile_photo_url': 'photo.jpg',
|
||||
'consultation_option_price': 2000,
|
||||
'start_date': '2024-01-01T00:00:00Z',
|
||||
'end_date': '2024-01-31T00:00:00Z',
|
||||
'consultant_id': 1,
|
||||
'diaries': [],
|
||||
'personal_diet_id': null,
|
||||
};
|
||||
|
||||
final model = ConsultationModel.fromJson(json);
|
||||
|
||||
expect(model.chatId, isNull);
|
||||
});
|
||||
|
||||
test('toJson serializes ConsultationModel correctly', () {
|
||||
final model = ConsultationModel(
|
||||
id: 1,
|
||||
|
||||
@@ -75,9 +75,9 @@ void main() {
|
||||
expect(chatId, isNull);
|
||||
});
|
||||
|
||||
test('getChatIdByUserSlug - возвращает null когда чат не найден', () async {
|
||||
test('getChatIdByUserSlug - возвращает null когда чата нет', () async {
|
||||
when(() => mockChatRepository.getChatByUserSlug(any()))
|
||||
.thenAnswer((_) async => '404');
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
final chatId = await cubit.getChatIdByUserSlug('john-doe');
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
@@ -8,6 +10,7 @@ import 'package:eatvio_app/features/consultations/presentation/widgets/consultat
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
@@ -33,6 +36,20 @@ void main() {
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('PonnamKarthik/fluttertoast'),
|
||||
(MethodCall methodCall) async => null,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('PonnamKarthik/fluttertoast'),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
final mockConsultation = ConsultationModel(
|
||||
@@ -207,5 +224,93 @@ void main() {
|
||||
verify(() => cubit.getChatIdByUserSlug('john-doe')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'opens chat directly with chat id without requesting it again',
|
||||
(tester) async {
|
||||
final cubit = MockConsultationsCubit();
|
||||
when(() => cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
final consultationWithChatId = mockConsultation.copyWith(
|
||||
chatId: 'direct-chat-uuid',
|
||||
);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
ConsultationCardWidget(
|
||||
consultationsModel: consultationWithChatId,
|
||||
),
|
||||
cubit: cubit,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.chat_outlined));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('chat:direct-chat-uuid'), findsOneWidget);
|
||||
verifyNever(() => cubit.getChatIdByUserSlug(any()));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('shows error and stays on card when chat id is null', (
|
||||
tester,
|
||||
) async {
|
||||
final cubit = MockConsultationsCubit();
|
||||
when(() => cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
when(() => cubit.getChatIdByUserSlug('john-doe'))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
ConsultationCardWidget(consultationsModel: mockConsultation),
|
||||
cubit: cubit,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.chat_outlined));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 3));
|
||||
|
||||
expect(find.textContaining('chat:'), findsNothing);
|
||||
expect(
|
||||
find.byType(ConsultationCardWidget),
|
||||
findsOneWidget,
|
||||
);
|
||||
verify(() => cubit.getChatIdByUserSlug('john-doe')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('does not request chat id twice while first is pending', (
|
||||
tester,
|
||||
) async {
|
||||
final cubit = MockConsultationsCubit();
|
||||
when(() => cubit.stream).thenAnswer((_) => const Stream.empty());
|
||||
final completer = Completer<String?>();
|
||||
when(() => cubit.getChatIdByUserSlug('john-doe'))
|
||||
.thenAnswer((_) => completer.future);
|
||||
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
ConsultationCardWidget(consultationsModel: mockConsultation),
|
||||
cubit: cubit,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.chat_outlined));
|
||||
await tester.tap(find.byIcon(Icons.chat_outlined));
|
||||
|
||||
completer.complete('chat-uuid');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('chat:chat-uuid'), findsOneWidget);
|
||||
verify(() => cubit.getChatIdByUserSlug('john-doe')).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user