import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:gametime/application/application.dart'; import 'package:gametime/domain/domain.dart'; import 'package:gametime/presentation/presentation.dart'; void main() { testWidgets('la liste groupe les séances par période', (tester) async { final groups = groupHistoriesByPeriod([ _history(id: 'today', startedAt: DateTime.utc(2026, 7, 17, 9)), _history(id: 'week', startedAt: DateTime.utc(2026, 7, 15, 9)), _history(id: 'old', startedAt: DateTime.utc(2026, 7, 1, 9)), ], DateTime.utc(2026, 7, 17, 12)); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Column(children: [for (final key in groups.keys) Text(key)]), ), ), ); expect(find.text("Aujourd'hui"), findsOneWidget); expect(find.text('Cette semaine'), findsOneWidget); expect(find.text('Plus ancien'), findsOneWidget); }); testWidgets('le détail affiche temps répétitions et score avec unité', ( tester, ) async { await tester.pumpWidget( MaterialApp( home: HistoryDetailScreen( history: _history(id: 'history-1'), historyUseCases: _historyUseCases(_FakeWorkoutHistoryRepository()), workoutTemplateUseCases: _workoutTemplateUseCases(), activeUseCases: _activeUseCases(), closeUseCase: _closeUseCase(), ), ), ); expect(find.text('Programme jambes'), findsOneWidget); expect(find.text('Squat'), findsOneWidget); expect( find.text('Temps 00:45 · 10 répétitions · Score 80 kg'), findsOneWidget, ); }); testWidgets('le détail affiche le score chrono comme temps réalisé', ( tester, ) async { await tester.pumpWidget( MaterialApp( home: HistoryDetailScreen( history: _history(id: 'history-1', stopwatchScore: true), historyUseCases: _historyUseCases(_FakeWorkoutHistoryRepository()), workoutTemplateUseCases: _workoutTemplateUseCases(), activeUseCases: _activeUseCases(), closeUseCase: _closeUseCase(), ), ), ); expect( find.text( 'Temps 00:45 · 10 répétitions · ' 'Temps réalisé : 00:38.7 / objectif 00:45', ), findsOneWidget, ); expect(find.textContaining('Score 38.7'), findsNothing); }); testWidgets('la suppression demande une confirmation', (tester) async { final repository = _FakeWorkoutHistoryRepository(); await tester.pumpWidget( MaterialApp( home: HistoryDetailScreen( history: _history(id: 'history-1'), historyUseCases: _historyUseCases(repository), workoutTemplateUseCases: _workoutTemplateUseCases(), activeUseCases: _activeUseCases(), closeUseCase: _closeUseCase(), ), ), ); await tester.tap(find.text("Supprimer de l'historique")); await tester.pumpAndSettle(); expect(repository.deletedIds, isEmpty); expect(find.text("Supprimer de l'historique ?"), findsOneWidget); await tester.tap(find.text('Annuler')); await tester.pumpAndSettle(); expect(repository.deletedIds, isEmpty); await tester.tap(find.text("Supprimer de l'historique")); await tester.pumpAndSettle(); await tester.tap(find.widgetWithText(FilledButton, 'Supprimer')); await tester.pumpAndSettle(); expect(repository.deletedIds, ['history-1']); }); } WorkoutHistory _history({ required String id, DateTime? startedAt, bool stopwatchScore = false, }) { final start = startedAt ?? DateTime.utc(2026, 7, 17, 10); return WorkoutHistory( metadata: _metadata(id), sourceWorkoutTemplateId: 'template-1', sourceActiveWorkoutSessionId: 'session-1', nameSnapshot: 'Séance jambes', startedAt: start, endedAt: start.add(const Duration(minutes: 30)), totalActiveMs: 1800000, completed: true, historySnapshotJson: jsonEncode({ 'sessionId': 'session-1', 'resolvedTemplateSnapshotJson': _resolvedSnapshot( stopwatchScore: stopwatchScore, ), 'results': [ { 'programSnapshotId': 'program-snapshot-1', 'exerciseSnapshotId': 'exercise-snapshot-1', 'programIndex': 0, 'exerciseIndex': 0, 'setIndex': 0, 'actualTimeMs': 45000, 'actualReps': 10, if (stopwatchScore) 'actualScoreTimeMs': 38700, if (!stopwatchScore) 'actualScore': 80, 'scoreLabelSnapshot': 'Charge', 'scoreInputModeSnapshot': stopwatchScore ? ScoreInputMode.stopwatch.name : ScoreInputMode.manual.name, if (!stopwatchScore) 'scoreUnitSnapshot': 'kg', if (stopwatchScore) 'targetScoreTimeMsSnapshot': 45000, }, ], }), ); } String _resolvedSnapshot({bool stopwatchScore = false}) { return jsonEncode({ 'name': 'Séance jambes', 'programs': [ { 'id': 'program-snapshot-1', 'programNameSnapshot': 'Programme jambes', 'programSnapshotJson': jsonEncode({ 'exercises': [ { 'id': 'exercise-snapshot-1', 'exerciseNameSnapshot': 'Squat', 'setsCount': 1, 'timeEnabled': true, 'repsEnabled': true, 'scoreEnabled': true, 'targetTimeSeconds': 45, 'targetReps': 10, if (stopwatchScore) 'targetScoreTimeMs': 45000, if (!stopwatchScore) 'targetScore': 80, 'scoreInputModeSnapshot': stopwatchScore ? ScoreInputMode.stopwatch.name : ScoreInputMode.manual.name, 'scoreLabelSnapshot': 'Charge', if (!stopwatchScore) 'scoreUnitSnapshot': 'kg', 'restSecondsOverride': 0, }, ], }), }, ], 'overrides': const [], }); } EntityMetadata _metadata(String id) { return EntityMetadata( id: id, createdAt: DateTime.utc(2026, 7, 17), updatedAt: DateTime.utc(2026, 7, 17), originDeviceId: 'device-1', ); } WorkoutHistoryUseCases _historyUseCases( _FakeWorkoutHistoryRepository repository, ) { return WorkoutHistoryUseCases( repository: repository, clock: _FakeClock(DateTime.utc(2026, 7, 17)), ); } WorkoutTemplateUseCases _workoutTemplateUseCases() { return WorkoutTemplateUseCases( templateRepository: _FakeWorkoutTemplateRepository(), programRepository: _FakeProgramRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17)), ids: _FakeIds(), originDeviceId: 'device-1', ); } ActiveWorkoutSessionUseCases _activeUseCases() { return ActiveWorkoutSessionUseCases( sessionRepository: _FakeActiveSessionRepository(), templateRepository: _FakeWorkoutTemplateRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17)), ids: _FakeIds(), originDeviceId: 'device-1', ); } CloseWorkoutSessionUseCase _closeUseCase() { return CloseWorkoutSessionUseCase( sessionRepository: _FakeActiveSessionRepository(), historyRepository: _FakeWorkoutHistoryRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17)), ids: _FakeIds(), originDeviceId: 'device-1', ); } final class _FakeClock implements Clock { _FakeClock(this.value); DateTime value; @override DateTime now() => value; } final class _FakeIds implements IdGenerator { var _next = 0; @override String newId() { _next += 1; return 'id-$_next'; } } final class _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository { final deletedIds = []; @override Future delete(String id, DateTime deletedAt) async { deletedIds.add(id); } @override Future findById(String id) async => null; @override Future> listActive() async => const []; @override Future save(WorkoutHistory history) async {} @override Future saveSetResult(WorkoutHistorySetResult result) async {} } final class _FakeWorkoutTemplateRepository implements WorkoutTemplateRepository { @override Future findById(String id) async => null; @override Future> listActive() async => const []; @override Future replaceComposition( WorkoutTemplate template, DateTime deletedAt, ) async {} @override Future save(WorkoutTemplate template) async {} @override Future saveOverride(WorkoutTemplateExerciseOverride override) async {} @override Future saveProgram(WorkoutTemplateProgram program) async {} } final class _FakeProgramRepository implements ProgramRepository { @override Future findById(String id) async => null; @override Future> listActive() async => const []; @override Future replaceExercises(Program program, DateTime deletedAt) async {} @override Future save(Program program) async {} @override Future saveExercise(ProgramExercise exercise) async {} } final class _FakeActiveSessionRepository implements ActiveSessionRepository { @override Future findById(String id) async => null; @override Future findOpen() async => null; @override Future findRestStateById(String id) async => null; @override Future findScoreStopwatchState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, }) async => null; @override Future> listRestStates(String sessionId) async { return const []; } @override Future> listScoreStopwatchStates( String sessionId, ) async { return const []; } @override Future> listSetResults(String sessionId) async { return const []; } @override Future save(ActiveWorkoutSession session) async {} @override Future saveRestState(ActiveRestState restState) async {} @override Future saveScoreStopwatchState(ActiveScoreStopwatchState state) async {} @override Future deleteScoreStopwatchState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, required DateTime deletedAt, }) async {} @override Future saveSetResult(ActiveSetResult result) async {} }