diff --git a/lib/presentation/exercise_library_screen.dart b/lib/presentation/exercise_library_screen.dart index 6216a1e..e1e06ac 100644 --- a/lib/presentation/exercise_library_screen.dart +++ b/lib/presentation/exercise_library_screen.dart @@ -404,6 +404,7 @@ final class _ExerciseFormScreenState extends State { var _importingImage = false; var _importingVideo = false; var _stepsEnabled = false; + var _autoStartNextTimedStep = true; var _nextStepDraftId = 0; final _stepDrafts = <_ExerciseStepDraft>[]; String? _measureError; @@ -442,6 +443,7 @@ final class _ExerciseFormScreenState extends State { _hasScore = exercise?.hasScoreMeasure ?? false; _scoreInputMode = exercise?.scoreInputMode ?? ScoreInputMode.manual; _stepsEnabled = exercise?.steps.isNotEmpty ?? false; + _autoStartNextTimedStep = exercise?.autoStartNextTimedStep ?? true; for (final step in exercise?.steps ?? const []) { _stepDrafts.add(_draftFromStep(step)); } @@ -742,6 +744,25 @@ final class _ExerciseFormScreenState extends State { }, ), if (_stepsEnabled) ...[ + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text( + 'Enchaîner automatiquement les chronos consécutifs', + ), + subtitle: Text( + _autoStartNextTimedStep + ? 'Le chrono suivant démarre dès que le précédent arrive à 0.' + : "L'app attendra ton démarrage avant de lancer le chrono " + 'suivant.', + ), + value: _autoStartNextTimedStep, + onChanged: (value) { + setState(() { + _autoStartNextTimedStep = value; + }); + }, + ), const SizedBox(height: 8), ReorderableListView.builder( shrinkWrap: true, @@ -1312,6 +1333,7 @@ final class _ExerciseFormScreenState extends State { defaultTargetReps: defaultTargetReps, defaultTargetScore: defaultTargetScore, defaultTargetScoreTimeMs: defaultTargetScoreTimeMs, + autoStartNextTimedStep: _autoStartNextTimedStep, steps: steps, ); } else { @@ -1332,6 +1354,7 @@ final class _ExerciseFormScreenState extends State { defaultTargetReps: defaultTargetReps, defaultTargetScore: defaultTargetScore, defaultTargetScoreTimeMs: defaultTargetScoreTimeMs, + autoStartNextTimedStep: _autoStartNextTimedStep, steps: steps, ); } diff --git a/lib/presentation/program_screen.dart b/lib/presentation/program_screen.dart index 886d8fb..a9bf91b 100644 --- a/lib/presentation/program_screen.dart +++ b/lib/presentation/program_screen.dart @@ -660,6 +660,11 @@ final class _ProgramExerciseCustomizationScreenState : scoreUnit == null ? 'Score' : 'Score ($scoreUnit)'; + final autoStartNextTimedStepCustomized = + _draft.autoStartNextTimedStepOverride != null; + final autoStartNextTimedStepStateLabel = autoStartNextTimedStepCustomized + ? 'Personnalisé pour ce programme' + : "Réglage de l'exercice"; return Scaffold( appBar: AppBar(title: const Text("Personnaliser l'exercice")), bottomNavigationBar: SafeArea( @@ -785,6 +790,36 @@ final class _ProgramExerciseCustomizationScreenState _draft.targetScoreTimeMs = null; }, ), + if (_draft.exerciseStepsSnapshot.isNotEmpty) ...[ + const SizedBox(height: 24), + Text('Séquence', style: Theme.of(context).textTheme.titleMedium), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text( + 'Enchaîner automatiquement les chronos consécutifs', + ), + subtitle: Text( + '$autoStartNextTimedStepStateLabel\n' + '${_autoStartHelpText(_draft.autoStartNextTimedStepEffective)}', + ), + value: _draft.autoStartNextTimedStepEffective, + onChanged: (value) { + setState(() { + _draft.autoStartNextTimedStepOverride = value; + }); + }, + ), + if (autoStartNextTimedStepCustomized) + TextButton( + onPressed: () { + setState(() { + _draft.autoStartNextTimedStepOverride = null; + }); + }, + child: const Text("Revenir au réglage de l'exercice"), + ), + ], const SizedBox(height: 24), Text('Repos', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), @@ -916,6 +951,8 @@ final class _ProgramExerciseDraft { this.exerciseImageMediaIdsSnapshot = const [], this.exerciseVideoMediaIdSnapshot, this.exerciseStepsSnapshot = const [], + this.autoStartNextTimedStepSnapshot = true, + this.autoStartNextTimedStepOverride, this.exerciseArchivedSnapshot = false, required this.availableTimeSnapshot, required this.availableRepsSnapshot, @@ -946,6 +983,7 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdsSnapshot: exercise.imageMediaIds, exerciseVideoMediaIdSnapshot: exercise.videoMediaId, exerciseStepsSnapshot: exercise.steps, + autoStartNextTimedStepSnapshot: exercise.autoStartNextTimedStep, exerciseArchivedSnapshot: exercise.archivedAt != null, availableTimeSnapshot: exercise.hasTimeMeasure, availableRepsSnapshot: exercise.hasRepsMeasure, @@ -978,6 +1016,8 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdsSnapshot: exercise.exerciseImageMediaIdsSnapshot, exerciseVideoMediaIdSnapshot: exercise.exerciseVideoMediaIdSnapshot, exerciseStepsSnapshot: exercise.exerciseStepsSnapshot, + autoStartNextTimedStepSnapshot: exercise.autoStartNextTimedStepSnapshot, + autoStartNextTimedStepOverride: exercise.autoStartNextTimedStepOverride, exerciseArchivedSnapshot: exercise.exerciseArchivedSnapshot, availableTimeSnapshot: exercise.availableTimeSnapshot, availableRepsSnapshot: exercise.availableRepsSnapshot, @@ -1008,6 +1048,7 @@ final class _ProgramExerciseDraft { final List exerciseImageMediaIdsSnapshot; final String? exerciseVideoMediaIdSnapshot; final List exerciseStepsSnapshot; + final bool autoStartNextTimedStepSnapshot; final bool exerciseArchivedSnapshot; final bool availableTimeSnapshot; final bool availableRepsSnapshot; @@ -1022,6 +1063,11 @@ final class _ProgramExerciseDraft { double? targetScore; int? targetScoreTimeMs; int? restSecondsOverride; + bool? autoStartNextTimedStepOverride; + + bool get autoStartNextTimedStepEffective { + return autoStartNextTimedStepOverride ?? autoStartNextTimedStepSnapshot; + } _ProgramExerciseDraft copy() { return _ProgramExerciseDraft( @@ -1034,6 +1080,8 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot, exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot, exerciseStepsSnapshot: exerciseStepsSnapshot, + autoStartNextTimedStepSnapshot: autoStartNextTimedStepSnapshot, + autoStartNextTimedStepOverride: autoStartNextTimedStepOverride, exerciseArchivedSnapshot: exerciseArchivedSnapshot, availableTimeSnapshot: availableTimeSnapshot, availableRepsSnapshot: availableRepsSnapshot, @@ -1063,6 +1111,8 @@ final class _ProgramExerciseDraft { exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot, exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot, exerciseStepsSnapshot: exerciseStepsSnapshot, + autoStartNextTimedStepSnapshot: autoStartNextTimedStepSnapshot, + autoStartNextTimedStepOverride: autoStartNextTimedStepOverride, exerciseArchivedSnapshot: exerciseArchivedSnapshot, availableTimeSnapshot: availableTimeSnapshot, availableRepsSnapshot: availableRepsSnapshot, @@ -1083,6 +1133,12 @@ final class _ProgramExerciseDraft { } } +String _autoStartHelpText(bool active) { + return active + ? 'Le chrono suivant démarre dès que le précédent arrive à 0.' + : "L'app attendra ton démarrage avant de lancer le chrono suivant."; +} + double? _millisecondsToSeconds(int? milliseconds) { return milliseconds == null ? null : milliseconds / 1000; } diff --git a/lib/presentation/workout_template_screen.dart b/lib/presentation/workout_template_screen.dart index 7d15f7f..aec5123 100644 --- a/lib/presentation/workout_template_screen.dart +++ b/lib/presentation/workout_template_screen.dart @@ -502,6 +502,12 @@ final class _WorkoutTemplateProgramDetailScreenState itemCount: widget.program.exercises.length, itemBuilder: (context, index) { final exercise = widget.program.exercises[index]; + final autoStartNextTimedStepCustomized = + exercise.autoStartNextTimedStepOverride != null; + final autoStartNextTimedStepStateLabel = + autoStartNextTimedStepCustomized + ? 'Personnalisé pour cette séance' + : 'Réglage du programme'; return Card( child: Padding( padding: const EdgeInsets.all(12), @@ -564,6 +570,38 @@ final class _WorkoutTemplateProgramDetailScreenState }); }, ), + if (exercise.exerciseStepsSnapshot.isNotEmpty) ...[ + const SizedBox(height: 16), + Text( + 'Séquence', + style: Theme.of(context).textTheme.titleMedium, + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text( + 'Enchaîner automatiquement les chronos consécutifs', + ), + subtitle: Text( + '$autoStartNextTimedStepStateLabel\n' + '${_autoStartHelpText(exercise.autoStartNextTimedStepEffective)}', + ), + value: exercise.autoStartNextTimedStepEffective, + onChanged: (value) { + setState(() { + exercise.autoStartNextTimedStepOverride = value; + }); + }, + ), + if (autoStartNextTimedStepCustomized) + TextButton( + onPressed: () { + setState(() { + exercise.autoStartNextTimedStepOverride = null; + }); + }, + child: const Text('Revenir au réglage du programme'), + ), + ], ], ), ), @@ -621,6 +659,8 @@ final class WorkoutTemplateProgramDraft { override.targetTimeSecondsOverride; exercise.targetRepsOverride = override.targetRepsOverride; exercise.targetScoreOverride = override.targetScoreOverride; + exercise.autoStartNextTimedStepOverride = + override.autoStartNextTimedStepOverride; } } } @@ -667,6 +707,7 @@ final class WorkoutTemplateProgramDraft { targetTimeSecondsOverride: exercise.targetTimeSecondsOverride, targetRepsOverride: exercise.targetRepsOverride, targetScoreOverride: exercise.targetScoreOverride, + autoStartNextTimedStepOverride: exercise.autoStartNextTimedStepOverride, ); } } @@ -680,6 +721,8 @@ final class WorkoutTemplateExerciseDraft { required this.timeEnabled, required this.repsEnabled, required this.scoreEnabled, + this.exerciseStepsSnapshot = const [], + this.autoStartNextTimedStepSnapshot = true, this.scoreUnitSnapshot, this.targetTimeSeconds, this.targetReps, @@ -689,6 +732,7 @@ final class WorkoutTemplateExerciseDraft { this.targetTimeSecondsOverride, this.targetRepsOverride, this.targetScoreOverride, + this.autoStartNextTimedStepOverride, }); final String snapshotExerciseId; @@ -697,6 +741,8 @@ final class WorkoutTemplateExerciseDraft { final bool timeEnabled; final bool repsEnabled; final bool scoreEnabled; + final List exerciseStepsSnapshot; + final bool autoStartNextTimedStepSnapshot; final String? scoreUnitSnapshot; final int? targetTimeSeconds; final int? targetReps; @@ -706,12 +752,18 @@ final class WorkoutTemplateExerciseDraft { int? targetTimeSecondsOverride; int? targetRepsOverride; double? targetScoreOverride; + bool? autoStartNextTimedStepOverride; + + bool get autoStartNextTimedStepEffective { + return autoStartNextTimedStepOverride ?? autoStartNextTimedStepSnapshot; + } bool get hasOverride { return setsCountOverride != null || targetTimeSecondsOverride != null || targetRepsOverride != null || - targetScoreOverride != null; + targetScoreOverride != null || + autoStartNextTimedStepOverride != null; } } @@ -746,6 +798,10 @@ List _parseExercises( final rawExercises = snapshot['exercises'] as List? ?? const []; return rawExercises.map((raw) { final exercise = raw as Map; + final autoStartNextTimedStepSnapshot = + (exercise['autoStartNextTimedStepOverride'] as bool?) ?? + (exercise['autoStartNextTimedStepSnapshot'] as bool?) ?? + true; return WorkoutTemplateExerciseDraft( snapshotExerciseId: exercise['id'] as String, exerciseNameSnapshot: exercise['exerciseNameSnapshot'] as String, @@ -753,6 +809,10 @@ List _parseExercises( timeEnabled: exercise['timeEnabled'] as bool, repsEnabled: exercise['repsEnabled'] as bool, scoreEnabled: exercise['scoreEnabled'] as bool, + exerciseStepsSnapshot: _parseExerciseSteps( + exercise['exerciseStepsSnapshot'], + ), + autoStartNextTimedStepSnapshot: autoStartNextTimedStepSnapshot, scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?, targetTimeSeconds: exercise['targetTimeSeconds'] as int?, targetReps: exercise['targetReps'] as int?, @@ -761,6 +821,34 @@ List _parseExercises( }).toList(); } +List _parseExerciseSteps(Object? value) { + final rawSteps = value as List? ?? const []; + return rawSteps.map((raw) { + final step = raw as Map; + return ExerciseStep( + id: step['id'] as String, + position: step['position'] as int, + name: step['name'] as String, + type: ExerciseStepType.values.byName(step['type'] as String), + defaultTargetValue: step['defaultTargetValue'] as int, + hasScore: step['hasScore'] as bool? ?? false, + scoreInputMode: ScoreInputMode.values.byName( + step['scoreInputMode'] as String? ?? ScoreInputMode.manual.name, + ), + scoreLabel: step['scoreLabel'] as String?, + scoreUnit: step['scoreUnit'] as String?, + defaultTargetScore: (step['defaultTargetScore'] as num?)?.toDouble(), + defaultTargetScoreTimeMs: step['defaultTargetScoreTimeMs'] as int?, + ); + }).toList(); +} + +String _autoStartHelpText(bool active) { + return active + ? 'Le chrono suivant démarre dès que le précédent arrive à 0.' + : "L'app attendra ton démarrage avant de lancer le chrono suivant."; +} + String _templateSummary(WorkoutTemplate template) { final programCount = template.programs.length; final exerciseCount = template.programs.fold( diff --git a/test/presentation/exercise_library_screen_test.dart b/test/presentation/exercise_library_screen_test.dart index 957b32c..7e56be5 100644 --- a/test/presentation/exercise_library_screen_test.dart +++ b/test/presentation/exercise_library_screen_test.dart @@ -435,6 +435,58 @@ void main() { expect(saved.steps.single.defaultTargetValue, 12); }); + testWidgets( + 'le réglage d’enchaînement automatique des étapes est sauvegardé', + (tester) async { + final exerciseRepository = _FakeExerciseRepository(); + + await tester.binding.setSurfaceSize(const Size(400, 3600)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + await _pumpExerciseForm(tester, exerciseRepository); + await tester.enterText( + find.widgetWithText(TextFormField, 'Nom'), + 'Combo', + ); + await tester.enterText( + find.widgetWithText(TextFormField, 'Temps par défaut (s)'), + '30', + ); + await tester.tap( + find.widgetWithText( + SwitchListTile, + 'Rythmer cet exercice avec des étapes', + ), + ); + await tester.pump(); + await tester.tap( + find.widgetWithText( + SwitchListTile, + 'Enchaîner automatiquement les chronos consécutifs', + ), + ); + await tester.pump(); + await tester.tap(find.text('Ajouter une étape')); + await tester.pump(); + await tester.enterText( + find.widgetWithText(TextFormField, 'Nom de l’étape').last, + 'Dribble main droite', + ); + await tester.enterText( + find.widgetWithText(TextFormField, 'Durée par défaut (s)').last, + '12', + ); + + await tester.ensureVisible(find.text('Enregistrer')); + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + + final saved = exerciseRepository.saved.single; + expect(saved.steps, hasLength(1)); + expect(saved.autoStartNextTimedStep, isFalse); + }, + ); + testWidgets('désigner une image comme icône l’indique visuellement', ( tester, ) async { diff --git a/test/presentation/program_screen_test.dart b/test/presentation/program_screen_test.dart index 717c852..7f5c390 100644 --- a/test/presentation/program_screen_test.dart +++ b/test/presentation/program_screen_test.dart @@ -306,6 +306,165 @@ void main() { }, ); + testWidgets( + 'personnaliser l’enchaînement automatique transmet l’override au programme', + (tester) async { + final steps = [ + _step( + id: 'step-1', + position: 0, + name: 'Course', + type: ExerciseStepType.time, + ), + _step( + id: 'step-2', + position: 1, + name: 'Retour', + type: ExerciseStepType.time, + ), + ]; + final programRepository = _FakeProgramRepository(); + final exerciseRepository = _FakeExerciseRepository() + ..exercises.add( + Exercise( + metadata: _metadata('exercise-steps'), + name: 'Navettes', + hasTimeMeasure: true, + hasRepsMeasure: false, + hasScoreMeasure: false, + defaultTargetTimeSeconds: 30, + steps: steps, + ), + ); + + await tester.binding.setSurfaceSize(const Size(400, 2400)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + 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('Navettes')); + await tester.pumpAndSettle(); + await tester.tap(find.byTooltip("Personnaliser l'exercice")); + await tester.pumpAndSettle(); + + await tester.tap( + find.widgetWithText( + SwitchListTile, + 'Enchaîner automatiquement les chronos consécutifs', + ), + ); + await tester.pump(); + expect( + find.textContaining('Personnalisé pour ce programme'), + findsOneWidget, + ); + + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + + final savedExercise = programRepository.programs.single.exercises.single; + expect(savedExercise.autoStartNextTimedStepSnapshot, isTrue); + expect(savedExercise.autoStartNextTimedStepOverride, isFalse); + }, + ); + + testWidgets( + 'revenir au réglage de l’exercice remet l’override programme à null', + (tester) async { + final steps = [ + _step( + id: 'step-1', + position: 0, + name: 'Course', + type: ExerciseStepType.time, + ), + ]; + final programRepository = _FakeProgramRepository(); + final exerciseRepository = _FakeExerciseRepository() + ..exercises.add( + Exercise( + metadata: _metadata('exercise-steps'), + name: 'Navettes', + hasTimeMeasure: true, + hasRepsMeasure: false, + hasScoreMeasure: false, + defaultTargetTimeSeconds: 30, + autoStartNextTimedStep: false, + steps: steps, + ), + ); + + await tester.binding.setSurfaceSize(const Size(400, 2400)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + 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('Navettes')); + await tester.pumpAndSettle(); + await tester.tap(find.byTooltip("Personnaliser l'exercice")); + await tester.pumpAndSettle(); + + await tester.tap( + find.widgetWithText( + SwitchListTile, + 'Enchaîner automatiquement les chronos consécutifs', + ), + ); + await tester.pump(); + expect( + find.textContaining('Personnalisé pour ce programme'), + findsOneWidget, + ); + + await tester.tap(find.text("Revenir au réglage de l'exercice")); + await tester.pump(); + expect(find.textContaining("Réglage de l'exercice"), findsOneWidget); + + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Enregistrer')); + await tester.pumpAndSettle(); + + final savedExercise = programRepository.programs.single.exercises.single; + expect(savedExercise.autoStartNextTimedStepSnapshot, isFalse); + expect(savedExercise.autoStartNextTimedStepOverride, isNull); + }, + ); + testWidgets('affiche l’objectif de chrono pour un score en mode chrono', ( tester, ) async { diff --git a/test/presentation/workout_template_screen_test.dart b/test/presentation/workout_template_screen_test.dart index 8c00744..8e92469 100644 --- a/test/presentation/workout_template_screen_test.dart +++ b/test/presentation/workout_template_screen_test.dart @@ -44,6 +44,117 @@ void main() { }, ); + testWidgets( + 'personnaliser l’enchaînement automatique transmet l’override séance', + (tester) async { + final program = WorkoutTemplateProgramDraft( + clientKey: 'program-1', + programNameSnapshot: 'Circuit', + defaultRestSecondsSnapshot: 60, + programSnapshotJson: '{}', + exercises: [ + WorkoutTemplateExerciseDraft( + snapshotExerciseId: 'exercise-1', + exerciseNameSnapshot: 'Navettes', + setsCount: 3, + timeEnabled: true, + repsEnabled: false, + scoreEnabled: false, + exerciseStepsSnapshot: [ + _step( + id: 'step-1', + position: 0, + name: 'Course', + type: ExerciseStepType.time, + ), + _step( + id: 'step-2', + position: 1, + name: 'Retour', + type: ExerciseStepType.time, + ), + ], + autoStartNextTimedStepSnapshot: true, + targetTimeSeconds: 30, + ), + ], + ); + + await tester.binding.setSurfaceSize(const Size(400, 1400)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + await tester.pumpWidget( + MaterialApp(home: WorkoutTemplateProgramDetailScreen(program: program)), + ); + + await tester.tap( + find.widgetWithText( + SwitchListTile, + 'Enchaîner automatiquement les chronos consécutifs', + ), + ); + await tester.pump(); + + expect( + find.textContaining('Personnalisé pour cette séance'), + findsOneWidget, + ); + final override = program.toOverrides().single; + expect(override.autoStartNextTimedStepOverride, isFalse); + }, + ); + + testWidgets( + 'revenir au réglage du programme remet l’override séance à null', + (tester) async { + final program = WorkoutTemplateProgramDraft( + clientKey: 'program-1', + programNameSnapshot: 'Circuit', + defaultRestSecondsSnapshot: 60, + programSnapshotJson: '{}', + exercises: [ + WorkoutTemplateExerciseDraft( + snapshotExerciseId: 'exercise-1', + exerciseNameSnapshot: 'Navettes', + setsCount: 3, + timeEnabled: true, + repsEnabled: false, + scoreEnabled: false, + exerciseStepsSnapshot: [ + _step( + id: 'step-1', + position: 0, + name: 'Course', + type: ExerciseStepType.time, + ), + ], + autoStartNextTimedStepSnapshot: false, + autoStartNextTimedStepOverride: true, + targetTimeSeconds: 30, + ), + ], + ); + + await tester.binding.setSurfaceSize(const Size(400, 1400)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + await tester.pumpWidget( + MaterialApp(home: WorkoutTemplateProgramDetailScreen(program: program)), + ); + + expect( + find.textContaining('Personnalisé pour cette séance'), + findsOneWidget, + ); + + await tester.tap(find.text('Revenir au réglage du programme')); + await tester.pump(); + + expect(find.textContaining('Réglage du programme'), findsOneWidget); + expect(program.toOverrides(), isEmpty); + }, + ); + testWidgets( 'lancer une séance alors qu’une autre est en cours affiche un message', (tester) async { @@ -308,6 +419,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);