From adf56ec9e01cc01590b0a454ba2583b24f2720e9 Mon Sep 17 00:00:00 2001 From: Blomios Date: Mon, 20 Jul 2026 08:10:19 +0200 Subject: [PATCH] =?UTF-8?q?fix(programme):=20=C3=A9tapes=20d'exercice=20ja?= =?UTF-8?q?mais=20affich=C3=A9es=20en=20s=C3=A9ance=20(ticket=20#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _ProgramExerciseDraft dans program_screen.dart (état interne du formulaire de composition de programme) n'avait jamais de champ exerciseStepsSnapshot depuis sa création, avant le chantier "Exercice à plusieurs étapes" (#54). Les étapes d'un exercice étaient donc systématiquement perdues à chaque composition/édition de programme via l'écran réel, peu importe l'ordre des opérations — d'où l'absence totale d'étapes affichées en séance. Ajoute le champ manquant et 2 tests de non-régression exerçant le vrai flux UI ProgramFormScreen. flutter pub get OK, dart format appliqué (aucun changement), analyze propre (mêmes infos préexistantes), 135/135 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 --- lib/presentation/program_screen.dart | 6 + test/presentation/program_screen_test.dart | 157 +++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/lib/presentation/program_screen.dart b/lib/presentation/program_screen.dart index 3ea6326..886d8fb 100644 --- a/lib/presentation/program_screen.dart +++ b/lib/presentation/program_screen.dart @@ -915,6 +915,7 @@ final class _ProgramExerciseDraft { this.exerciseImageMediaIdSnapshot, this.exerciseImageMediaIdsSnapshot = const [], this.exerciseVideoMediaIdSnapshot, + this.exerciseStepsSnapshot = const [], this.exerciseArchivedSnapshot = false, required this.availableTimeSnapshot, required this.availableRepsSnapshot, @@ -944,6 +945,7 @@ final class _ProgramExerciseDraft { exercise.iconMediaId ?? exercise.imageMediaId, exerciseImageMediaIdsSnapshot: exercise.imageMediaIds, exerciseVideoMediaIdSnapshot: exercise.videoMediaId, + exerciseStepsSnapshot: exercise.steps, exerciseArchivedSnapshot: exercise.archivedAt != null, availableTimeSnapshot: exercise.hasTimeMeasure, availableRepsSnapshot: exercise.hasRepsMeasure, @@ -975,6 +977,7 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdSnapshot: exercise.exerciseImageMediaIdSnapshot, exerciseImageMediaIdsSnapshot: exercise.exerciseImageMediaIdsSnapshot, exerciseVideoMediaIdSnapshot: exercise.exerciseVideoMediaIdSnapshot, + exerciseStepsSnapshot: exercise.exerciseStepsSnapshot, exerciseArchivedSnapshot: exercise.exerciseArchivedSnapshot, availableTimeSnapshot: exercise.availableTimeSnapshot, availableRepsSnapshot: exercise.availableRepsSnapshot, @@ -1004,6 +1007,7 @@ final class _ProgramExerciseDraft { final String? exerciseImageMediaIdSnapshot; final List exerciseImageMediaIdsSnapshot; final String? exerciseVideoMediaIdSnapshot; + final List exerciseStepsSnapshot; final bool exerciseArchivedSnapshot; final bool availableTimeSnapshot; final bool availableRepsSnapshot; @@ -1029,6 +1033,7 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot, exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot, exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot, + exerciseStepsSnapshot: exerciseStepsSnapshot, exerciseArchivedSnapshot: exerciseArchivedSnapshot, availableTimeSnapshot: availableTimeSnapshot, availableRepsSnapshot: availableRepsSnapshot, @@ -1057,6 +1062,7 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot, exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot, exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot, + exerciseStepsSnapshot: exerciseStepsSnapshot, exerciseArchivedSnapshot: exerciseArchivedSnapshot, availableTimeSnapshot: availableTimeSnapshot, availableRepsSnapshot: availableRepsSnapshot, diff --git a/test/presentation/program_screen_test.dart b/test/presentation/program_screen_test.dart index 47f5e3e..04f54a9 100644 --- a/test/presentation/program_screen_test.dart +++ b/test/presentation/program_screen_test.dart @@ -164,6 +164,148 @@ void main() { }, ); + testWidgets( + 'ajouter un exercice avec étapes conserve le snapshot des étapes', + (tester) async { + final steps = [ + _step( + id: 'step-1', + position: 0, + name: 'Course', + type: ExerciseStepType.time, + ), + _step( + id: 'step-2', + position: 1, + name: 'Tirs', + type: ExerciseStepType.reps, + ), + ]; + final programRepository = _FakeProgramRepository(); + final exerciseRepository = _FakeExerciseRepository() + ..exercises.add( + Exercise( + metadata: _metadata('exercise-steps'), + name: 'Circuit tirs', + hasTimeMeasure: false, + hasRepsMeasure: true, + hasScoreMeasure: false, + defaultTargetReps: 2, + steps: steps, + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: ProgramFormScreen( + programUseCases: _programUseCases( + programRepository, + exerciseRepository, + ), + exerciseUseCases: _exerciseUseCases(exerciseRepository), + ), + ), + ); + + await tester.enterText( + find.widgetWithText(TextFormField, 'Nom'), + 'Programme étapes', + ); + await tester.tap(find.text('Ajouter un exercice')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Circuit tirs')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + + final savedSteps = programRepository + .programs + .single + .exercises + .single + .exerciseStepsSnapshot; + expect(savedSteps, hasLength(2)); + expect(savedSteps.map((step) => step.name), ['Course', 'Tirs']); + expect(savedSteps.map((step) => step.type), [ + ExerciseStepType.time, + ExerciseStepType.reps, + ]); + }, + ); + + testWidgets( + 'réenregistrer un programme existant conserve les étapes du snapshot', + (tester) async { + final steps = [ + _step( + id: 'step-1', + position: 0, + name: 'Course', + type: ExerciseStepType.time, + ), + _step( + id: 'step-2', + position: 1, + name: 'Tirs', + type: ExerciseStepType.reps, + ), + ]; + final programRepository = _FakeProgramRepository() + ..programs.add( + Program( + metadata: _metadata('program-steps'), + name: 'Programme étapes', + defaultRestSeconds: 45, + exercises: [ + ProgramExercise( + metadata: _metadata('program-exercise-steps'), + programId: 'program-steps', + sourceExerciseId: 'exercise-steps', + position: 0, + exerciseNameSnapshot: 'Circuit tirs', + exerciseStepsSnapshot: steps, + availableTimeSnapshot: false, + availableRepsSnapshot: true, + availableScoreSnapshot: false, + setsCount: 3, + timeEnabled: false, + repsEnabled: true, + scoreEnabled: false, + targetReps: 2, + ), + ], + ), + ); + final exerciseRepository = _FakeExerciseRepository(); + + await tester.pumpWidget( + MaterialApp( + home: ProgramFormScreen( + programUseCases: _programUseCases( + programRepository, + exerciseRepository, + ), + exerciseUseCases: _exerciseUseCases(exerciseRepository), + program: programRepository.programs.single, + ), + ), + ); + + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + + final savedSteps = programRepository + .programs + .single + .exercises + .single + .exerciseStepsSnapshot; + expect(savedSteps, hasLength(2)); + expect(savedSteps.map((step) => step.name), ['Course', 'Tirs']); + expect(savedSteps.map((step) => step.position), [0, 1]); + }, + ); + testWidgets('affiche l’objectif de chrono pour un score en mode chrono', ( tester, ) async { @@ -510,6 +652,21 @@ EntityMetadata _metadata(String id) { ); } +ExerciseStep _step({ + required String id, + required int position, + required String name, + required ExerciseStepType type, +}) { + return ExerciseStep( + id: id, + position: position, + name: name, + type: type, + defaultTargetValue: type == ExerciseStepType.time ? 30 : 5, + ); +} + final class _FakeClock implements Clock { const _FakeClock(this.value);