import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:gametime/application/application.dart'; import 'package:gametime/application/app_bootstrap.dart'; import 'package:gametime/domain/domain.dart'; import 'package:gametime/presentation/home_screen.dart'; void main() { testWidgets('le bandeau de reprise apparaît au retour sur l’accueil', ( tester, ) async { final navigatorKey = GlobalKey(); final activeRepository = _FakeActiveSessionRepository(); await tester.pumpWidget( MaterialApp( navigatorKey: navigatorKey, navigatorObservers: [homeRouteObserver], home: HomeScreen(bootstrap: _FakeBootstrap(activeRepository)), ), ); await tester.pump(); await tester.pump(); expect(find.textContaining('Séance en cours'), findsNothing); unawaited( navigatorKey.currentState!.push( MaterialPageRoute( builder: (context) => const Scaffold(body: Text('Séance active')), ), ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); activeRepository.session = ActiveWorkoutSession( metadata: _metadata('session-1'), sourceWorkoutTemplateId: 'template-1', status: ActiveWorkoutStatus.paused, startedAt: DateTime.utc(2026, 7, 17, 12), pausedAt: DateTime.utc(2026, 7, 17, 12, 5), lastPersistedAt: DateTime.utc(2026, 7, 17, 12, 5), elapsedActiveMs: 300000, currentProgramIndex: 0, currentExerciseIndex: 0, currentSetIndex: 1, resolvedTemplateSnapshotJson: _sessionSnapshot(), ); navigatorKey.currentState!.pop(); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); await tester.pump(); expect( find.text('Séance en cours — Reprendre · Squat, série 2/3'), findsOneWidget, ); }); testWidgets('le bandeau de reprise disparaît au retour après abandon', ( tester, ) async { final navigatorKey = GlobalKey(); final activeRepository = _FakeActiveSessionRepository() ..session = ActiveWorkoutSession( metadata: _metadata('session-1'), sourceWorkoutTemplateId: 'template-1', status: ActiveWorkoutStatus.paused, startedAt: DateTime.utc(2026, 7, 17, 12), pausedAt: DateTime.utc(2026, 7, 17, 12, 5), lastPersistedAt: DateTime.utc(2026, 7, 17, 12, 5), elapsedActiveMs: 300000, currentProgramIndex: 0, currentExerciseIndex: 0, currentSetIndex: 1, resolvedTemplateSnapshotJson: _sessionSnapshot(), ); await tester.pumpWidget( MaterialApp( navigatorKey: navigatorKey, navigatorObservers: [homeRouteObserver], home: HomeScreen(bootstrap: _FakeBootstrap(activeRepository)), ), ); await tester.pump(); await tester.pump(); expect(find.textContaining('Séance en cours'), findsOneWidget); unawaited( navigatorKey.currentState!.push( MaterialPageRoute( builder: (context) => const Scaffold(body: Text('Séance active')), ), ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); activeRepository.session = null; navigatorKey.currentState!.pop(); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); await tester.pump(); expect(find.textContaining('Séance en cours'), findsNothing); }); } final class _FakeBootstrap implements AppDependencies { _FakeBootstrap(_FakeActiveSessionRepository activeRepository) : activeWorkoutSessionUseCases = ActiveWorkoutSessionUseCases( sessionRepository: activeRepository, templateRepository: _FakeWorkoutTemplateRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ), closeWorkoutSessionUseCase = CloseWorkoutSessionUseCase( sessionRepository: activeRepository, historyRepository: _FakeWorkoutHistoryRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ), exerciseUseCases = ExerciseUseCases( repository: _FakeExerciseRepository(), programRepository: _FakeProgramRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ), mediaUseCases = MediaUseCases( mediaRepository: _FakeMediaAssetRepository(), exerciseRepository: _FakeExerciseRepository(), storage: _FakeMediaStorage(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ), programUseCases = ProgramUseCases( programRepository: _FakeProgramRepository(), exerciseRepository: _FakeExerciseRepository(), templateRepository: _FakeWorkoutTemplateRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ), workoutTemplateUseCases = WorkoutTemplateUseCases( templateRepository: _FakeWorkoutTemplateRepository(), programRepository: _FakeProgramRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ids: _FakeIds(), originDeviceId: 'device-1', ), workoutHistoryUseCases = WorkoutHistoryUseCases( repository: _FakeWorkoutHistoryRepository(), clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)), ); @override final ExerciseUseCases exerciseUseCases; @override final MediaUseCases mediaUseCases; @override final ProgramUseCases programUseCases; @override final WorkoutTemplateUseCases workoutTemplateUseCases; @override final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases; @override final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase; @override final WorkoutHistoryUseCases workoutHistoryUseCases; } String _sessionSnapshot() { return jsonEncode({ 'name': 'Séance jambes', 'programs': [ { 'id': 'template-program-1', 'programNameSnapshot': 'Programme jambes', 'programSnapshotJson': jsonEncode({ 'exercises': [ { 'id': 'exercise-1', 'exerciseNameSnapshot': 'Squat', 'setsCount': 3, 'timeEnabled': true, 'repsEnabled': false, 'scoreEnabled': false, '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', ); } final class _FakeClock implements Clock { const _FakeClock(this.value); final DateTime value; @override DateTime now() => value; } final class _FakeIds implements IdGenerator { var _next = 0; @override String newId() { _next += 1; return 'id-$_next'; } } final class _FakeActiveSessionRepository implements ActiveSessionRepository { ActiveWorkoutSession? session; final restStates = []; final scoreStopwatchStates = []; final results = []; @override Future findById(String id) async { return session?.metadata.id == id ? session : null; } @override Future findOpen() async => session; @override Future findRestStateById(String id) async => null; @override Future findScoreStopwatchState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, }) async { return null; } @override Future> listRestStates(String sessionId) async { return restStates; } @override Future> listScoreStopwatchStates( String sessionId, ) async { return scoreStopwatchStates; } @override Future> listSetResults(String sessionId) async { return results; } @override Future save(ActiveWorkoutSession session) async { this.session = session; } @override Future saveRestState(ActiveRestState restState) async { restStates.add(restState); } @override Future saveScoreStopwatchState(ActiveScoreStopwatchState state) async { scoreStopwatchStates.add(state); } @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 { results.add(result); } } final class _FakeExerciseRepository implements ExerciseRepository { @override Future findById(String id) async => null; @override Future isReferencedByProgram(String id) async => false; @override Future> listActive() async => const []; @override Future save(Exercise exercise) async {} } final class _FakeMediaAssetRepository implements MediaAssetRepository { @override Future findById(String id) async => null; @override Future> listActive() async => const []; @override Future save(MediaAsset mediaAsset) async {} } final class _FakeMediaStorage implements LocalMediaStorage { @override Future deleteByLocalUri(String localUri) async {} @override Future importFile({ required String sourcePath, required MediaKind kind, required String stableFileName, }) async { return StoredMediaFile( localUri: 'file:///$stableFileName', mimeType: 'image/png', sizeBytes: 0, ); } @override Future> listManagedLocalUris() async => const {}; } 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 _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 _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository { @override Future delete(String id, DateTime deletedAt) async {} @override Future findById(String id) async => null; @override Future> listActive() async => const []; @override Future save(WorkoutHistory history) async {} @override Future saveSetResult(WorkoutHistorySetResult result) async {} }