import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:student_app/presentation/screens/virtual_labs/lab_identity.dart'; import 'package:student_app/presentation/screens/virtual_labs/labs_gallery_screen.dart'; 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 (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, 48); // stable unique ids }); test('bound vs authoring-tool split: 34 lesson-bound, 14 standalone tools', () { final bound = Grade10LabsRegistry.boundEntries; final tools = Grade10LabsRegistry.authoringTools; 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, reason: 'no lab may be published before server bundle version'); } for (final e in tools) { expect(e.identity.curriculumLessonId, isEmpty, reason: '${e.id} must not masquerade as a lesson lab'); } }); test('every bound identity carries an explicit, well-formed curriculum id', () { final pattern = RegExp(r'^[a-z_]+_10_semester_[12]_unit_\d{2}_lesson_\d{2}$' r'|^[a-z_]+_10_semester_[12]_unit_\d{2}_unit_enrichment_and_review$'); for (final e in Grade10LabsRegistry.boundEntries) { expect(pattern.hasMatch(e.identity.curriculumLessonId), isTrue, reason: 'malformed lesson id: ${e.identity.curriculumLessonId}'); expect(e.identity.curriculumVersion, 'jordan-grade10-2026-source-review'); } }); test('entryForCurriculumLessonId matches ONLY exact curriculum ids', () { // Exact bound id resolves (draft, not published). final vectors = Grade10LabsRegistry.entryForCurriculumLessonId( 'physics_10_semester_1_unit_01_lesson_02'); expect(vectors, isNotNull); 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_99'), isNull); // Unbound authoring tools never resolve through lesson lookup. for (final e in Grade10LabsRegistry.authoringTools) { expect(Grade10LabsRegistry.entryForCurriculumLessonId(e.id), isNull); } }); test('findForLesson requires publication — nothing published yet', () { Grade10LabsRegistry.previewModeEnabled = false; for (final e in Grade10LabsRegistry.boundEntries) { final found = Grade10LabsRegistry.findForLesson( subjectTitle: e.subjectAr, lessonTitle: e.lessonAr, curriculumLessonId: e.identity.curriculumLessonId, ); expect(found, isNull, reason: 'draft labs must never surface to students in production: ${e.id}'); } Grade10LabsRegistry.previewModeEnabled = true; }); test('findForLesson surfaces labs in previewMode for local development', () { Grade10LabsRegistry.previewModeEnabled = true; final found = Grade10LabsRegistry.findForLesson( subjectTitle: 'اللغة العربية', lessonTitle: 'أي عنوان', curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_01', ); expect(found, isNotNull); expect(found?.identity.curriculumLessonId, 'arabic_10_semester_1_unit_01_lesson_01'); }); test('a lab is published only when released AND bundle version provided', () { // Emulate a server bundle arriving only for released bound lesson. const released = LabIdentity( curriculumLessonId: 'math_10_semester_1_unit_01_lesson_02', subjectAr: 'الرياضيات', lessonAr: 'حل أنظمة خطية وتربيعية', reviewStatus: 'released', publishedBundleVersion: 'pkg-2026.1', ); expect(released.isBound, isTrue); expect(released.isReleased, isTrue); expect(released.isPublished, isTrue); // Unbound tools cannot be published at all (no lesson anchor). const tool = LabIdentity(toolKey: 'x_tool', subjectAr: 'غير ذلك', lessonAr: 'أداة'); expect(tool.isPublished, isFalse); }); testWidgets('gallery renders the authoring-preview surface', (t) async { await t.pumpWidget( const MaterialApp(home: Scaffold(body: VirtualLabsGalleryScreen()))); await t.pumpAndSettle(); expect(find.text('المختبرات الافتراضية — الصف العاشر'), findsOneWidget); expect(find.text('جمع المتجهات وتحليلها'), findsOneWidget); // Standalone tools are listed as authoring tools (registry-level proof, // lazy list keeps far tiles unbuilt). expect( Grade10LabsRegistry.authoringTools .any((e) => e.lessonAr == 'اختبار اللهب'), isTrue, ); }); testWidgets( 'subject view shows honest unpublished state when preview is false', (t) async { Grade10LabsRegistry.previewModeEnabled = false; await t.pumpWidget(const MaterialApp( home: Scaffold( body: SubjectVirtualLabsView( subjectTitle: 'الفيزياء', primaryColor: Colors.cyan, ), ), )); await t.pump(); expect(find.text('تجارب ومختبرات الفيزياء'), findsNothing); expect(find.text('لا مختبرات منشورة لمبحث الفيزياء بعد'), findsOneWidget); expect(find.text('معرض المعاينة التأليفية'), findsOneWidget); Grade10LabsRegistry.previewModeEnabled = true; }); testWidgets('sample labs build on the shared 2/3-1/3 scaffold', (t) async { for (final id in [ '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_10_semester_1_unit_01_lesson_02', // inheritance (bound) ]) { final entry = Grade10LabsRegistry.all.firstWhere((e) => e.id == id); await t .pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('لوحة التحكم والضبط'), findsOneWidget); expect(find.text('فحص الفهم والاستيعاب', skipOffstage: false), findsOneWidget); } }); testWidgets('arabic listening lab: etiquettes toggle unlock readiness', (t) async { final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_01')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر الانتباه والتركيز'), findsOneWidget); for (var i = 0; i < 4; i++) { await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } expect(find.text('جاهزٌ للاستماعِ الواعي ✓'), findsOneWidget); }); testWidgets('arabic listening lab: ordering trial stages solves activity', (t) async { final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_01')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('مراحل الابتلاء')); await t.pump(); for (final letter in ['د', 'أ', 'ج', 'ب']) { await t.tap(find.text(letter)); await t.pump(); } expect(find.textContaining('أحسنتَ ترتيبَ المراحل'), findsOneWidget); }); testWidgets('arabic apology lab: speech-building order solves activity', (t) async { final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_02')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر فنّ الاعتذار'), findsOneWidget); for (final letter in ['أ', 'ب', 'ج']) { await t.tap(find.text(letter)); await t.pump(); } expect(find.textContaining('أحسنتَ بناءَ حديثِك'), findsOneWidget); }); testWidgets('arabic apology lab: tone affects listener reaction', (t) async { final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_02')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('نبرة الصوت')); await t.pump(); await t.tap(find.text('هادئٌ صادقٌ')); await t.pump(); expect(find.textContaining('بذلُ السلامِ وحُسنُ الكلام'), findsWidgets); }); testWidgets('arabic quranic lab: surah-apology matching lights pedestals', (t) async { await t.binding.setSurfaceSize(const Size(900, 2200)); addTearDown(() => t.binding.setSurfaceSize(null)); final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_03')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر القراءة الفاهمة'), findsOneWidget); // حَلّ كل سورة باختيار الخيار الصحيح (الأول دائماً). final labels = [ 'الإقرارُ بالخطأ وطلبُ المغفرة (﴿لا تثريبَ عليكم﴾)', 'إسفٌ شديدٌ والمسارعةُ إلى الرشادِ بعدَ ذكرِ العجل', 'الاعترافُ بالنسيانِ وآدابُ التعلّمِ والخُلُقُ الرفيع', ]; for (var i = 0; i < 3; i++) { await t.tap(find.text(labels[i])); await t.pump(); } expect(find.textContaining('صحيح: لكلِّ قصةٍ وجهٌ'), findsOneWidget); }); testWidgets('arabic quranic lab: critical silent-reading behaviours', (t) async { await t.binding.setSurfaceSize(const Size(900, 2200)); addTearDown(() => t.binding.setSurfaceSize(null)); final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_03')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('القراءةُ الصامتة')); await t.pump(); // السلوكان الصحيحان ON والخطآن يبقيان OFF في الحالة الابتدائية. await t.tap(find.byType(CupertinoSwitch).at(0)); await t.tap(find.byType(CupertinoSwitch).at(1)); await t.pump(); expect(find.text('قراءةٌ صامتةٌ صحيحة: العينُ والعقلُ بلا همسٍ ولا صوت ✓'), findsOneWidget); }); testWidgets('arabic letter lab: elements fill the letter sheet', (t) async { await t.binding.setSurfaceSize(const Size(900, 2600)); addTearDown(() => t.binding.setSurfaceSize(null)); final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_04')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر رسالة الاعتذار'), findsOneWidget); final labels = [ 'عنصرُ المستلِم', 'عنصرُ الموضوع', 'عنصرُ التحيةِ والسلامِ', 'عنصرُ المقدمة', 'عنصرُ صلبِ الرسالةِ: الاعتذار', 'عنصرُ الخاتمةِ والتوقيع', ]; for (var i = 0; i < 6; i++) { await t.tap(find.text(labels[i])); await t.pump(); } expect(find.textContaining('رسالتُك اكتملت'), findsOneWidget); }); testWidgets('arabic letter lab: ordering letter paragraphs 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_01_lesson_04')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('ترتيبُ الرسالة')); await t.pump(); for (final letter in ['أ', 'ب', 'ج', 'د']) { await t.tap(find.text(letter)); await t.pump(); } expect( find.textContaining('أتممتَ ترتيبَ رسالةِ الاعتذار'), findsOneWidget); }); testWidgets('arabic conditional lab: classify jazma tools solves bins', (t) async { await t.binding.setSurfaceSize(const Size(900, 2600)); addTearDown(() => t.binding.setSurfaceSize(null)); final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_05')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر أسلوب الشرط'), findsOneWidget); // The 5 jazma tools pick «جازمة» (rows 0-4). for (var i = 0; i < 5; i++) { await t.tap(find.text('جازمة').at(i)); await t.pump(); } // The 3 non-jazma tools pick «غيرُ جازمة» (rows 5-7). for (var i = 5; i < 8; i++) { await t.tap(find.text('غيرُ جازمة').at(i)); await t.pump(); } expect(find.textContaining('صنَّفْتَ أدواتِ الشرطِ'), findsOneWidget); }); testWidgets('arabic conditional lab: fill iram model 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_01_lesson_05')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('أتمَّ الإعراب')); await t.pump(); const row1 = 'فعلٌ مضارعٌ مجزومٌ، وعلامةُ جزمِه حذفُ حرفِ العلةِ من آخرِه'; expect(find.text(row1), findsOneWidget); await t.tap(find.text(row1)); await t.pump(); await t.tap(find.text('ضميرٌ مستترٌ تقديرُه (أنت)')); await t.pump(); await t.tap( find.text('ضميرٌ متصلٌّ مبنيٌّ على الكسرِ في محلِّ نصبٍ مفعولٌ به')); await t.pump(); expect(find.textContaining('أتممتَ نموذجَ إعرابِ'), findsOneWidget); }); testWidgets('arabic informative lab: classify sentences solves buckets', (t) async { await t.binding.setSurfaceSize(const Size(900, 2600)); addTearDown(() => t.binding.setSurfaceSize(null)); final entry = Grade10LabsRegistry.entryForCurriculumLessonId( 'arabic_10_semester_1_unit_01_lesson_06')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر الأسلوب الخبري'), findsOneWidget); // Rows 0,1,4,5 are خبرية; rows 2,3 are إنشائية. await t.tap(find.text('خبريّةٌ').at(0)); await t.pump(); await t.tap(find.text('خبريّةٌ').at(1)); await t.pump(); await t.tap(find.text('إنشائيّةٌ').at(2)); await t.pump(); await t.tap(find.text('إنشائيّةٌ').at(3)); await t.pump(); await t.tap(find.text('خبريّةٌ').at(4)); await t.pump(); await t.tap(find.text('خبريّةٌ').at(5)); await t.pump(); expect(find.textContaining('صنَّفْتَ الجملَ'), findsOneWidget); }); testWidgets('arabic informative lab: complete rules 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_01_lesson_06')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('أكملِ القواعدَ')); await t.pump(); await t.tap(find.text('الصدقَ والكذبَ')); await t.pump(); await t.tap(find.text('الخبريِّ والإنشائيِّ')); await t.pump(); await t.tap(find.text('الاسميةِ والفعليةِ')); await t.pump(); expect( find.textContaining('أكملتَ قواعدَ الأسلوبِ الخبريِّ'), findsOneWidget); }); testWidgets('arabic unit2 listening lab: correcting statements solves task', (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_01')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر الاستماع الواعي (قصة الضيف)'), findsOneWidget); const targets = [true, true, true, true, false]; for (var i = 0; i < targets.length; i++) { if (targets[i]) { await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } } expect(find.textContaining('عباراتُك صحيحة'), findsOneWidget); }); testWidgets('arabic unit2 listening lab: story order 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_01')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('رتِّبِ الحكاية')); await t.pump(); for (final letter in ['أ', 'ب', 'ج', 'د']) { await t.tap(find.text(letter)); await t.pump(); } expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget); }); testWidgets('arabic unit2 speaking lab: values checklist solves task', (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_02')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر العرض التقديمي (أتحدثُ بطلاقةٍ)'), findsOneWidget); const offIdx = 4; for (var i = 0; i < 6; i++) { if (i == offIdx) continue; await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } expect(find.textContaining('قيمُ الوطنيةِ معطوبةٌ'), findsOneWidget); }); testWidgets('arabic unit2 speaking lab: presentation plan 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_02')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('عرضُ التحدّثِ')); await t.pump(); // targets [0,0,0,1,0] for rows مطلوب/ليس مطلوبًا for (var i = 0; i < 5; i++) { if (i == 3) continue; await t.tap(find.text('مطلوبٌ').at(i)); await t.pump(); } await t.tap(find.text('ليسَ مطلوبًا').at(3)); await t.pump(); expect(find.textContaining('خطّةُ العرضِ سليمةٌ'), findsOneWidget); }); testWidgets( 'arabic unit2 speaking lab: quote-to-meaning solves with ' 'checkpoint', (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_02')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('القولُ والمضمونُ')); await t.pump(); // correct letters per row: أ، ب، ج، ب → indexes 0,1,2,1 await t.tap(find.text('أ').at(0)); await t.pump(); await t.tap(find.text('ب').at(1)); await t.pump(); await t.tap(find.text('ج').at(2)); await t.pump(); await t.tap(find.text('ب').at(3)); await t.pump(); expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget); }); testWidgets('arabic unit2 poetry lab: lexicon matching 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_03')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر قراءةِ شعرِ المقاومة (إلى الصامدين غرب النهر)'), findsOneWidget); // correct letters per row: ب، أ، ب، ب → indices 1,0,1,1 const taps = ['ب', 'أ', 'ب']; for (var i = 0; i < 3; i++) { await t.tap(find.text(taps[i]).at(i)); await t.pump(); } // fourth row correct is 'ب' at index 3 await t.tap(find.text('ب').at(3)); await t.pump(); expect(find.textContaining('معجمُ القصيدةِ محفوظٌ'), findsOneWidget); }); testWidgets('arabic unit2 poetry lab: poem journey ordering 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_03')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('جوُّ القصيدةِ')); await t.pump(); for (final letter in ['أ', 'ب', 'ج', 'د']) { await t.tap(find.text(letter)); await t.pump(); } expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget); }); testWidgets('arabic unit2 poetry lab: prosody 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_03')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('رَشِيقُ التفعيلةِ')); await t.pump(); // targets [T,T,F,T,T,F]; toggle switches 2 and 5 stay off for (var i = 0; i < 6; i++) { if (i == 2 || i == 5) continue; await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } expect(find.textContaining('رَشِيقُ التفعيلةِ واضحٌ'), findsOneWidget); }); testWidgets('arabic unit2 writing lab: craft 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_04')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر كتابةِ تحليلِ النصِّ الشعريِّ'), findsOneWidget); // targets [T,T,T,T,T,F,T,T]; index 5 (الورق والحبر) stays off for (var i = 0; i < 8; i++) { if (i == 5) continue; await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } expect(find.textContaining('عناصرُ الكتابةِ مثبّتةٌ'), findsOneWidget); }); testWidgets( 'arabic unit2 writing lab: criteria ordering solves with ' 'checkpoint', (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_04')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('معاييرُ التحليلِ')); await t.pump(); for (final letter in ['أ', 'ب', 'ج', 'د']) { await t.tap(find.text(letter)); await t.pump(); } expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget); }); testWidgets('arabic unit2 writing lab: analysis 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_04')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('تحليلُ الكرميِّ')); await t.pump(); // targets [T,T,T,T,T,F]; toggle switch 5 (كلام مجرد) stays off for (var i = 0; i < 6; i++) { if (i == 5) continue; await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } expect(find.textContaining('تحليلُ الكرميِّ مطابقٌ'), findsOneWidget); }); testWidgets('arabic unit2 vocative lab: components 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_05')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); expect(find.text('مختبر أسلوبِ النداءِ'), findsOneWidget); // targets [T,T,T,T,T,T,T,F]; index 7 (المفعول والعِدة) stays off for (var i = 0; i < 8; i++) { if (i == 7) continue; await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } expect(find.textContaining('مكوّناتُ النداءِ مثبّتةٌ'), findsOneWidget); }); testWidgets( 'arabic unit2 vocative lab: types matching solves with ' 'checkpoint', (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_05')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('أنواعُ المنادى')); await t.pump(); // correct letters per row: أ، ب، أ، ج، أ → indices 0,1,0,2,0 const taps = ['أ', 'ب', 'أ', 'ج', 'أ']; for (var i = 0; i < 5; i++) { await t.tap(find.text(taps[i]).at(i)); await t.pump(); } expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget); }); testWidgets( 'arabic unit2 vocative lab: situational 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_05')!; await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null)))); await t.pump(); await t.tap(find.text('توظيفُ النداءِ')); await t.pump(); // targets [T,T,T,T,F,F]; toggles 4 and 5 stay off for (var i = 0; i < 6; i++) { if (i == 4 || i == 5) continue; await t.tap(find.byType(CupertinoSwitch).at(i)); await t.pump(); } 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); }); }