Files
saqel/apps/student_app/test/virtual_labs_smoke_test.dart
T

799 lines
34 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, 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);
});
}