Files
GameTime/test/presentation/home_screen_test.dart
Blomios 212418e062 fix(ui): bandeau de reprise de séance en cours (ticket #32)
Ajoute/corrige le bandeau de reprise sur home_screen.dart, avec les
ajustements de bootstrap applicatif nécessaires, et sa couverture de
tests. flutter analyze propre, 45/45 tests verts, build APK debug
validé. Ticket laissé en QA : validation manuelle sur APK par
l'utilisateur avant clôture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 09:52:40 +02:00

418 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 laccueil', (
tester,
) async {
final navigatorKey = GlobalKey<NavigatorState>();
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<void>(
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<NavigatorState>();
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<void>(
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(),
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(),
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 = <ActiveRestState>[];
final scoreStopwatchStates = <ActiveScoreStopwatchState>[];
final results = <ActiveSetResult>[];
@override
Future<ActiveWorkoutSession?> findById(String id) async {
return session?.metadata.id == id ? session : null;
}
@override
Future<ActiveWorkoutSession?> findOpen() async => session;
@override
Future<ActiveRestState?> findRestStateById(String id) async => null;
@override
Future<ActiveScoreStopwatchState?> findScoreStopwatchState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
}) async {
return null;
}
@override
Future<List<ActiveRestState>> listRestStates(String sessionId) async {
return restStates;
}
@override
Future<List<ActiveScoreStopwatchState>> listScoreStopwatchStates(
String sessionId,
) async {
return scoreStopwatchStates;
}
@override
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
return results;
}
@override
Future<void> save(ActiveWorkoutSession session) async {
this.session = session;
}
@override
Future<void> saveRestState(ActiveRestState restState) async {
restStates.add(restState);
}
@override
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {
scoreStopwatchStates.add(state);
}
@override
Future<void> deleteScoreStopwatchState({
required String sessionId,
required int programIndex,
required int exerciseIndex,
required int setIndex,
required DateTime deletedAt,
}) async {}
@override
Future<void> saveSetResult(ActiveSetResult result) async {
results.add(result);
}
}
final class _FakeExerciseRepository implements ExerciseRepository {
@override
Future<Exercise?> findById(String id) async => null;
@override
Future<bool> isReferencedByProgram(String id) async => false;
@override
Future<List<Exercise>> listActive() async => const [];
@override
Future<void> save(Exercise exercise) async {}
}
final class _FakeMediaAssetRepository implements MediaAssetRepository {
@override
Future<MediaAsset?> findById(String id) async => null;
@override
Future<List<MediaAsset>> listActive() async => const [];
@override
Future<void> save(MediaAsset mediaAsset) async {}
}
final class _FakeMediaStorage implements LocalMediaStorage {
@override
Future<void> deleteByLocalUri(String localUri) async {}
@override
Future<StoredMediaFile> importFile({
required String sourcePath,
required MediaKind kind,
required String stableFileName,
}) async {
return StoredMediaFile(
localUri: 'file:///$stableFileName',
mimeType: 'image/png',
sizeBytes: 0,
);
}
@override
Future<Set<String>> listManagedLocalUris() async => const {};
}
final class _FakeProgramRepository implements ProgramRepository {
@override
Future<Program?> findById(String id) async => null;
@override
Future<List<Program>> listActive() async => const [];
@override
Future<void> replaceExercises(Program program, DateTime deletedAt) async {}
@override
Future<void> save(Program program) async {}
@override
Future<void> saveExercise(ProgramExercise exercise) async {}
}
final class _FakeWorkoutTemplateRepository
implements WorkoutTemplateRepository {
@override
Future<WorkoutTemplate?> findById(String id) async => null;
@override
Future<List<WorkoutTemplate>> listActive() async => const [];
@override
Future<void> replaceComposition(
WorkoutTemplate template,
DateTime deletedAt,
) async {}
@override
Future<void> save(WorkoutTemplate template) async {}
@override
Future<void> saveOverride(WorkoutTemplateExerciseOverride override) async {}
@override
Future<void> saveProgram(WorkoutTemplateProgram program) async {}
}
final class _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository {
@override
Future<void> delete(String id, DateTime deletedAt) async {}
@override
Future<WorkoutHistory?> findById(String id) async => null;
@override
Future<List<WorkoutHistory>> listActive() async => const [];
@override
Future<void> save(WorkoutHistory history) async {}
@override
Future<void> saveSetResult(WorkoutHistorySetResult result) async {}
}