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
@@ -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);
});
}