feat(ui): configuration du score chronométré (ticket #26)

Ajoute la configuration du score chronométré sur exercise_library_screen.dart
et program_screen.dart, s'appuyant sur les fondations domain du ticket
#25. flutter analyze propre, 41/41 tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 09:08:16 +02:00
parent ab6b373a79
commit 2f732aa1b1
4 changed files with 299 additions and 38 deletions

View File

@ -246,6 +246,7 @@ final class ExerciseListTile extends StatelessWidget {
(measure) => MeasureBadge( (measure) => MeasureBadge(
measure: measure, measure: measure,
scoreUnit: exercise.scoreUnit, scoreUnit: exercise.scoreUnit,
scoreInputMode: exercise.scoreInputMode,
), ),
), ),
if (hasMedia) if (hasMedia)
@ -293,6 +294,7 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
var _hasTime = true; var _hasTime = true;
var _hasReps = false; var _hasReps = false;
var _hasScore = false; var _hasScore = false;
var _scoreInputMode = ScoreInputMode.manual;
var _saving = false; var _saving = false;
var _importingImage = false; var _importingImage = false;
var _importingVideo = false; var _importingVideo = false;
@ -314,6 +316,7 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
_hasTime = exercise?.hasTimeMeasure ?? true; _hasTime = exercise?.hasTimeMeasure ?? true;
_hasReps = exercise?.hasRepsMeasure ?? false; _hasReps = exercise?.hasRepsMeasure ?? false;
_hasScore = exercise?.hasScoreMeasure ?? false; _hasScore = exercise?.hasScoreMeasure ?? false;
_scoreInputMode = exercise?.scoreInputMode ?? ScoreInputMode.manual;
} }
@override @override
@ -408,27 +411,63 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
), ),
if (_hasScore) ...[ if (_hasScore) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
TextFormField( Text(
controller: _scoreLabelController, 'Mode de saisie',
decoration: const InputDecoration(labelText: 'Score à saisir'), style: Theme.of(context).textTheme.titleSmall,
validator: (value) { ),
if (!_hasScore) return null; RadioListTile<ScoreInputMode>(
return value == null || value.trim().isEmpty contentPadding: EdgeInsets.zero,
? 'Le libellé du score est obligatoire.' title: const Text('Saisie libre'),
: null; value: ScoreInputMode.manual,
groupValue: _scoreInputMode,
onChanged: (value) {
if (value == null) return;
setState(() => _scoreInputMode = value);
}, },
), ),
const SizedBox(height: 12), RadioListTile<ScoreInputMode>(
TextFormField( contentPadding: EdgeInsets.zero,
controller: _scoreUnitController, title: const Text('Chrono intégré'),
decoration: const InputDecoration(labelText: 'Unité'), subtitle: const Text('Temps réalisé'),
validator: (value) { value: ScoreInputMode.stopwatch,
if (!_hasScore) return null; groupValue: _scoreInputMode,
return value == null || value.trim().isEmpty onChanged: (value) {
? 'Lunité du score est obligatoire.' if (value == null) return;
: null; setState(() => _scoreInputMode = value);
}, },
), ),
if (_scoreInputMode == ScoreInputMode.manual) ...[
const SizedBox(height: 12),
TextFormField(
controller: _scoreLabelController,
decoration: const InputDecoration(
labelText: 'Score à saisir',
),
validator: (value) {
if (!_hasScore ||
_scoreInputMode != ScoreInputMode.manual) {
return null;
}
return value == null || value.trim().isEmpty
? 'Le libellé du score est obligatoire.'
: null;
},
),
const SizedBox(height: 12),
TextFormField(
controller: _scoreUnitController,
decoration: const InputDecoration(labelText: 'Unité'),
validator: (value) {
if (!_hasScore ||
_scoreInputMode != ScoreInputMode.manual) {
return null;
}
return value == null || value.trim().isEmpty
? 'Lunité du score est obligatoire.'
: null;
},
),
],
], ],
if (_warning != null) ...[ if (_warning != null) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
@ -537,6 +576,16 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
} }
setState(() => _saving = true); setState(() => _saving = true);
final scoreInputMode = _hasScore ? _scoreInputMode : ScoreInputMode.manual;
final scoreLabel = _hasScore
? switch (scoreInputMode) {
ScoreInputMode.manual => _scoreLabelController.text.trim(),
ScoreInputMode.stopwatch => 'Temps réalisé',
}
: null;
final scoreUnit = _hasScore && scoreInputMode == ScoreInputMode.manual
? _scoreUnitController.text.trim()
: null;
try { try {
if (exercise == null) { if (exercise == null) {
await widget.exerciseUseCases.create( await widget.exerciseUseCases.create(
@ -547,8 +596,9 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
hasTimeMeasure: _hasTime, hasTimeMeasure: _hasTime,
hasRepsMeasure: _hasReps, hasRepsMeasure: _hasReps,
hasScoreMeasure: _hasScore, hasScoreMeasure: _hasScore,
scoreLabel: _hasScore ? _scoreLabelController.text.trim() : null, scoreInputMode: scoreInputMode,
scoreUnit: _hasScore ? _scoreUnitController.text.trim() : null, scoreLabel: scoreLabel,
scoreUnit: scoreUnit,
); );
} else { } else {
await widget.exerciseUseCases.update( await widget.exerciseUseCases.update(
@ -560,12 +610,16 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
hasTimeMeasure: _hasTime, hasTimeMeasure: _hasTime,
hasRepsMeasure: _hasReps, hasRepsMeasure: _hasReps,
hasScoreMeasure: _hasScore, hasScoreMeasure: _hasScore,
scoreLabel: _hasScore ? _scoreLabelController.text.trim() : null, scoreInputMode: scoreInputMode,
scoreUnit: _hasScore ? _scoreUnitController.text.trim() : null, scoreLabel: scoreLabel,
scoreUnit: scoreUnit,
); );
} }
if (mounted) { if (mounted) {
Navigator.of(context).pop(true); final navigator = Navigator.of(context);
if (navigator.canPop()) {
navigator.pop(true);
}
} }
} on Exception catch (error) { } on Exception catch (error) {
_showSnackBar(error.toString()); _showSnackBar(error.toString());
@ -579,7 +633,8 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
bool _measuresChanged(Exercise exercise) { bool _measuresChanged(Exercise exercise) {
return exercise.hasTimeMeasure != _hasTime || return exercise.hasTimeMeasure != _hasTime ||
exercise.hasRepsMeasure != _hasReps || exercise.hasRepsMeasure != _hasReps ||
exercise.hasScoreMeasure != _hasScore; exercise.hasScoreMeasure != _hasScore ||
exercise.scoreInputMode != _scoreInputMode;
} }
String? _optionalText(TextEditingController controller) { String? _optionalText(TextEditingController controller) {
@ -601,20 +656,32 @@ final class _ExerciseFormScreenState extends State<ExerciseFormScreen> {
} }
final class MeasureBadge extends StatelessWidget { final class MeasureBadge extends StatelessWidget {
const MeasureBadge({required this.measure, this.scoreUnit, super.key}); const MeasureBadge({
required this.measure,
this.scoreInputMode = ScoreInputMode.manual,
this.scoreUnit,
super.key,
});
final WorkoutMeasure measure; final WorkoutMeasure measure;
final ScoreInputMode scoreInputMode;
final String? scoreUnit; final String? scoreUnit;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final unit = scoreUnit?.trim(); final unit = scoreUnit?.trim();
final label = var label = measure.label;
measure == WorkoutMeasure.score && unit != null && unit.isNotEmpty if (measure == WorkoutMeasure.score &&
? '${measure.label} ($unit)' scoreInputMode == ScoreInputMode.stopwatch) {
: measure.label; label = 'Score chrono';
} else if (measure == WorkoutMeasure.score &&
unit != null &&
unit.isNotEmpty) {
label = '${measure.label} ($unit)';
}
return Semantics( return Semantics(
label: label, label: label,
excludeSemantics: true,
child: Chip(visualDensity: VisualDensity.compact, label: Text(label)), child: Chip(visualDensity: VisualDensity.compact, label: Text(label)),
); );
} }

View File

@ -539,6 +539,13 @@ final class _ProgramExerciseCustomizationScreenState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scoreUnit = _draft.scoreUnitSnapshot; final scoreUnit = _draft.scoreUnitSnapshot;
final hasStopwatchScore =
_draft.scoreInputModeSnapshot == ScoreInputMode.stopwatch;
final scoreTitle = hasStopwatchScore
? 'Score chrono'
: scoreUnit == null
? 'Score'
: 'Score ($scoreUnit)';
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text("Personnaliser l'exercice")), appBar: AppBar(title: const Text("Personnaliser l'exercice")),
bottomNavigationBar: SafeArea( bottomNavigationBar: SafeArea(
@ -603,10 +610,19 @@ final class _ProgramExerciseCustomizationScreenState
if (_draft.availableScoreSnapshot) if (_draft.availableScoreSnapshot)
SwitchListTile( SwitchListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text(scoreUnit == null ? 'Score' : 'Score ($scoreUnit)'), title: Text(scoreTitle),
value: _draft.enabledMeasures.contains(WorkoutMeasure.score), value: _draft.enabledMeasures.contains(WorkoutMeasure.score),
onChanged: (value) => _toggle(WorkoutMeasure.score, value), onChanged: (value) => _toggle(WorkoutMeasure.score, value),
), ),
if (_draft.enabledMeasures.contains(WorkoutMeasure.time) &&
_draft.enabledMeasures.contains(WorkoutMeasure.score) &&
hasStopwatchScore) ...[
const SizedBox(height: 8),
const Text(
"Temps sert d'objectif de durée ; Score chrono enregistre "
'le temps réalisé.',
),
],
if (_measureError != null) if (_measureError != null)
Padding( Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
@ -630,13 +646,31 @@ final class _ProgramExerciseCustomizationScreenState
onChanged: (value) => _draft.targetReps = value?.round(), onChanged: (value) => _draft.targetReps = value?.round(),
), ),
if (_draft.enabledMeasures.contains(WorkoutMeasure.score)) if (_draft.enabledMeasures.contains(WorkoutMeasure.score))
_NumberField( if (hasStopwatchScore)
label: scoreUnit == null _NumberField(
? 'Cible score' label: 'Objectif de chrono',
: 'Cible score ($scoreUnit)', helperText: 'Le résultat réel sera mesuré pendant la série.',
initialValue: _draft.targetScore, initialValue: _millisecondsToSeconds(
onChanged: (value) => _draft.targetScore = value, _draft.targetScoreTimeMs,
), ),
onChanged: (value) {
_draft.targetScore = null;
_draft.targetScoreTimeMs = value == null
? null
: (value * 1000).round();
},
)
else
_NumberField(
label: scoreUnit == null
? 'Cible score'
: 'Cible score ($scoreUnit)',
initialValue: _draft.targetScore,
onChanged: (value) {
_draft.targetScore = value;
_draft.targetScoreTimeMs = null;
},
),
const SizedBox(height: 24), const SizedBox(height: 24),
Text('Repos', style: Theme.of(context).textTheme.titleMedium), Text('Repos', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -662,6 +696,10 @@ final class _ProgramExerciseCustomizationScreenState
_draft.enabledMeasures = {..._draft.enabledMeasures, measure}; _draft.enabledMeasures = {..._draft.enabledMeasures, measure};
} else { } else {
_draft.enabledMeasures = {..._draft.enabledMeasures}..remove(measure); _draft.enabledMeasures = {..._draft.enabledMeasures}..remove(measure);
if (measure == WorkoutMeasure.score) {
_draft.targetScore = null;
_draft.targetScoreTimeMs = null;
}
} }
}); });
} }
@ -731,11 +769,13 @@ final class _NumberField extends StatelessWidget {
required this.label, required this.label,
required this.initialValue, required this.initialValue,
required this.onChanged, required this.onChanged,
this.helperText,
}); });
final String label; final String label;
final num? initialValue; final num? initialValue;
final ValueChanged<double?> onChanged; final ValueChanged<double?> onChanged;
final String? helperText;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -743,7 +783,7 @@ final class _NumberField extends StatelessWidget {
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: TextFormField( child: TextFormField(
initialValue: initialValue?.toString(), initialValue: initialValue?.toString(),
decoration: InputDecoration(labelText: label), decoration: InputDecoration(labelText: label, helperText: helperText),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
onChanged: (value) => onChanged(double.tryParse(value)), onChanged: (value) => onChanged(double.tryParse(value)),
), ),
@ -764,6 +804,7 @@ final class _ProgramExerciseDraft {
required this.availableTimeSnapshot, required this.availableTimeSnapshot,
required this.availableRepsSnapshot, required this.availableRepsSnapshot,
required this.availableScoreSnapshot, required this.availableScoreSnapshot,
this.scoreInputModeSnapshot = ScoreInputMode.manual,
this.scoreLabelSnapshot, this.scoreLabelSnapshot,
this.scoreUnitSnapshot, this.scoreUnitSnapshot,
required this.setsCount, required this.setsCount,
@ -771,6 +812,7 @@ final class _ProgramExerciseDraft {
this.targetTimeSeconds, this.targetTimeSeconds,
this.targetReps, this.targetReps,
this.targetScore, this.targetScore,
this.targetScoreTimeMs,
this.restSecondsOverride, this.restSecondsOverride,
}); });
@ -789,6 +831,7 @@ final class _ProgramExerciseDraft {
availableTimeSnapshot: exercise.hasTimeMeasure, availableTimeSnapshot: exercise.hasTimeMeasure,
availableRepsSnapshot: exercise.hasRepsMeasure, availableRepsSnapshot: exercise.hasRepsMeasure,
availableScoreSnapshot: exercise.hasScoreMeasure, availableScoreSnapshot: exercise.hasScoreMeasure,
scoreInputModeSnapshot: exercise.scoreInputMode,
scoreLabelSnapshot: exercise.scoreLabel, scoreLabelSnapshot: exercise.scoreLabel,
scoreUnitSnapshot: exercise.scoreUnit, scoreUnitSnapshot: exercise.scoreUnit,
setsCount: 3, setsCount: 3,
@ -810,6 +853,7 @@ final class _ProgramExerciseDraft {
availableTimeSnapshot: exercise.availableTimeSnapshot, availableTimeSnapshot: exercise.availableTimeSnapshot,
availableRepsSnapshot: exercise.availableRepsSnapshot, availableRepsSnapshot: exercise.availableRepsSnapshot,
availableScoreSnapshot: exercise.availableScoreSnapshot, availableScoreSnapshot: exercise.availableScoreSnapshot,
scoreInputModeSnapshot: exercise.scoreInputModeSnapshot,
scoreLabelSnapshot: exercise.scoreLabelSnapshot, scoreLabelSnapshot: exercise.scoreLabelSnapshot,
scoreUnitSnapshot: exercise.scoreUnitSnapshot, scoreUnitSnapshot: exercise.scoreUnitSnapshot,
setsCount: exercise.setsCount, setsCount: exercise.setsCount,
@ -821,6 +865,7 @@ final class _ProgramExerciseDraft {
targetTimeSeconds: exercise.targetTimeSeconds, targetTimeSeconds: exercise.targetTimeSeconds,
targetReps: exercise.targetReps, targetReps: exercise.targetReps,
targetScore: exercise.targetScore, targetScore: exercise.targetScore,
targetScoreTimeMs: exercise.targetScoreTimeMs,
restSecondsOverride: exercise.restSecondsOverride, restSecondsOverride: exercise.restSecondsOverride,
); );
} }
@ -836,6 +881,7 @@ final class _ProgramExerciseDraft {
final bool availableTimeSnapshot; final bool availableTimeSnapshot;
final bool availableRepsSnapshot; final bool availableRepsSnapshot;
final bool availableScoreSnapshot; final bool availableScoreSnapshot;
final ScoreInputMode scoreInputModeSnapshot;
final String? scoreLabelSnapshot; final String? scoreLabelSnapshot;
final String? scoreUnitSnapshot; final String? scoreUnitSnapshot;
int setsCount; int setsCount;
@ -843,6 +889,7 @@ final class _ProgramExerciseDraft {
int? targetTimeSeconds; int? targetTimeSeconds;
int? targetReps; int? targetReps;
double? targetScore; double? targetScore;
int? targetScoreTimeMs;
int? restSecondsOverride; int? restSecondsOverride;
_ProgramExerciseDraft copy() { _ProgramExerciseDraft copy() {
@ -858,6 +905,7 @@ final class _ProgramExerciseDraft {
availableTimeSnapshot: availableTimeSnapshot, availableTimeSnapshot: availableTimeSnapshot,
availableRepsSnapshot: availableRepsSnapshot, availableRepsSnapshot: availableRepsSnapshot,
availableScoreSnapshot: availableScoreSnapshot, availableScoreSnapshot: availableScoreSnapshot,
scoreInputModeSnapshot: scoreInputModeSnapshot,
scoreLabelSnapshot: scoreLabelSnapshot, scoreLabelSnapshot: scoreLabelSnapshot,
scoreUnitSnapshot: scoreUnitSnapshot, scoreUnitSnapshot: scoreUnitSnapshot,
setsCount: setsCount, setsCount: setsCount,
@ -865,11 +913,14 @@ final class _ProgramExerciseDraft {
targetTimeSeconds: targetTimeSeconds, targetTimeSeconds: targetTimeSeconds,
targetReps: targetReps, targetReps: targetReps,
targetScore: targetScore, targetScore: targetScore,
targetScoreTimeMs: targetScoreTimeMs,
restSecondsOverride: restSecondsOverride, restSecondsOverride: restSecondsOverride,
); );
} }
ProgramExerciseConfig toConfig() { ProgramExerciseConfig toConfig() {
final scoreEnabled = enabledMeasures.contains(WorkoutMeasure.score);
final stopwatchScore = scoreInputModeSnapshot == ScoreInputMode.stopwatch;
return ProgramExerciseConfig( return ProgramExerciseConfig(
existingMetadata: existingMetadata, existingMetadata: existingMetadata,
sourceExerciseId: sourceExerciseId, sourceExerciseId: sourceExerciseId,
@ -881,18 +932,26 @@ final class _ProgramExerciseDraft {
availableTimeSnapshot: availableTimeSnapshot, availableTimeSnapshot: availableTimeSnapshot,
availableRepsSnapshot: availableRepsSnapshot, availableRepsSnapshot: availableRepsSnapshot,
availableScoreSnapshot: availableScoreSnapshot, availableScoreSnapshot: availableScoreSnapshot,
scoreInputModeSnapshot: scoreInputModeSnapshot,
scoreLabelSnapshot: scoreLabelSnapshot, scoreLabelSnapshot: scoreLabelSnapshot,
scoreUnitSnapshot: scoreUnitSnapshot, scoreUnitSnapshot: scoreUnitSnapshot,
setsCount: setsCount, setsCount: setsCount,
enabledMeasures: enabledMeasures, enabledMeasures: enabledMeasures,
targetTimeSeconds: targetTimeSeconds, targetTimeSeconds: targetTimeSeconds,
targetReps: targetReps, targetReps: targetReps,
targetScore: targetScore, targetScore: scoreEnabled && !stopwatchScore ? targetScore : null,
targetScoreTimeMs: scoreEnabled && stopwatchScore
? targetScoreTimeMs
: null,
restSecondsOverride: restSecondsOverride, restSecondsOverride: restSecondsOverride,
); );
} }
} }
double? _millisecondsToSeconds(int? milliseconds) {
return milliseconds == null ? null : milliseconds / 1000;
}
String _programSummary(Program program) { String _programSummary(Program program) {
final exerciseCount = program.exercises.length; final exerciseCount = program.exercises.length;
final setsCount = program.exercises.fold<int>( final setsCount = program.exercises.fold<int>(
@ -907,6 +966,10 @@ String _exerciseSummary(_ProgramExerciseDraft draft, int defaultRestSeconds) {
final measures = draft.enabledMeasures final measures = draft.enabledMeasures
.map((measure) { .map((measure) {
final unit = draft.scoreUnitSnapshot?.trim(); final unit = draft.scoreUnitSnapshot?.trim();
if (measure == WorkoutMeasure.score &&
draft.scoreInputModeSnapshot == ScoreInputMode.stopwatch) {
return 'Score chrono';
}
if (measure == WorkoutMeasure.score && if (measure == WorkoutMeasure.score &&
unit != null && unit != null &&
unit.isNotEmpty) { unit.isNotEmpty) {

View File

@ -66,6 +66,70 @@ void main() {
expect(find.text('Score (kg)'), findsOneWidget); expect(find.text('Score (kg)'), findsOneWidget);
}); });
testWidgets('basculer en chrono intégré masque lunité et affiche le badge', (
tester,
) async {
final exerciseRepository = _FakeExerciseRepository();
await tester.pumpWidget(
MaterialApp(
home: ExerciseFormScreen(
exerciseUseCases: _exerciseUseCases(exerciseRepository),
mediaUseCases: _mediaUseCases(exerciseRepository),
),
),
);
await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Run');
await tester.dragUntilVisible(
find.text('Score'),
find.byType(ListView),
const Offset(0, -200),
);
await tester.tap(find.widgetWithText(SwitchListTile, 'Score'));
await tester.pump();
await tester.dragUntilVisible(
find.text('Unité'),
find.byType(ListView),
const Offset(0, -200),
);
expect(find.widgetWithText(TextFormField, 'Unité'), findsOneWidget);
await tester.dragUntilVisible(
find.text('Chrono intégré'),
find.byType(ListView),
const Offset(0, 200),
);
await tester.tap(find.text('Chrono intégré'));
await tester.pump();
await tester.dragUntilVisible(
find.text('Temps réalisé'),
find.byType(ListView),
const Offset(0, -200),
);
expect(find.text('Temps réalisé'), findsOneWidget);
expect(find.widgetWithText(TextFormField, 'Unité'), findsNothing);
await tester.ensureVisible(find.text('Enregistrer'));
await tester.tap(find.text('Enregistrer'));
await tester.pumpAndSettle();
final saved = exerciseRepository.saved.single;
expect(saved.scoreInputMode, ScoreInputMode.stopwatch);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(body: ExerciseListTile(exercise: saved)),
),
);
await tester.pumpAndSettle();
expect(find.text('Score chrono'), findsOneWidget);
expect(find.text('Score'), findsNothing);
});
testWidgets( testWidgets(
'sélectionner une image déclenche limport et confirme le fichier', 'sélectionner une image déclenche limport et confirme le fichier',
(tester) async { (tester) async {

View File

@ -102,6 +102,73 @@ void main() {
expect(find.widgetWithText(SwitchListTile, 'Score'), findsNothing); expect(find.widgetWithText(SwitchListTile, 'Score'), findsNothing);
}, },
); );
testWidgets('affiche lobjectif de chrono pour un score en mode chrono', (
tester,
) async {
final exerciseRepository = _FakeExerciseRepository()
..exercises.add(
Exercise(
metadata: _metadata('exercise-1'),
name: 'Suicides',
hasTimeMeasure: true,
hasRepsMeasure: true,
hasScoreMeasure: true,
scoreInputMode: ScoreInputMode.stopwatch,
scoreLabel: 'Temps réalisé',
),
);
await tester.pumpWidget(
MaterialApp(
home: ProgramFormScreen(
programUseCases: _programUseCases(
_FakeProgramRepository(),
exerciseRepository,
),
exerciseUseCases: _exerciseUseCases(exerciseRepository),
),
),
);
await tester.tap(find.text('Ajouter un exercice'));
await tester.pumpAndSettle();
await tester.tap(find.text('Suicides'));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip("Personnaliser l'exercice"));
await tester.pumpAndSettle();
expect(find.text('Score chrono'), findsOneWidget);
await tester.dragUntilVisible(
find.text(
"Temps sert d'objectif de durée ; Score chrono enregistre "
'le temps réalisé.',
),
find.byType(ListView),
const Offset(0, -200),
);
expect(
find.text(
"Temps sert d'objectif de durée ; Score chrono enregistre "
'le temps réalisé.',
),
findsOneWidget,
);
await tester.dragUntilVisible(
find.text('Objectif de chrono'),
find.byType(ListView),
const Offset(0, -200),
);
expect(
find.widgetWithText(TextFormField, 'Objectif de chrono'),
findsOneWidget,
);
expect(find.widgetWithText(TextFormField, 'Cible score'), findsNothing);
expect(
find.text('Le résultat réel sera mesuré pendant la série.'),
findsOneWidget,
);
});
} }
ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) { ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) {