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('le temps affiché est recalculé depuis les horodatages', ( tester, ) async { final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12, 0, 15)); final session = ActiveWorkoutSession( metadata: _metadata('session-1'), sourceWorkoutTemplateId: 'template-1', status: ActiveWorkoutStatus.running, startedAt: DateTime.utc(2026, 7, 17, 11, 59), lastPersistedAt: DateTime.utc(2026, 7, 17, 12), elapsedActiveMs: 30000, currentProgramIndex: 0, currentExerciseIndex: 0, currentSetIndex: 0, resolvedTemplateSnapshotJson: _sessionSnapshot(), ); await tester.pumpWidget( MaterialApp( home: WorkoutExecutionScreen( initialSession: session, activeUseCases: _activeUseCases( _FakeActiveSessionRepository(), clock, ), closeUseCase: _closeUseCase(_FakeActiveSessionRepository(), clock), ), ), ); expect(find.text('00:45'), findsOneWidget); }); testWidgets('les mesures cumulées respectent la hiérarchie visuelle', ( tester, ) async { final scoreController = TextEditingController(); addTearDown(scoreController.dispose); await tester.pumpWidget( MaterialApp( home: Scaffold( body: SetMeasureInput( exercise: const ExecutionExercise( id: 'exercise-1', name: 'Squat', setsCount: 3, timeEnabled: true, repsEnabled: true, scoreEnabled: true, restSeconds: 0, scoreUnit: 'kg', ), reps: 0, scoreController: scoreController, onRepsChanged: (_) {}, ), ), ), ); final timeY = tester.getTopLeft(find.text('Temps')).dy; final repsY = tester.getTopLeft(find.text('Répétitions')).dy; final scoreY = tester.getTopLeft(find.text('Score (kg)')).dy; expect(timeY, lessThan(repsY)); expect(repsY, lessThan(scoreY)); }); testWidgets('aucun repos ne s’affiche après la toute dernière série', ( tester, ) async { final plan = WorkoutExecutionPlan.fromSession( 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: 1, restSeconds: 60, ), ), ); const position = ExecutionPosition( programIndex: 0, exerciseIndex: 0, setIndex: 0, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Text( plan.shouldShowRestAfter(position) ? 'Repos' : 'Pas de repos', ), ), ); expect(find.text('Pas de repos'), findsOneWidget); }); } ActiveWorkoutSessionUseCases _activeUseCases( _FakeActiveSessionRepository repository, _FakeClock clock, ) { return ActiveWorkoutSessionUseCases( sessionRepository: repository, templateRepository: _FakeWorkoutTemplateRepository(), clock: clock, ids: _FakeIds(), originDeviceId: 'device-1', ); } CloseWorkoutSessionUseCase _closeUseCase( _FakeActiveSessionRepository repository, _FakeClock clock, ) { return CloseWorkoutSessionUseCase( sessionRepository: repository, historyRepository: _FakeWorkoutHistoryRepository(), clock: clock, ids: _FakeIds(), originDeviceId: 'device-1', ); } String _sessionSnapshot({int setsCount = 3, int restSeconds = 0}) { return jsonEncode({ 'name': 'Séance jambes', 'programs': [ { 'id': 'template-program-1', 'programNameSnapshot': 'Programme jambes', 'programSnapshotJson': jsonEncode({ 'exercises': [ { 'id': 'exercise-1', 'exerciseNameSnapshot': 'Squat', 'setsCount': setsCount, 'timeEnabled': true, 'repsEnabled': true, 'scoreEnabled': true, 'targetTimeSeconds': 45, 'targetReps': 10, 'targetScore': 80, 'scoreUnitSnapshot': 'kg', 'restSecondsOverride': restSeconds, }, ], }), }, ], '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 { _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 _FakeActiveSessionRepository implements ActiveSessionRepository { ActiveWorkoutSession? session; final results = []; @override Future findById(String id) async { return session?.metadata.id == id ? session : null; } @override Future findOpen() async => session; @override Future> listRestStates(String sessionId) async { return const []; } @override Future> listSetResults(String sessionId) async { return results; } @override Future save(ActiveWorkoutSession session) async { this.session = session; } @override Future saveRestState(ActiveRestState restState) async {} @override Future saveSetResult(ActiveSetResult result) async { results.add(result); } } 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 save(WorkoutHistory history) async {} @override Future saveSetResult(WorkoutHistorySetResult result) async {} }