fix(execution): reprise en temps réel de la séance en cours (ticket #14)

Corrige la reprise de séance sur workout_execution_screen.dart suite
aux retours de QA. flutter analyze propre, 26/26 tests verts, build
APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 22:18:24 +02:00
parent 586813e76f
commit 576238b490
2 changed files with 201 additions and 14 deletions

View File

@ -115,6 +115,108 @@ void main() {
expect(find.text('Pas de repos'), findsOneWidget);
});
testWidgets('un repos actif est repris avec le temps restant recalculé', (
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: 0,
resolvedTemplateSnapshotJson: _sessionSnapshot(
setsCount: 2,
restSeconds: 60,
),
);
repository.session = session;
repository.restStates.add(
ActiveRestState(
metadata: _metadata('rest-1'),
activeWorkoutSessionId: 'session-1',
afterProgramIndex: 0,
afterExerciseIndex: 0,
afterSetIndex: 0,
plannedRestSeconds: 60,
adjustedRestSeconds: 60,
startedAt: DateTime.now().toUtc().subtract(const Duration(seconds: 15)),
),
);
await tester.pumpWidget(
MaterialApp(
home: WorkoutExecutionScreen(
initialSession: session,
activeUseCases: _activeUseCases(repository, clock),
closeUseCase: _closeUseCase(repository, clock),
historyUseCases: _historyUseCases(clock),
workoutTemplateUseCases: _workoutTemplateUseCases(),
),
),
);
await tester.pump();
expect(find.text('Repos avant la prochaine série'), findsOneWidget);
expect(find.text('00:45'), findsOneWidget);
await tester.tap(find.text('+15 s'));
await tester.pump();
expect(find.text('01:00'), findsOneWidget);
expect(repository.restStates.single.adjustedRestSeconds, 75);
await tester.tap(find.text('Ignorer le repos'));
await tester.pump();
expect(repository.restStates.single.skippedAt, isNotNull);
});
testWidgets('la mesure temps enregistre une durée réelle de série', (
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: 0,
resolvedTemplateSnapshotJson: _sessionSnapshot(
setsCount: 2,
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.pump(const Duration(seconds: 2));
await tester.tap(find.text('Terminer la série'));
await tester.pump();
expect(repository.results.single.actualTimeMs, greaterThan(0));
});
}
ActiveWorkoutSessionUseCases _activeUseCases(
@ -221,6 +323,7 @@ final class _FakeIds implements IdGenerator {
final class _FakeActiveSessionRepository implements ActiveSessionRepository {
ActiveWorkoutSession? session;
final results = <ActiveSetResult>[];
final restStates = <ActiveRestState>[];
@override
Future<ActiveWorkoutSession?> findById(String id) async {
@ -231,11 +334,20 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
Future<ActiveWorkoutSession?> findOpen() async => session;
@override
Future<ActiveRestState?> findRestStateById(String id) async => null;
Future<ActiveRestState?> findRestStateById(String id) async {
for (final restState in restStates) {
if (restState.metadata.id == id) {
return restState;
}
}
return null;
}
@override
Future<List<ActiveRestState>> listRestStates(String sessionId) async {
return const [];
return restStates
.where((restState) => restState.activeWorkoutSessionId == sessionId)
.toList();
}
@override
@ -249,7 +361,16 @@ final class _FakeActiveSessionRepository implements ActiveSessionRepository {
}
@override
Future<void> saveRestState(ActiveRestState restState) async {}
Future<void> saveRestState(ActiveRestState restState) async {
final index = restStates.indexWhere(
(saved) => saved.metadata.id == restState.metadata.id,
);
if (index == -1) {
restStates.add(restState);
} else {
restStates[index] = restState;
}
}
@override
Future<void> saveSetResult(ActiveSetResult result) async {