Update Saqel Platform: 2026-09-12 13:38:49
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:student_app/data/models/lesson_model.dart';
|
||||
import 'package:student_app/presentation/screens/curriculum/lesson_homework_sheet.dart';
|
||||
import 'package:student_app/presentation/screens/notebook/smart_error_notebook_screen.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Guardian Child Model & Error Metrics Integration', () {
|
||||
test('correctly parses server-authoritative error notebook metrics', () {
|
||||
final json = {
|
||||
'student': {
|
||||
'id': 42,
|
||||
'uuid': 'student-uuid-42',
|
||||
'full_name': 'عمر الخطيب',
|
||||
'national_id': '****1234',
|
||||
'grade_level': 'الصف العاشر الأساسي',
|
||||
'stream': 'علمي',
|
||||
'school_name': 'مدرسة الملك عبد الله للتميز',
|
||||
},
|
||||
'metrics': {
|
||||
'readiness_score': 88.5,
|
||||
'exams_passed_count': 7,
|
||||
'exams_total_count': 8,
|
||||
'error_notebook': {
|
||||
'total_errors': 6,
|
||||
'mastered_count': 4,
|
||||
'pending_count': 2,
|
||||
'mastery_percentage': 66.7,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
final model = GuardianChildModel.fromJson(json);
|
||||
|
||||
expect(model.id, 42);
|
||||
expect(model.name, 'عمر الخطيب');
|
||||
expect(model.readinessScore, 88.5);
|
||||
expect(model.examsPassed, 7);
|
||||
expect(model.examsTotal, 8);
|
||||
expect(model.errorTotalCount, 6);
|
||||
expect(model.errorMasteredCount, 4);
|
||||
expect(model.errorPendingCount, 2);
|
||||
expect(model.errorMasteryRate, 66.7);
|
||||
});
|
||||
|
||||
test('handles empty or perfect error metrics gracefully', () {
|
||||
final json = {
|
||||
'student': {'id': 1, 'full_name': 'سارة'},
|
||||
'metrics': {'readiness_score': 95.0},
|
||||
};
|
||||
|
||||
final model = GuardianChildModel.fromJson(json);
|
||||
|
||||
expect(model.errorTotalCount, 0);
|
||||
expect(model.errorMasteredCount, 0);
|
||||
expect(model.errorPendingCount, 0);
|
||||
expect(model.errorMasteryRate, 100.0);
|
||||
});
|
||||
});
|
||||
|
||||
group('Smart Error Notebook Guardian Mode UI Tests', () {
|
||||
testWidgets('renders guardian inspection mode with child name and lock badge', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SmartErrorNotebookScreen(
|
||||
isGuardianMode: true,
|
||||
studentName: 'عمر الخطيب',
|
||||
studentId: 42,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.textContaining('عمر الخطيب'), findsOneWidget);
|
||||
expect(find.text('رقابة الأهل 👨👧👦'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders student self-study mode without guardian badge', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SmartErrorNotebookScreen(
|
||||
isGuardianMode: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(find.text('دفتر الأخطاء الذكي والمسار العلاجي'), findsOneWidget);
|
||||
expect(find.text('رقابة الأهل 👨👧👦'), findsNothing);
|
||||
});
|
||||
});
|
||||
|
||||
group('Homework Sheet Error Logging', () {
|
||||
testWidgets('LessonHomeworkSheet mounts and handles answer verification', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: LessonHomeworkSheet(
|
||||
subjectId: 'physics_10',
|
||||
subjectTitle: 'الفيزياء',
|
||||
lessonId: 'lesson_01',
|
||||
lessonTitle: 'الكميات القياسية والمتجهة',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('واجب الدرس'), findsOneWidget);
|
||||
expect(find.textContaining('التمرين 1'), findsOneWidget);
|
||||
expect(find.text('تحقق من الإجابة 🚀'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:student_app/data/models/subject_model.dart';
|
||||
import 'package:student_app/data/repositories/curriculum_question_bank.dart';
|
||||
import 'package:student_app/data/repositories/curriculum_repository.dart';
|
||||
import 'package:student_app/presentation/screens/curriculum/teacher_selection_sheet.dart';
|
||||
import 'package:student_app/presentation/screens/player/scaffolded_thinking_pause_sheet.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('CurriculumQuestionBank Textbooks Isolation', () {
|
||||
test('Islamic textbooks return authentic Grade 10 books', () {
|
||||
final books = CurriculumQuestionBank.getSubjectTextbooks(
|
||||
subjectId: 'islamic_10',
|
||||
subjectTitle: 'التربية الإسلامية',
|
||||
);
|
||||
expect(books.length, 2);
|
||||
expect(books.first.title, contains('التربية الإسلامية'));
|
||||
expect(books.first.assetType, 'textbook');
|
||||
expect(books.first.mimeType, 'application/pdf');
|
||||
});
|
||||
|
||||
test('Math textbooks include exercise book', () {
|
||||
final books = CurriculumQuestionBank.getSubjectTextbooks(
|
||||
subjectId: 'math_10',
|
||||
subjectTitle: 'الرياضيات',
|
||||
);
|
||||
expect(books.length, 3);
|
||||
expect(books.any((b) => b.title.contains('التمارين')), isTrue);
|
||||
});
|
||||
|
||||
test('Physics textbooks return authentic semesters', () {
|
||||
final books = CurriculumQuestionBank.getSubjectTextbooks(
|
||||
subjectId: 'physics_10',
|
||||
subjectTitle: 'الفيزياء',
|
||||
);
|
||||
expect(books.length, 2);
|
||||
expect(books.any((b) => b.title.contains('الفيزياء')), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('TeacherSelectionSheet UI Tests', () {
|
||||
testWidgets('renders multiple teachers sorted by rating and allows choice', (tester) async {
|
||||
const lesson = CurriculumLessonItemModel(
|
||||
id: 'lesson_math_01',
|
||||
title: 'حل نظام مكون من معادلتين تربيعية وخطية',
|
||||
durationSeconds: 1200,
|
||||
hasVideo: true,
|
||||
checkpointsCount: 3,
|
||||
outcomes: ['عزل المتغير', 'التعويض'],
|
||||
);
|
||||
|
||||
final videos = [
|
||||
const PublishedLessonVideoModel(
|
||||
videoVersionId: 'v_ahmad',
|
||||
submissionId: 'sub_1',
|
||||
teacherName: 'أ. أحمد الطراونة',
|
||||
rating: 4.8,
|
||||
ratingCount: 95,
|
||||
isNew: false,
|
||||
),
|
||||
const PublishedLessonVideoModel(
|
||||
videoVersionId: 'v_khalid',
|
||||
submissionId: 'sub_2',
|
||||
teacherName: 'أ. خالد الحنيطي',
|
||||
rating: 4.9,
|
||||
ratingCount: 140,
|
||||
isNew: false,
|
||||
),
|
||||
];
|
||||
|
||||
PublishedLessonVideoModel? selected;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () async {
|
||||
selected = await TeacherSelectionSheet.show(
|
||||
context,
|
||||
lesson: lesson,
|
||||
videos: videos,
|
||||
);
|
||||
},
|
||||
child: const Text('افتح اختيار المعلم'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Open sheet
|
||||
await tester.tap(find.text('افتح اختيار المعلم'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Top rated teacher (Khalid, 4.9) should appear first with badge
|
||||
expect(find.text('اختر شرح المعلم المعتمد 👨🏫'), findsOneWidget);
|
||||
expect(find.text('الأعلى تقييماً'), findsOneWidget);
|
||||
expect(find.text('أ. خالد الحنيطي'), findsOneWidget);
|
||||
expect(find.text('أ. أحمد الطراونة'), findsOneWidget);
|
||||
|
||||
// Tap on Khalid
|
||||
await tester.tap(find.text('أ. خالد الحنيطي'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verified selection
|
||||
expect(selected, isNotNull);
|
||||
expect(selected!.teacherName, 'أ. خالد الحنيطي');
|
||||
expect(selected!.videoVersionId, 'v_khalid');
|
||||
});
|
||||
});
|
||||
|
||||
group('ScaffoldedThinkingPauseSheet UI Tests', () {
|
||||
testWidgets('enforces 60-second thinking phase and unmasks steps progressively', (tester) async {
|
||||
const lesson = CurriculumLessonItemModel(
|
||||
id: 'lesson_math_01',
|
||||
title: 'حل نظام مكون من معادلتين',
|
||||
durationSeconds: 900,
|
||||
hasVideo: true,
|
||||
checkpointsCount: 2,
|
||||
outcomes: ['حل الأنظمة'],
|
||||
);
|
||||
|
||||
tester.view.physicalSize = const Size(1080, 2200);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
bool completed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () {
|
||||
ScaffoldedThinkingPauseSheet.show(
|
||||
context,
|
||||
lesson: lesson,
|
||||
subject: const SubjectModel(
|
||||
id: 'math_10',
|
||||
title: 'الرياضيات',
|
||||
englishTitle: 'Mathematics',
|
||||
iconCode: 'function',
|
||||
primaryColor: Colors.blue,
|
||||
secondaryColor: Colors.cyan,
|
||||
),
|
||||
onCompleted: () => completed = true,
|
||||
);
|
||||
},
|
||||
child: const Text('افتح وقفة التفكير'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Open sheet
|
||||
await tester.tap(find.text('افتح وقفة التفكير'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Check thinking phase elements
|
||||
expect(find.text('وقفة تفكير وبناء الحل خطوة بخطوة ⏱️'), findsOneWidget);
|
||||
expect(find.text('وقفة تأمل ذهني مستقل (60 ثانية) 🧠'), findsOneWidget);
|
||||
expect(find.text('أنا جاهز، ابدأ بناء الحل خطوة بخطوة 🚀'), findsOneWidget);
|
||||
|
||||
// Tap ready button to skip countdown and enter step-by-step unmasking
|
||||
await tester.tap(find.text('أنا جاهز، ابدأ بناء الحل خطوة بخطوة 🚀'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Step 1 should be revealed
|
||||
expect(find.text('بناء الحل التراكمي خطوة بخطوة 📐'), findsOneWidget);
|
||||
expect(find.text('الخطوة 1 من 4'), findsOneWidget);
|
||||
expect(find.text('الخطوة 1: عزل المتغير في المعادلة الخطية'), findsOneWidget);
|
||||
|
||||
// Reveal Step 2
|
||||
expect(find.text('اكشف الخطوة التالية (2 من 4) ⬇️'), findsOneWidget);
|
||||
await tester.tap(find.text('اكشف الخطوة التالية (2 من 4) ⬇️'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('الخطوة 2: التعويض في المعادلة التربيعية وتصفيرها'), findsOneWidget);
|
||||
|
||||
// Reveal Step 3
|
||||
expect(find.text('اكشف الخطوة التالية (3 من 4) ⬇️'), findsOneWidget);
|
||||
await tester.tap(find.text('اكشف الخطوة التالية (3 من 4) ⬇️'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('الخطوة 3: حساب المميز والتحليل إلى العوامل'), findsOneWidget);
|
||||
|
||||
// Reveal Step 4
|
||||
expect(find.text('اكشف الخطوة التالية (4 من 4) ⬇️'), findsOneWidget);
|
||||
await tester.tap(find.text('اكشف الخطوة التالية (4 من 4) ⬇️'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('الخطوة 4: إيجاد الأزواج المرتبة والتحقق البياني'), findsOneWidget);
|
||||
|
||||
// Completion button
|
||||
expect(find.text('اكتمل بناء الحل المنهجي بنجاح! استمر 🎯'), findsOneWidget);
|
||||
expect(completed, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:student_app/data/repositories/curriculum_question_bank.dart';
|
||||
import 'package:student_app/presentation/screens/curriculum/lesson_homework_sheet.dart';
|
||||
import 'package:student_app/presentation/screens/exams/adaptive_exam_screen.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('CurriculumQuestionBank Subject Isolation & Decoupling', () {
|
||||
test('Islamic Studies question bank contains ZERO math equations', () {
|
||||
final exam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: 'islamic_10',
|
||||
unitKey: 'unit_01',
|
||||
);
|
||||
|
||||
expect(exam.title, contains('القرآن الكريم'));
|
||||
expect(exam.questions.isNotEmpty, isTrue);
|
||||
|
||||
for (final q in exam.questions) {
|
||||
// Assert zero math contamination
|
||||
expect(q.questionText.contains('x²'), isFalse, reason: 'Math leaked into Islamic exam: ${q.questionText}');
|
||||
expect(q.questionText.contains('معادلة'), isFalse, reason: 'Math leaked into Islamic exam: ${q.questionText}');
|
||||
expect(q.questionText.contains('مشتق'), isFalse, reason: 'Calculus leaked into Islamic exam: ${q.questionText}');
|
||||
// Assert Islamic topic relevance
|
||||
expect(
|
||||
q.topicTag.contains('قرآن') ||
|
||||
q.topicTag.contains('بيع') ||
|
||||
q.topicTag.contains('معاملات') ||
|
||||
q.topicTag.contains('فقه') ||
|
||||
q.topicTag.contains('سيرة') ||
|
||||
q.topicTag.contains('إسلام') ||
|
||||
q.topicTag.contains('حديث') ||
|
||||
q.topicTag.contains('آداب'),
|
||||
isTrue,
|
||||
reason: 'Non-Islamic topic tag in Islamic exam: ${q.topicTag}',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('Physics question bank contains authentic physics concepts', () {
|
||||
final examU1 = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: 'physics_10',
|
||||
unitKey: 'unit_01',
|
||||
);
|
||||
expect(examU1.title, contains('المتجهات'));
|
||||
expect(examU1.questions.any((q) => q.questionText.contains('متجه')), isTrue);
|
||||
|
||||
final examU2 = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: 'physics_10',
|
||||
unitKey: 'unit_02',
|
||||
);
|
||||
expect(examU2.title, contains('المقذوفات'));
|
||||
expect(examU2.questions.any((q) => q.questionText.contains('أقصى ارتفاع')), isTrue);
|
||||
});
|
||||
|
||||
test('Arabic question bank contains authentic grammar and literature', () {
|
||||
final exam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: 'arabic_10',
|
||||
unitKey: 'unit_01',
|
||||
);
|
||||
expect(exam.title, contains('الشرط'));
|
||||
expect(exam.questions.any((q) => q.topicTag.contains('الشرط')), isTrue);
|
||||
});
|
||||
|
||||
test('Lesson homework returns 3 focused questions with explanations', () {
|
||||
final hw = CurriculumQuestionBank.getLessonHomework(
|
||||
subjectId: 'islamic_10',
|
||||
lessonId: 'lesson_01',
|
||||
lessonTitle: 'واجب المسلم تجاه القرآن الكريم',
|
||||
);
|
||||
|
||||
expect(hw.questions.length, greaterThanOrEqualTo(3));
|
||||
for (final q in hw.questions) {
|
||||
expect(q.questionText.isNotEmpty, isTrue);
|
||||
expect(q.options.length, 4);
|
||||
expect(q.explanation.isNotEmpty, isTrue);
|
||||
expect(q.correctIndex >= 0 && q.correctIndex < 4, isTrue);
|
||||
}
|
||||
});
|
||||
|
||||
test('Subject worksheets are populated and isolated per subject', () {
|
||||
final islamicSheets = CurriculumQuestionBank.getSubjectWorksheets(
|
||||
subjectId: 'islamic_10',
|
||||
subjectTitle: 'التربية الإسلامية',
|
||||
);
|
||||
expect(islamicSheets.isNotEmpty, isTrue);
|
||||
expect(islamicSheets.any((s) => s.title.contains('القرآن')), isTrue);
|
||||
|
||||
final physicsSheets = CurriculumQuestionBank.getSubjectWorksheets(
|
||||
subjectId: 'physics_10',
|
||||
subjectTitle: 'الفيزياء',
|
||||
);
|
||||
expect(physicsSheets.isNotEmpty, isTrue);
|
||||
expect(physicsSheets.any((s) => s.title.contains('المتجهات')), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('LessonHomeworkSheet UI Test', () {
|
||||
testWidgets('renders homework questions and verifies answer', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: LessonHomeworkSheet(
|
||||
subjectId: 'islamic_10',
|
||||
subjectTitle: 'التربية الإسلامية',
|
||||
lessonId: 'lesson_01',
|
||||
lessonTitle: 'واجب المسلم تجاه القرآن الكريم',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('واجب الدرس'), findsOneWidget);
|
||||
expect(find.textContaining('التمرين 1'), findsOneWidget);
|
||||
expect(find.text('تحقق من الإجابة 🚀'), findsOneWidget);
|
||||
|
||||
// Tap first option
|
||||
await tester.tap(find.textContaining('استحباب تلاوة القرآن'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap check answer
|
||||
await tester.tap(find.text('تحقق من الإجابة 🚀'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Check explanation shows up
|
||||
expect(find.textContaining('الشرح التوضيحي'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('AdaptiveExamScreen Subject Decoupling UI Test', () {
|
||||
testWidgets('opening Islamic exam does NOT show math equations', (tester) async {
|
||||
final initialExam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: 'islamic_10',
|
||||
unitKey: 'unit_01',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: AdaptiveExamScreen(
|
||||
examId: 9999,
|
||||
title: 'اختبار الفهم التكيفي: القرآن الكريم',
|
||||
subjectTitle: 'التربية الإسلامية',
|
||||
subjectCode: 'islamic_10',
|
||||
unitKey: 'unit_01',
|
||||
initialExam: initialExam,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
// Verify header
|
||||
expect(find.text('التربية الإسلامية'), findsOneWidget);
|
||||
expect(find.text('اختبار الفهم التكيفي: القرآن الكريم'), findsOneWidget);
|
||||
|
||||
// Verify that math equations never appear
|
||||
expect(find.textContaining('x³'), findsNothing);
|
||||
expect(find.textContaining('معادلة تربيعية'), findsNothing);
|
||||
|
||||
// Unmount to cancel BlocProvider and ExamCubit countdown timer cleanly
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -7,19 +7,19 @@ import 'package:student_app/presentation/screens/virtual_labs/labs_registry.dart
|
||||
import 'package:student_app/presentation/screens/virtual_labs/subject_virtual_labs_view.dart';
|
||||
|
||||
void main() {
|
||||
test('registry covers all Grade-10 labs (44 entries, 14 subjects)', () {
|
||||
expect(Grade10LabsRegistry.all.length, 44);
|
||||
test('registry covers all Grade-10 labs (48 entries, 14 subjects)', () {
|
||||
expect(Grade10LabsRegistry.all.length, 48);
|
||||
expect(Grade10LabsRegistry.subjects.length, 14);
|
||||
final ids = Grade10LabsRegistry.all.map((e) => e.id).toSet();
|
||||
expect(ids.length, 44); // stable unique ids
|
||||
expect(ids.length, 48); // stable unique ids
|
||||
});
|
||||
|
||||
test('bound vs authoring-tool split: 28 lesson-bound, 16 standalone tools',
|
||||
test('bound vs authoring-tool split: 34 lesson-bound, 14 standalone tools',
|
||||
() {
|
||||
final bound = Grade10LabsRegistry.boundEntries;
|
||||
final tools = Grade10LabsRegistry.authoringTools;
|
||||
expect(bound.length, 28);
|
||||
expect(tools.length, 16);
|
||||
expect(bound.length, 34);
|
||||
expect(tools.length, 14);
|
||||
for (final e in bound) {
|
||||
expect(e.identity.isBound, isTrue, reason: '${e.id} should be bound');
|
||||
expect(e.isPublished, isFalse,
|
||||
@@ -50,13 +50,19 @@ void main() {
|
||||
expect(vectors!.identity.curriculumLessonId,
|
||||
'physics_10_semester_1_unit_01_lesson_02');
|
||||
|
||||
final vectorsIntro = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'physics_10_semester_1_unit_01_lesson_01');
|
||||
expect(vectorsIntro, isNotNull);
|
||||
expect(vectorsIntro!.identity.curriculumLessonId,
|
||||
'physics_10_semester_1_unit_01_lesson_01');
|
||||
|
||||
// A same-title keyword/lesson_01 guess must NOT resolve.
|
||||
expect(Grade10LabsRegistry.entryForCurriculumLessonId('lesson_01'), isNull);
|
||||
expect(
|
||||
Grade10LabsRegistry.entryForCurriculumLessonId('physics_10'), isNull);
|
||||
expect(
|
||||
Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'physics_10_semester_1_unit_01_lesson_01'),
|
||||
'physics_10_semester_1_unit_01_lesson_99'),
|
||||
isNull);
|
||||
|
||||
// Unbound authoring tools never resolve through lesson lookup.
|
||||
@@ -150,7 +156,7 @@ void main() {
|
||||
'physics_10_semester_1_unit_01_lesson_02', // vectors (bound)
|
||||
'math_10_semester_1_unit_03_lesson_02', // unit circle (bound)
|
||||
'finance_budget', // budget (tool)
|
||||
'islamic_inheritance', // inheritance (tool, no curriculum anchor)
|
||||
'islamic_10_semester_1_unit_01_lesson_02', // inheritance (bound)
|
||||
]) {
|
||||
final entry = Grade10LabsRegistry.all.firstWhere((e) => e.id == id);
|
||||
await t
|
||||
@@ -654,4 +660,139 @@ void main() {
|
||||
}
|
||||
expect(find.textContaining('توظيفُ النداءِ سليمٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arabic unit2 insha lab: concept checklist solves activity', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_06')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
// Concept toggles: 0,1,2,3,4,5 are true, 6,7 are false
|
||||
for (var i = 0; i < 6; i++) {
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('مفهومُ الإنشاءِ الطّلبيِّ مثبَّتٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arabic unit2 insha lab: types selection solves activity', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_06')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('أنواعُ الإنشاءِ'));
|
||||
await t.pump();
|
||||
expect(find.text('النداءُ'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'physics vectors intro lab: crosswind landing and tab switching render correctly',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'physics_10_semester_1_unit_01_lesson_01')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
|
||||
// Verify Tab 0: Crosswind Landing
|
||||
expect(find.text('الكميات القياسية والمتجهة وتمثيلها'), findsOneWidget);
|
||||
expect(find.textContaining('السرعة الأرضية |Vg|'), findsOneWidget);
|
||||
expect(find.text('تنفيذ هبوط تجريبي على المدرج 🛬'), findsOneWidget);
|
||||
|
||||
// Tap test landing
|
||||
await t.tap(find.text('تنفيذ هبوط تجريبي على المدرج 🛬'));
|
||||
await t.pump(const Duration(milliseconds: 200));
|
||||
expect(find.text('جاري الهبوط التجريبي...'), findsOneWidget);
|
||||
|
||||
// Switch to Tab 1: Vector properties & scalar multiple
|
||||
await t.tap(find.text('تمثيل وسالب المتجه'));
|
||||
await t.pump();
|
||||
expect(find.textContaining('سالب المتجه'), findsWidgets);
|
||||
expect(find.text('المقدار |A|'), findsOneWidget);
|
||||
|
||||
// Switch to Tab 2: Dot and cross product
|
||||
await t.tap(find.text('الضرب النقطي والتقاطعي'));
|
||||
await t.pump();
|
||||
expect(find.textContaining('الضرب القياسي'), findsWidgets);
|
||||
expect(find.textContaining('الضرب المتجهي'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'physics projectile motion lab: launch, presets and vectors render correctly',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'physics_10_semester_1_unit_02_lesson_02')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
|
||||
// Verify main components render
|
||||
expect(find.text('حركة المقذوفات في بُعدين'), findsOneWidget);
|
||||
expect(find.textContaining('المدى R ='), findsOneWidget);
|
||||
expect(find.text('إطلاق القذيفة 🚀'), findsOneWidget);
|
||||
|
||||
// Tap angle preset 30°
|
||||
await t.tap(find.text('30°'));
|
||||
await t.pump();
|
||||
expect(find.text('30°'), findsWidgets);
|
||||
|
||||
// Launch projectile
|
||||
await t.tap(find.text('إطلاق القذيفة 🚀'));
|
||||
await t.pump(const Duration(milliseconds: 100));
|
||||
expect(find.text('إيقاف مؤقت'), findsOneWidget);
|
||||
|
||||
// Reset simulation
|
||||
await t.tap(find.text('إعادة'));
|
||||
await t.pump();
|
||||
expect(find.text('إطلاق القذيفة 🚀'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'earth air masses lab: front switching and checkpoint trigger render correctly',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'earth_sciences_10_semester_2_unit_03_lesson_01')!;
|
||||
var checkpointTriggered = false;
|
||||
await t.pumpWidget(MaterialApp(
|
||||
home: Scaffold(body: entry.builder((q, opts, idx) {
|
||||
checkpointTriggered = true;
|
||||
}))));
|
||||
await t.pump();
|
||||
|
||||
// Verify main title and front options
|
||||
expect(find.textContaining('الكتل والجبهات الهوائية'), findsWidgets);
|
||||
expect(find.text('جبهة باردة ❄️'), findsWidgets);
|
||||
expect(find.text('جبهة دافئة ☀️'), findsWidgets);
|
||||
expect(find.text('مستقرة ⏸️'), findsWidgets);
|
||||
expect(find.text('مقفلة 🌀'), findsWidgets);
|
||||
|
||||
// Tap warm front tab
|
||||
await t.tap(find.text('جبهة دافئة ☀️').first);
|
||||
await t.pump();
|
||||
|
||||
// Tap occluded front tab
|
||||
await t.tap(find.text('مقفلة 🌀').first);
|
||||
await t.pump();
|
||||
|
||||
// Tap air mass chip cT
|
||||
await t.tap(find.text('cT قارية مدارية').first);
|
||||
await t.pump();
|
||||
expect(find.textContaining('مدارية'), findsWidgets);
|
||||
|
||||
// Tap checkpoint trigger button
|
||||
await t.tap(find.text('فحص الفهم والاستيعاب').first);
|
||||
await t.pump();
|
||||
expect(checkpointTriggered, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:student_app/presentation/screens/vocational/vocational_training_screen.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('VocationalTrainingScreen EV lab: renders chassis blueprint, isolated component canvas, and toggles MSD', (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(900, 3000));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: VocationalTrainingScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
// 1. Verify main header and directory tab
|
||||
expect(find.textContaining('مؤسسة التدريب المهني'), findsOneWidget);
|
||||
expect(find.textContaining('دليل الـ 140 مهنة'), findsOneWidget);
|
||||
expect(find.textContaining('مختبر فحص المركبات الكهربائية'), findsOneWidget);
|
||||
|
||||
// 2. Switch to EV Diagnostic Lab tab
|
||||
await tester.tap(find.textContaining('مختبر فحص المركبات الكهربائية'));
|
||||
await tester.pump();
|
||||
|
||||
// Verify car blueprint and telemetry
|
||||
expect(find.text('مخطط هيكل السيارة الكهربائية وتنظيم القطاعات'), findsOneWidget);
|
||||
expect(find.text('الشحن (SoC)'), findsOneWidget);
|
||||
expect(find.text('جهد الكابلات'), findsOneWidget);
|
||||
expect(find.text('حلقة القفل HVIL'), findsOneWidget);
|
||||
|
||||
// Verify Multimeter display
|
||||
expect(find.textContaining('جهاز الفحص الرقمي'), findsOneWidget);
|
||||
expect(find.text('384.0'), findsOneWidget);
|
||||
expect(find.text('V DC'), findsWidgets);
|
||||
|
||||
// Verify Component Anatomy Card with 3 tabs and canvas
|
||||
expect(find.text('تشريح القطاع والرسم 🔬'), findsOneWidget);
|
||||
expect(find.text('طريقة الربط والشبك 🔗'), findsOneWidget);
|
||||
expect(find.text('الفحص وقراءة العداد 📟'), findsOneWidget);
|
||||
expect(find.text('المجسات متصلة بالقطعة'), findsOneWidget);
|
||||
|
||||
// 3. Toggle probes placement
|
||||
await tester.tap(find.text('المجسات متصلة بالقطعة'));
|
||||
await tester.pump();
|
||||
expect(find.text('المجسات مرفوعة'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('المجسات مرفوعة'));
|
||||
await tester.pump();
|
||||
expect(find.text('المجسات متصلة بالقطعة'), findsOneWidget);
|
||||
|
||||
// 4. Test detail tabs
|
||||
// Switch to 'طريقة الربط والشبك 🔗'
|
||||
await tester.tap(find.text('طريقة الربط والشبك 🔗'));
|
||||
await tester.pump();
|
||||
expect(find.textContaining('مسار التوصيل والشبك'), findsOneWidget);
|
||||
|
||||
// Switch to 'الفحص وقراءة العداد 📟'
|
||||
await tester.tap(find.text('الفحص وقراءة العداد 📟'));
|
||||
await tester.pump();
|
||||
expect(find.textContaining('طريقة الفحص بالملتيميتر'), findsOneWidget);
|
||||
|
||||
// 5. Test MSD Service Plug disconnect (Zero voltage verification)
|
||||
expect(find.textContaining('نزع قابس الأمان يدويّاً'), findsOneWidget);
|
||||
await tester.tap(find.textContaining('نزع قابس الأمان يدويّاً'));
|
||||
await tester.pump();
|
||||
|
||||
// After pulling MSD, pack voltage drops to 0.00 V
|
||||
expect(find.text('0.00'), findsWidgets);
|
||||
expect(find.textContaining('خلو الجهد مؤكد'), findsOneWidget);
|
||||
|
||||
// 6. Select another component: Inverter & Motor
|
||||
await tester.tap(find.textContaining('محول القدرة'));
|
||||
await tester.pump();
|
||||
|
||||
// Verify multimeter and anatomy update for Inverter
|
||||
expect(find.text('0.18'), findsOneWidget);
|
||||
expect(find.text('Ω'), findsWidgets);
|
||||
expect(find.text('مقاومة ملفات المحرك Ω'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user