Update Saqel Platform: 2026-09-12 13:38:49

This commit is contained in:
Hamza-Ayed
2026-09-12 13:38:49 +03:00
parent 1f31369893
commit f4d4f02853
34 changed files with 9136 additions and 386 deletions
@@ -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);
});
}