import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:kouyu_english/core/ai_service.dart'; import 'package:kouyu_english/core/app_state.dart'; import 'package:kouyu_english/core/models.dart'; import 'package:kouyu_english/widgets/lexicon_lookup.dart'; void main() { group('Sentence and Phrase Parsing Logic', () { test('isSentenceQuery correctly detects sentences vs words', () { expect(isSentenceQuery('apple'), isFalse); expect(isSentenceQuery('check in'), isFalse); expect(isSentenceQuery('Where is the subway?'), isTrue); expect(isSentenceQuery("I'd like to check in."), isTrue); expect(isSentenceQuery('This is a longer sentence with more than three words'), isTrue); }); test('extractCourseLexiconPhrases finds embedded course lexicon items', () { final matches = extractCourseLexiconPhrases("Hello, nice to meet you, where's the taxi?"); expect(matches, isNotEmpty); final words = matches.map((m) => m.word.toLowerCase()).toList(); expect(words.any((w) => w.contains('nice to meet you')), isTrue); }); }); group('Models Serialization', () { test('PhraseBreakdownItem serialization round-trip', () { const item = PhraseBreakdownItem( phrase: 'check in', meaning: '办理入住/值机', ipa: '/tʃek ɪn/', usageNote: '连读发音,酒店或机场常用', ); final json = item.toJson(); final parsed = PhraseBreakdownItem.fromJson(json); expect(parsed.phrase, 'check in'); expect(parsed.meaning, '办理入住/值机'); expect(parsed.ipa, '/tʃek ɪn/'); expect(parsed.usageNote, '连读发音,酒店或机场常用'); }); test('SentenceAnalysisResult serialization round-trip', () { final result = SentenceAnalysisResult( originalText: "I would like to check in, please.", translation: '我想办理入住手续,谢谢。', sentencePattern: "I would like to + 动词原形 (礼貌表达需求)", grammarNote: 'would like 语气委婉客气,适合服务场景。', pronunciationTips: 'would like 发音轻柔,check in 发生连读。', phrases: const [ PhraseBreakdownItem( phrase: 'would like to', meaning: '想要做某事(委婉)', ), PhraseBreakdownItem( phrase: 'check in', meaning: '办理入住', ipa: '/tʃek ɪn/', ), ], provider: 'mock', model: 'test-model', createdAt: DateTime.utc(2026, 9, 16, 10, 0, 0), ); final json = result.toJson(); final parsed = SentenceAnalysisResult.fromJson(json); expect(parsed.originalText, result.originalText); expect(parsed.translation, result.translation); expect(parsed.sentencePattern, result.sentencePattern); expect(parsed.grammarNote, result.grammarNote); expect(parsed.pronunciationTips, result.pronunciationTips); expect(parsed.phrases.length, 2); expect(parsed.phrases[0].phrase, 'would like to'); expect(parsed.phrases[1].ipa, '/tʃek ɪn/'); expect(parsed.provider, 'mock'); expect(parsed.model, 'test-model'); }); }); group('AiService Mock Sentence Analysis', () { test('returns structured breakdown for would like', () async { final analysis = await AiService.instance.analyzeSentence( provider: AiProviderType.mock, endpoint: '', model: 'mock', text: 'I would like a cup of tea.', ); expect(analysis, isNotNull); expect(analysis!.translation, contains('想要')); expect(analysis.sentencePattern, contains('would like')); expect(analysis.pronunciationTips, isNotNull); expect(analysis.phrases, isNotEmpty); expect(analysis.phrases.any((p) => p.phrase == 'would like'), isTrue); }); test('returns structured breakdown for where is', () async { final analysis = await AiService.instance.analyzeSentence( provider: AiProviderType.mock, endpoint: '', model: 'mock', text: 'Where is the gate?', ); expect(analysis, isNotNull); expect(analysis!.translation, contains('在哪里')); expect(analysis.sentencePattern, contains('Where is')); expect(analysis.phrases.any((p) => p.phrase == 'where is'), isTrue); }); test('returns structured fallback for generic sentence', () async { final analysis = await AiService.instance.analyzeSentence( provider: AiProviderType.mock, endpoint: '', model: 'mock', text: 'The weather is very sunny today.', ); expect(analysis, isNotNull); expect(analysis!.translation, isNotEmpty); expect(analysis.sentencePattern, isNotEmpty); expect(analysis.grammarNote, isNotEmpty); }); }); group('AppState Sentence Analysis & Review Integration', () { test('caching and retrieving sentence analyses with query normalization', () { final state = AppState(); final result = SentenceAnalysisResult( originalText: ' Where is the bus stop? ', translation: '请问公交站在哪里?', sentencePattern: 'Where is + 地点', createdAt: DateTime.now(), ); state.cacheSentenceAnalysis(result); expect(state.sentenceAnalysisFor('where is the bus stop?'), isNotNull); expect(state.sentenceAnalysisFor('WHERE IS THE BUS STOP? '), isNotNull); expect(state.sentenceAnalysisFor('where is the bus stop?')?.translation, '请问公交站在哪里?'); state.removeSentenceAnalysis('where is the bus stop?'); expect(state.sentenceAnalysisFor('where is the bus stop?'), isNull); }); test('addPhraseToReview converts phrase breakdown into vocabulary item in review queue', () { final state = AppState(); state.addPhraseToReview( phrase: 'check in', meaning: '办理登机或入住', ipa: '/tʃek ɪn/', usageNote: '酒店机场高频词', contextSentence: "I'd like to check in please.", ); expect(state.reviewQueue.any((r) => r.target == 'check in'), isTrue); final saved = state.reviewQueue.firstWhere((r) => r.target == 'check in'); expect(saved.hint, contains('办理登机或入住')); expect(saved.hint, contains('酒店机场高频词')); expect(saved.prompt, "I'd like to check in please."); }); }); group('UI Lookup Sheet Sentence Analysis View', () { testWidgets('renders sentence analysis result cards when cached', (tester) async { final state = AppState(); final analysis = SentenceAnalysisResult( originalText: 'Where is the departure gate?', translation: '请问登机口在哪里?', sentencePattern: 'Where is + 目的地', grammarNote: 'where 引导的疑问句,注意语调。', pronunciationTips: 'Where 与 is 自然连读。', phrases: const [ PhraseBreakdownItem( phrase: 'departure gate', ipa: '/dɪˈpɑːrtʃər ɡeɪt/', meaning: '登机口', usageNote: '机场核心词汇', ), ], provider: 'mock', createdAt: DateTime.now(), ); state.cacheSentenceAnalysis(analysis); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Builder( builder: (context) => ElevatedButton( onPressed: () => showLexiconLookup( context, state: state, initialText: 'Where is the departure gate?', ), child: const Text('查句'), ), ), ), ), ); await tester.tap(find.text('查句')); await tester.pumpAndSettle(); expect(find.text('查词与整句深度解析'), findsOneWidget); expect(find.text('中文整句翻译'), findsOneWidget); expect(find.text('请问登机口在哪里?'), findsOneWidget); expect(find.text('核心句型'), findsOneWidget); expect(find.text('Where is + 目的地'), findsOneWidget); expect(find.text('口语连读与发音'), findsOneWidget); expect(find.text('重点短语与搭配 (1)'), findsOneWidget); expect(find.text('departure gate'), findsOneWidget); expect(find.text('登机口'), findsOneWidget); expect(find.text('加复习'), findsOneWidget); // Scroll to '加复习' and tap await tester.ensureVisible(find.text('加复习')); await tester.pumpAndSettle(); await tester.tap(find.text('加复习')); await tester.pumpAndSettle(); expect(state.reviewQueue.any((r) => r.target == 'departure gate'), isTrue); expect(find.text('已在复习'), findsOneWidget); }); }); }