import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:kouyu_english/core/app_state.dart'; import 'package:kouyu_english/core/generated_content.dart'; import 'package:kouyu_english/core/models.dart'; import 'package:kouyu_english/features/dialogue/dialogue_flow.dart'; import 'package:kouyu_english/features/review/review_page.dart'; import 'package:kouyu_english/features/shell/learning_shell.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:kouyu_english/core/courses/course_repository.dart'; import 'support/app_assets.dart'; void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); testWidgets('Onboarding PlacementPage back button returns to WelcomePage', ( tester, ) async { await pumpLoadedApp(tester); await tester.pumpAndSettle(); expect(find.text('每天 20 分钟,\n说出能用的英语。'), findsOneWidget); await tester.tap(find.text('继续')); await tester.pumpAndSettle(); expect(find.text('从哪里开始?'), findsOneWidget); expect(find.byIcon(Icons.arrow_back), findsOneWidget); await tester.tap(find.byIcon(Icons.arrow_back)); await tester.pumpAndSettle(); expect(find.text('每天 20 分钟,\n说出能用的英语。'), findsOneWidget); }); testWidgets('Profile sub-page back button returns to 我的', (tester) async { final state = AppState()..finishOnboarding(); await tester.pumpWidget(MaterialApp(home: LearningShell(state: state))); await tester.pumpAndSettle(); await tester.tap(find.text('我的')); await tester.pumpAndSettle(); final preferences = find.text('学习偏好'); await tester.ensureVisible(preferences); await tester.tap(preferences); await tester.pumpAndSettle(); expect(find.text('每日学习时间'), findsOneWidget); await tester.tap(find.byType(BackButton)); await tester.pumpAndSettle(); expect(find.text('每日学习时间'), findsNothing); expect(find.text('学习偏好'), findsOneWidget); }); testWidgets('LessonFlow back button returns to tab', (tester) async { final state = AppState()..finishOnboarding(); await tester.pumpWidget(MaterialApp(home: LearningShell(state: state))); await tester.pumpAndSettle(); expect(find.text('开始今天的学习'), findsOneWidget); await tester.tap(find.text('开始今天的学习')); await tester.pumpAndSettle(); // In LessonFlow expect(find.byIcon(Icons.arrow_back), findsOneWidget); // Tap back button await tester.tap(find.byIcon(Icons.arrow_back)); await tester.pumpAndSettle(); expect(find.text('开始今天的学习'), findsOneWidget); }); testWidgets('学习 tab shows today task and the lesson path', (tester) async { final state = AppState()..finishOnboarding(); await tester.pumpWidget(MaterialApp(home: LearningShell(state: state))); await tester.pumpAndSettle(); expect(find.byType(NavigationDestination), findsNWidgets(4)); expect(find.text('今日新课'), findsOneWidget); expect(find.text('课程路径'), findsOneWidget); expect(find.textContaining('第 1 课 · '), findsWidgets); }); testWidgets('DialogueSummaryPage has back button', (tester) async { var backCalled = false; await tester.pumpWidget( MaterialApp( home: DialogueSummaryPage( summary: const DialogueSummaryData( completedTasks: ['介绍姓名'], personalSentence: 'My name is Shen.', usedHelp: false, ), onHome: () {}, onLesson: () {}, onRetry: () {}, onBack: () => backCalled = true, ), ), ); await tester.pumpAndSettle(); expect(find.text('对话完成'), findsNWidgets(2)); // AppBar title and Eyebrow expect(find.byIcon(Icons.arrow_back), findsOneWidget); await tester.tap(find.byIcon(Icons.arrow_back)); await tester.pumpAndSettle(); expect(backCalled, isTrue); }); testWidgets('AdaptiveLessonPage has back button in all states', ( tester, ) async { final state = AppState()..finishOnboarding(); // 1. Empty state var finished = false; await tester.pumpWidget( MaterialApp( home: AdaptiveLessonPage( state: state, onFinished: () => finished = true, ), ), ); await tester.pumpAndSettle(); expect(find.text('AI 四技能补练'), findsNWidgets(2)); expect(find.byIcon(Icons.arrow_back), findsOneWidget); await tester.tap(find.byIcon(Icons.arrow_back)); expect(finished, isTrue); // 2. Active lesson state state.cacheApprovedAdaptiveLesson( const GeneratedLesson( lessonId: 'adapt-1', revision: 1, stageVersion: 'A0', abilityIds: ['greeting'], prerequisiteIds: [], targetItemIds: ['name'], receptiveChunks: ['My name is Mia.'], previewItemIds: ['name'], estimatedMinutes: 5, tasks: [ GeneratedLessonTask( taskId: 't1', skill: 'listening', type: 'listen', prompt: '听并写出名字', stimulus: 'My name is Mia.', answer: 'Mia', targetItemIds: ['name'], ), ], ), auditor: 'test', ); finished = false; await tester.pumpWidget( MaterialApp( home: AdaptiveLessonPage( state: state, onFinished: () => finished = true, ), ), ); await tester.pumpAndSettle(); expect(find.byIcon(Icons.arrow_back), findsOneWidget); await tester.tap(find.byIcon(Icons.arrow_back)); expect(finished, isTrue); }); testWidgets( 'AssessmentPreparationPage and AssessmentPage have back buttons', (tester) async { final state = AppState()..finishOnboarding(); await tester.pumpWidget(MaterialApp(home: LearningShell(state: state))); await tester.pumpAndSettle(); // Navigate to "我的" await tester.tap(find.text('我的')); await tester.pumpAndSettle(); final assessments = find.text('四技能评估'); await tester.ensureVisible(assessments); await tester.tap(assessments); await tester.pumpAndSettle(); // Open assessment pack A0-E1 final packAButton = find.text('开始 A0-E1'); await tester.ensureVisible(packAButton); await tester.pumpAndSettle(); expect(packAButton, findsOneWidget); await tester.tap(packAButton); await tester.pumpAndSettle(); // In AssessmentPreparationPage expect(find.text('评估准备'), findsOneWidget); expect(find.byIcon(Icons.arrow_back), findsOneWidget); // Start assessment await tester.tap(find.text('开始评估')); await tester.pumpAndSettle(); // In AssessmentPage expect(find.byIcon(Icons.arrow_back), findsOneWidget); // Returns to 我的 await tester.tap(find.byIcon(Icons.arrow_back)); await tester.pumpAndSettle(); expect(find.text('评估准备'), findsNothing); expect(find.text('四技能评估'), findsOneWidget); }, ); testWidgets( 'LessonFlow on complete step: back button finalizes segment and advances lesson', (tester) async { await tester.runAsync( () => CourseRepository.instance.load(levels: {'A0'}), ); final state = AppState()..finishOnboarding(); expect(state.activeLessonId, 'a0-01'); await tester.pumpWidget(MaterialApp(home: LearningShell(state: state))); await tester.pumpAndSettle(); // Tap '开始今天的学习' to enter LessonFlow await tester.tap(find.text('开始今天的学习')); await tester.pumpAndSettle(); // Complete all steps of a0-01 state.completePreview(); state.completeListening(recognized: true); state.completeSpeaking(); state.completeReading(); state.completeWriting(assisted: false, rawAnswer: 'hello'); state.completeLessonDialogue(); state.completeIndependentAttempt(assisted: false, rawAnswer: 'hello'); expect(state.lessonStep, LessonStep.complete); await tester.pumpAndSettle(); // We should be in lesson flow complete screen expect(find.text('本段已保存'), findsOneWidget); expect(find.text('回到首页'), findsOneWidget); expect(find.byIcon(Icons.arrow_back), findsOneWidget); // Tap the top-left AppBar back button instead of '回到首页' await tester.tap(find.byIcon(Icons.arrow_back)); await tester.pumpAndSettle(); // Should be back on the 学习 tab, and lesson must have advanced to a0-02 expect(state.completedLessonIds, contains('a0-01')); expect(state.activeLessonId, 'a0-02'); expect(find.textContaining('第 2 课 · '), findsWidgets); }, ); testWidgets( 'TodayTaskCard reflects multi-segment progress and completed fallback', (tester) async { await tester.runAsync( () => CourseRepository.instance.load(levels: {'A0'}), ); final state = AppState()..finishOnboarding(); state.activeLessonId = 'a0-04'; // a0-04 has 3 segments await tester.pumpWidget(MaterialApp(home: LearningShell(state: state))); await tester.pumpAndSettle(); // Segment 1 (initial) expect(find.text('今日新课'), findsOneWidget); expect(find.text('开始今天的学习'), findsOneWidget); // Now complete segment 1 state.completeSegment('a0-04', 0); await tester.pumpAndSettle(); // Segment 2 (resuming) expect(find.text('继续课程'), findsOneWidget); expect(find.text('继续学习第 2 段'), findsOneWidget); expect(find.textContaining('第 2/3 段'), findsWidgets); // If activeLessonId is marked as completed, card targets next incomplete state.completedLessonIds.add('a0-04'); state.notifyListeners(); await tester.pumpAndSettle(); // Should target a0-01 or next incomplete, not show completed lesson expect(find.text('继续课程'), findsNothing); expect(find.textContaining('第 1 课 · '), findsWidgets); }, ); }