import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:gametime/application/application.dart'; import 'package:gametime/domain/domain.dart'; import 'package:gametime/presentation/presentation.dart'; void main() { testWidgets('la validation au moins une mesure active bloque la sauvegarde', ( tester, ) async { final exerciseRepository = _FakeExerciseRepository(); final exerciseUseCases = _exerciseUseCases(exerciseRepository); await tester.pumpWidget( MaterialApp( home: ExerciseFormScreen( exerciseUseCases: exerciseUseCases, mediaUseCases: _mediaUseCases(exerciseRepository), ), ), ); await tester.enterText(find.byType(TextFormField).first, 'Gainage'); await tester.tap(find.widgetWithText(SwitchListTile, 'Temps')); await tester.pump(); await tester.dragUntilVisible( find.text('Enregistrer'), find.byType(ListView), const Offset(0, -200), ); await tester.pumpAndSettle(); await tester.tap(find.text('Enregistrer')); await tester.pump(); expect( find.text('Active au moins une mesure pour enregistrer.'), findsOneWidget, ); expect(exerciseRepository.saved, isEmpty); }); testWidgets('la liste affiche les badges des mesures disponibles', ( tester, ) async { final exercise = Exercise( metadata: _metadata('exercise-1'), name: 'Squat', description: 'Jambes · charge libre', hasTimeMeasure: true, hasRepsMeasure: true, hasScoreMeasure: true, scoreLabel: 'Charge', scoreUnit: 'kg', ); await tester.pumpWidget( MaterialApp( home: Scaffold(body: ExerciseListTile(exercise: exercise)), ), ); expect(find.text('Squat'), findsOneWidget); expect(find.text('Jambes · charge libre'), findsOneWidget); expect(find.text('Temps'), findsOneWidget); expect(find.text('Répétitions'), findsOneWidget); expect(find.text('Score (kg)'), findsOneWidget); }); testWidgets( 'sélectionner une image déclenche l’import et confirme le fichier', (tester) async { final exerciseRepository = _FakeExerciseRepository(); final storage = _FakeMediaStorage(); await tester.pumpWidget( MaterialApp( home: ExerciseFormScreen( exerciseUseCases: _exerciseUseCases(exerciseRepository), mediaUseCases: _mediaUseCases(exerciseRepository, storage: storage), mediaPicker: const _FakeMediaPicker( imagePath: '/tmp/imports/photo.png', ), ), ), ); await tester.tap(find.text('Choisir une image')); await tester.pump(); expect(storage.imports.single.sourcePath, '/tmp/imports/photo.png'); expect(storage.imports.single.kind, MediaKind.image); expect(find.text('photo.png'), findsOneWidget); expect(find.text('Image importée.'), findsOneWidget); }, ); } ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) { return ExerciseUseCases( repository: repository, clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ); } MediaUseCases _mediaUseCases( _FakeExerciseRepository exerciseRepository, { _FakeMediaStorage? storage, }) { return MediaUseCases( mediaRepository: _FakeMediaAssetRepository(), exerciseRepository: exerciseRepository, storage: storage ?? _FakeMediaStorage(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ); } final class _FakeMediaPicker implements MediaSourcePicker { const _FakeMediaPicker({this.imagePath, this.videoPath}); final String? imagePath; final String? videoPath; @override Future pickPath(MediaKind kind) async { return switch (kind) { MediaKind.image => imagePath, MediaKind.video => videoPath, }; } } EntityMetadata _metadata(String id) { return EntityMetadata( id: id, createdAt: DateTime.utc(2026, 7, 17), updatedAt: DateTime.utc(2026, 7, 17), originDeviceId: 'device-1', ); } final class _FakeClock implements Clock { const _FakeClock(this.value); final DateTime value; @override DateTime now() => value; } final class _FakeIds implements IdGenerator { var _next = 0; @override String newId() { _next += 1; return 'id-$_next'; } } final class _FakeExerciseRepository implements ExerciseRepository { final exercises = []; final saved = []; var referenced = false; @override Future findById(String id) async { for (final exercise in exercises) { if (exercise.metadata.id == id) { return exercise; } } return null; } @override Future isReferencedByProgram(String id) async => referenced; @override Future> listActive() async => exercises; @override Future save(Exercise exercise) async { saved.add(exercise); exercises.removeWhere((item) => item.metadata.id == exercise.metadata.id); exercises.add(exercise); } } final class _FakeMediaAssetRepository implements MediaAssetRepository { final saved = []; @override Future findById(String id) async { for (final asset in saved) { if (asset.metadata.id == id) { return asset; } } return null; } @override Future> listActive() async => saved; @override Future save(MediaAsset mediaAsset) async { saved.add(mediaAsset); } } final class _FakeMediaStorage implements LocalMediaStorage { final imports = <_MediaImportCall>[]; @override Future deleteByLocalUri(String localUri) async {} @override Future importFile({ required String sourcePath, required MediaKind kind, required String stableFileName, }) async { imports.add( _MediaImportCall( sourcePath: sourcePath, kind: kind, stableFileName: stableFileName, ), ); return StoredMediaFile( localUri: 'file:///tmp/$stableFileName', mimeType: kind == MediaKind.image ? 'image/png' : 'video/mp4', sizeBytes: 1, ); } @override Future> listManagedLocalUris() async => const {}; } final class _MediaImportCall { const _MediaImportCall({ required this.sourcePath, required this.kind, required this.stableFileName, }); final String sourcePath; final MediaKind kind; final String stableFileName; }