chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)
Regroupe l'état de travail en cours réalisé dans un même worktree sur plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164), mélangeant des tickets QA et inProgress. Ne constitue pas une feature terminée : commit de sauvegarde avant triage/split par ticket en branches feature/* dédiées. Exclut les dossiers d'environnement de build locaux et le heap dump parasite (.gitignore mis à jour). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -895,6 +895,81 @@ void main() {
|
||||
expect(repository.restStates.single.skippedAt, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('les mutations externes de repos resynchronisent l’écran', (
|
||||
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;
|
||||
|
||||
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'), findsNothing);
|
||||
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(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Repos avant la prochaine série'), findsOneWidget);
|
||||
|
||||
repository.restStates.clear();
|
||||
repository.session = ActiveWorkoutSession(
|
||||
metadata: session.metadata,
|
||||
sourceWorkoutTemplateId: session.sourceWorkoutTemplateId,
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: session.startedAt,
|
||||
lastPersistedAt: session.lastPersistedAt,
|
||||
elapsedActiveMs: session.elapsedActiveMs,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 1,
|
||||
resolvedTemplateSnapshotJson: session.resolvedTemplateSnapshotJson,
|
||||
);
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Repos avant la prochaine série'), findsNothing);
|
||||
expect(find.text('Terminer la série'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('un repos en pause est repris sans compter la durée de pause', (
|
||||
tester,
|
||||
) async {
|
||||
@ -1629,6 +1704,139 @@ void main() {
|
||||
expect(find.byType(SingleChildScrollView), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('répétitions et chrono score d’étape tiennent sans overflow', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.binding.setSurfaceSize(const Size(400, 700));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
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(
|
||||
targetReps: 12,
|
||||
exerciseSteps: [
|
||||
_step(
|
||||
'step-1',
|
||||
0,
|
||||
'Pompes tempo',
|
||||
ExerciseStepType.reps,
|
||||
12,
|
||||
hasScore: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
stepUseCases: _stepUseCases(repository, clock),
|
||||
stepAudioCuePlayer: _FakeStepAudioCuePlayer(),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(find.text('RÉPÉTITIONS'), findsOneWidget);
|
||||
expect(find.text('CHRONO SCORE'), findsOneWidget);
|
||||
expect(find.byTooltip('Lancer'), findsOneWidget);
|
||||
expect(find.byTooltip('Réinitialiser'), findsOneWidget);
|
||||
expect(
|
||||
find.widgetWithText(OutlinedButton, 'Passer l’étape'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.widgetWithText(FilledButton, 'Étape suivante'), findsOneWidget);
|
||||
expect(find.widgetWithText(TextButton, 'Réinitialiser'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('chrono décompte et score libre tiennent avec stepper compact', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.binding.setSurfaceSize(const Size(400, 700));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
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(
|
||||
timeEnabled: false,
|
||||
repsEnabled: false,
|
||||
scoreEnabled: false,
|
||||
exerciseSteps: [
|
||||
_step(
|
||||
'step-1',
|
||||
0,
|
||||
'Maintien défensif',
|
||||
ExerciseStepType.time,
|
||||
10,
|
||||
hasScore: true,
|
||||
scoreInputMode: ScoreInputMode.manual,
|
||||
scoreLabel: 'Paniers',
|
||||
scoreUnit: 'points',
|
||||
defaultTargetScore: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
repository.session = session;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: WorkoutExecutionScreen(
|
||||
initialSession: session,
|
||||
activeUseCases: _activeUseCases(repository, clock),
|
||||
stepUseCases: _stepUseCases(repository, clock),
|
||||
stepAudioCuePlayer: _FakeStepAudioCuePlayer(),
|
||||
closeUseCase: _closeUseCase(repository, clock),
|
||||
historyUseCases: _historyUseCases(clock),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(find.text('CHRONO'), findsOneWidget);
|
||||
expect(find.text('SCORE'), findsOneWidget);
|
||||
expect(find.text('00:10'), findsOneWidget);
|
||||
expect(find.text('5'), findsOneWidget);
|
||||
expect(find.byTooltip('Diminuer'), findsOneWidget);
|
||||
expect(find.byTooltip('Augmenter'), findsOneWidget);
|
||||
expect(find.widgetWithText(TextField, 'Score de l’étape'), findsNothing);
|
||||
expect(
|
||||
find.widgetWithText(OutlinedButton, 'Passer l’étape'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.widgetWithText(FilledButton, 'Étape suivante'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('l’écran actif avec séquence longue reste sans scroll', (
|
||||
tester,
|
||||
) async {
|
||||
@ -1790,6 +1998,24 @@ void main() {
|
||||
|
||||
expect(find.text('00:03'), findsOneWidget);
|
||||
expect(audio.shortBeeps, 1);
|
||||
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 3);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('00:02'), findsOneWidget);
|
||||
expect(audio.shortBeeps, 2);
|
||||
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 4);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('00:01'), findsOneWidget);
|
||||
expect(audio.shortBeeps, 3);
|
||||
|
||||
clock.value = DateTime.utc(2026, 7, 17, 12, 0, 5, 200);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.pump();
|
||||
|
||||
expect(audio.longBeeps, 1);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@ -2727,6 +2953,9 @@ final class _FakeWorkoutTemplateRepository
|
||||
@override
|
||||
Future<void> save(WorkoutTemplate template) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveAll(List<WorkoutTemplate> templates) async {}
|
||||
|
||||
@override
|
||||
Future<void> saveOverride(WorkoutTemplateExerciseOverride override) async {}
|
||||
|
||||
@ -2750,8 +2979,11 @@ final class _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository {
|
||||
@override
|
||||
Future<void> patchHeartRateSummary({
|
||||
required String historyId,
|
||||
int? minHeartRateBpm,
|
||||
required double averageHeartRateBpm,
|
||||
required int maxHeartRateBpm,
|
||||
double? totalDistanceMeters,
|
||||
double? totalCaloriesKcal,
|
||||
required DateTime patchedAt,
|
||||
}) async {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user