import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_app/core/network/api_repository.dart'; import 'package:flutter_app/features/chat/presentation/cubit/chat_cubit.dart'; import 'package:flutter_app/features/chat/presentation/cubit/chat_state.dart'; class _VersionTestApi extends ApiRepository { _VersionTestApi() : super(baseUrl: 'http://127.0.0.1:8000'); final responses = >[ ['الجواب الأول'], ['الجواب الثاني'], ]; SavedConversation? lastSaved; @override Future getModelName() async => 'gemma4:e2b'; @override Future> getAvailableModels() async => ['gemma4:e2b']; @override Future> listConversations() async => []; @override Future saveConversation(SavedConversation conversation) async { lastSaved = conversation; } @override Stream completeStream(List messages, {String? model}) => Stream.fromIterable(responses.removeAt(0)); } class _CancellableTestApi extends _VersionTestApi { final controller = StreamController(); var cancelled = false; final storedConversation = SavedConversation( id: 'conversation', title: 'سؤال قديم', updatedAt: DateTime.utc(2026), messages: const [ ChatMessage('سؤال قديم', fromUser: true), ChatMessage( 'إجابة محفوظة', fromUser: false, answerVersions: ['إجابة محفوظة'], ), ], ); @override Future getConversation(String id) async => storedConversation; @override Stream completeStream(List messages, {String? model}) => controller.stream; @override void cancelActiveRequest() { cancelled = true; controller.close(); } } void main() { test('regeneration keeps prior answers and selection is persisted', () async { final api = _VersionTestApi(); final cubit = ChatCubit(api); await Future.delayed(Duration.zero); await cubit.send('ما معنى API؟'); expect(cubit.state.messages.last.answerVersions, ['الجواب الأول']); expect(cubit.state.modelStatus, ModelConnectionStatus.connected); expect(cubit.state.requestStatus, RequestStatus.completed); expect(cubit.state.lastResponseDuration, isNotNull); await cubit.regenerateLastAnswer(); expect(cubit.state.messages.last.text, 'الجواب الثاني'); expect(cubit.state.messages.last.answerVersions, [ 'الجواب الأول', 'الجواب الثاني', ]); expect(cubit.state.messages.last.selectedVersion, 1); await cubit.selectAnswerVersion(1, 0); expect(cubit.state.messages.last.text, 'الجواب الأول'); expect(cubit.state.messages.last.selectedVersion, 0); expect(api.lastSaved?.messages.last.answerVersions, [ 'الجواب الأول', 'الجواب الثاني', ]); expect(api.lastSaved?.messages.last.selectedVersion, 0); await cubit.close(); }); test( 'cancelling generation preserves partial text and records duration', () async { final api = _CancellableTestApi(); final cubit = ChatCubit(api); await Future.delayed(Duration.zero); final send = cubit.send('اكتب فقرة قصيرة'); await Future.delayed(const Duration(milliseconds: 20)); api.controller.add('بداية الإجابة'); await Future.delayed(Duration.zero); await cubit.cancelGeneration(); await send; expect(api.cancelled, isTrue); expect(cubit.state.isSending, isFalse); expect(cubit.state.requestStatus, RequestStatus.cancelled); expect(cubit.state.messages.last.text, 'بداية الإجابة'); expect(cubit.state.lastResponseDuration, isNotNull); expect(api.lastSaved?.messages.last.text, 'بداية الإجابة'); await cubit.close(); await api.controller.close(); }, ); test( 'cancelling regeneration before its first token keeps saved answer', () async { final api = _CancellableTestApi(); final cubit = ChatCubit(api); await Future.delayed(Duration.zero); await cubit.openConversation(api.storedConversation); final regeneration = cubit.regenerateLastAnswer(); await Future.delayed(const Duration(milliseconds: 20)); await cubit.cancelGeneration(); await regeneration; expect(cubit.state.messages.last.text, 'إجابة محفوظة'); expect(api.lastSaved?.messages.last.text, 'إجابة محفوظة'); expect(cubit.state.requestStatus, RequestStatus.cancelled); await cubit.close(); await api.controller.close(); }, ); }