import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:flutter/material.dart'; import 'package:kouyu_english/core/app_state.dart'; import 'package:kouyu_english/features/review/review_page.dart'; import 'package:kouyu_english/core/ai_service.dart'; import 'package:kouyu_english/core/models.dart'; void main() { Future<(WritingAiFeedback?, String)> check(Map reply) async { AiService.instance.setFallbackApiKey('test-key'); var prompt = ''; final content = jsonEncode(reply); final result = await http.runWithClient( () => AiService.instance.answerFeedback( provider: AiProviderType.compatible, endpoint: 'https://api.deepseek.com', model: 'deepseek-flash', answerId: 'A0-P12', target: "I'm from …", taskPrompt: '告诉对方你来自哪里。', answer: 'I am from Chna', ), () => MockClient((request) async { final body = jsonDecode(request.body) as Map; prompt = jsonEncode(body); return http.Response( jsonEncode({ 'choices': [ { 'message': {'content': content}, }, ], }), 200, headers: {'content-type': 'application/json; charset=utf-8'}, ); }), ); return (result, prompt); } test('review AI check returns spelling corrections for the item', () async { final (feedback, prompt) = await check({ 'schemaVersion': 'writing-feedback-1', 'verdict': 'rewrite', 'feedback': '句型正确,有一个拼写错误。', 'suggestion': 'I am from China.', 'missing': ['Chna 拼写应为 China'], 'lessonId': 'A0-P12', }); expect(prompt, contains('spelling')); expect(prompt, contains('I am from Chna')); expect(feedback, isNotNull); expect(feedback!.verdict, 'rewrite'); expect(feedback.suggestion, 'I am from China.'); expect(feedback.missing, ['Chna 拼写应为 China']); }); test('review AI check rejects feedback for a different item', () async { final (feedback, _) = await check({ 'schemaVersion': 'writing-feedback-1', 'verdict': 'accepted', 'feedback': '很好。', 'suggestion': null, 'missing': [], 'lessonId': 'A0-P01', }); expect(feedback, isNull); }); test('review AI check is skipped without a configured provider', () async { final feedback = await AiService.instance.answerFeedback( provider: AiProviderType.mock, endpoint: '', model: '', answerId: 'A0-P12', target: "I'm from …", taskPrompt: '告诉对方你来自哪里。', answer: 'I am from China', ); expect(feedback, isNull); }); testWidgets('finishing after an AI correction counts as assisted', ( tester, ) async { AiService.instance.setFallbackApiKey('test-key'); final state = AppState() ..aiProvider = AiProviderType.compatible ..aiEndpoint = 'https://api.deepseek.com' ..aiModel = 'deepseek-flash'; state.reviewQueue.add( ReviewItem( id: 'A0-P12', target: "I'm from …", prompt: '告诉对方你来自哪里。', hint: "I'm from China.", dueAt: DateTime.now().subtract(const Duration(hours: 1)), skill: '回忆表达', ), ); final content = jsonEncode({ 'schemaVersion': 'writing-feedback-1', 'verdict': 'rewrite', 'feedback': '句型正确,有一个拼写错误。', 'suggestion': 'I am from China.', 'missing': ['Chna 拼写应为 China'], 'lessonId': 'A0-P12', }); await http.runWithClient( () async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: ReviewPage( state: state, onFinished: () {}, onOpenAdaptiveLesson: () {}, ), ), ), ); await tester.enterText(find.byType(TextField), 'I am from Chna'); await tester.pump(); final aiButton = find.text('AI 检查语法和拼写(可选)'); await tester.ensureVisible(aiButton); await tester.tap(aiButton); await tester.runAsync( () => Future.delayed(const Duration(milliseconds: 50)), ); await tester.pump(); }, () => MockClient( (request) async => http.Response( jsonEncode({ 'choices': [ { 'message': {'content': content}, }, ], }), 200, headers: {'content-type': 'application/json; charset=utf-8'}, ), ), ); expect(find.text('· Chna 拼写应为 China'), findsOneWidget); expect(find.text('参考改正:I am from China.'), findsOneWidget); final submit = find.text('带提示完成'); expect(submit, findsOneWidget); await tester.ensureVisible(submit); await tester.tap(submit); await tester.pump(); expect(state.attemptEvidence.single.outcome, EvidenceKind.assisted); expect(state.attemptEvidence.single.assisted, isTrue); }); }