830 lines
35 KiB
Dart
830 lines
35 KiB
Dart
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, 13 subjects)', () {
|
|
expect(Grade10LabsRegistry.all.length, 48);
|
|
expect(Grade10LabsRegistry.subjects.length, 13);
|
|
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('findForLesson strictly enforces subject isolation (no physics leak to History/Digital)', () {
|
|
Grade10LabsRegistry.previewModeEnabled = true;
|
|
|
|
// History lesson 2 must NEVER return physics vector lab
|
|
final historyLab = Grade10LabsRegistry.findForLesson(
|
|
subjectTitle: 'تاريخ الأردن (Jordan History 10)',
|
|
lessonTitle: 'الدرس الثاني: الإمبراطورية الساسانية',
|
|
lessonId: 'lesson_02',
|
|
);
|
|
expect(historyLab, isNull);
|
|
|
|
// Digital skills lesson 1 must NEVER return physics vectors intro lab
|
|
final digitalLab = Grade10LabsRegistry.findForLesson(
|
|
subjectTitle: 'المهارات الرقمية (Digital Skills 10)',
|
|
lessonTitle: 'معالجة البيانات',
|
|
lessonId: 'lesson_01',
|
|
);
|
|
expect(digitalLab, isNull);
|
|
|
|
// History lesson 1 MUST return Persian Empire lab, NOT physics!
|
|
final historyUnit1Lab = Grade10LabsRegistry.findForLesson(
|
|
subjectTitle: 'تاريخ الأردن (Jordan History 10)',
|
|
lessonTitle: 'الدرس الأول: الإمبراطورية الفارسية: النشأة والتطور',
|
|
lessonId: 'lesson_01',
|
|
curriculumLessonId: 'history_10_semester_1_unit_01_lesson_01',
|
|
);
|
|
expect(historyUnit1Lab, isNotNull);
|
|
expect(historyUnit1Lab?.identity.curriculumLessonId, 'history_10_semester_1_unit_01_lesson_01');
|
|
expect(historyUnit1Lab?.subjectAr, 'التاريخ');
|
|
});
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
|