feat(ui): édition ponctuelle des séries en séance (ticket #23)

Expose l'édition ponctuelle des séries (backend du ticket #21) sur
workout_execution_screen.dart, sans impacter le modèle de séance.
flutter analyze propre, 33/33 tests verts, build APK debug validé.
Clôture le lot navigation libre / programme simplifié (tickets #20 à #23).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 00:37:57 +02:00
parent 104d809dd6
commit c2f807e588
2 changed files with 441 additions and 6 deletions

View File

@ -280,6 +280,123 @@ void main() {
expect(find.text('Plan de séance'), findsNothing);
});
testWidgets(
'modifier une série passée la termine sans changer la position courante',
(tester) async {
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final repository = _FakeActiveSessionRepository();
final session = ActiveWorkoutSession(
metadata: _metadata('session-1'),
sourceWorkoutTemplateId: 'template-1',
status: ActiveWorkoutStatus.running,
startedAt: DateTime.utc(2026, 7, 17, 12),
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
elapsedActiveMs: 0,
currentProgramIndex: 0,
currentExerciseIndex: 0,
currentSetIndex: 2,
resolvedTemplateSnapshotJson: _sessionSnapshot(
setsCount: 4,
restSeconds: 0,
),
);
repository.session = session;
await tester.pumpWidget(
MaterialApp(
home: WorkoutExecutionScreen(
initialSession: session,
activeUseCases: _activeUseCases(repository, clock),
closeUseCase: _closeUseCase(repository, clock),
historyUseCases: _historyUseCases(clock),
workoutTemplateUseCases: _workoutTemplateUseCases(),
),
),
);
await tester.tap(find.byTooltip('Voir le plan'));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(ListTile, 'Série 2'));
await tester.pumpAndSettle();
expect(find.text('Modifier la série'), findsOneWidget);
await tester.enterText(_textFieldWithLabel('Répétitions'), '12');
await tester.ensureVisible(find.text('Enregistrer le résultat'));
await tester.tap(find.text('Enregistrer le résultat'));
await tester.pumpAndSettle();
expect(repository.session?.currentSetIndex, 2);
expect(repository.results.single.status, SetResultStatus.completed);
expect(repository.results.single.actualReps, 12);
expect(find.text('Série mise à jour'), findsWidgets);
expect(find.text('12 reps'), findsOneWidget);
await tester.tap(find.byTooltip('Fermer'));
await tester.pumpAndSettle();
expect(
find.text('Programme 1/1 · Exercice 1/1 · Série 3/4'),
findsOneWidget,
);
},
);
testWidgets(
'toucher la série courante dans le plan revient à lécran principal',
(tester) async {
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final repository = _FakeActiveSessionRepository();
final session = ActiveWorkoutSession(
metadata: _metadata('session-1'),
sourceWorkoutTemplateId: 'template-1',
status: ActiveWorkoutStatus.running,
startedAt: DateTime.utc(2026, 7, 17, 12),
lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
elapsedActiveMs: 0,
currentProgramIndex: 0,
currentExerciseIndex: 0,
currentSetIndex: 1,
resolvedTemplateSnapshotJson: _sessionSnapshot(
setsCount: 3,
restSeconds: 0,
),
);
repository.session = session;
await tester.pumpWidget(
MaterialApp(
home: WorkoutExecutionScreen(
initialSession: session,
activeUseCases: _activeUseCases(repository, clock),
closeUseCase: _closeUseCase(repository, clock),
historyUseCases: _historyUseCases(clock),
workoutTemplateUseCases: _workoutTemplateUseCases(),
),
),
);
await tester.tap(find.byTooltip('Voir le plan'));
await tester.pumpAndSettle();
await tester.tap(find.text('En cours'));
await tester.pumpAndSettle();
expect(find.text('Plan de séance'), findsNothing);
expect(find.text('Modifier la série'), findsNothing);
expect(
find.text('Programme 1/1 · Exercice 1/1 · Série 2/3'),
findsOneWidget,
);
},
);
}
Finder _textFieldWithLabel(String label) {
return find.byWidgetPredicate(
(widget) => widget is TextField && widget.decoration?.labelText == label,
description: 'TextField with label "$label"',
);
}
ActiveWorkoutSessionUseCases _activeUseCases(
@ -437,7 +554,18 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
@override
Future<void> saveSetResult(ActiveSetResult result) async {
results.add(result);
final index = results.indexWhere(
(saved) =>
saved.activeWorkoutSessionId == result.activeWorkoutSessionId &&
saved.programIndex == result.programIndex &&
saved.exerciseIndex == result.exerciseIndex &&
saved.setIndex == result.setIndex,
);
if (index == -1) {
results.add(result);
} else {
results[index] = result;
}
}
}