Files
GameTime/test/presentation/workout_template_screen_test.dart
Blomios 82226677a9 fix(ui): popup séance déjà en cours (ticket #33)
Corrige le popup averti quand une séance est déjà en cours sur
workout_template_screen.dart. flutter analyze propre, 46/46 tests
verts, build APK debug validé. Ticket laissé en QA : validation
manuelle sur APK par l'utilisateur avant clôture. Clôture le sprint
Bug resolution (tickets #24, #29, #31, #32, #33).

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

346 lines
9.1 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: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 vue programme de séance ne permet pas de changer les mesures actives',
(tester) async {
final program = WorkoutTemplateProgramDraft(
clientKey: 'program-1',
programNameSnapshot: 'Jambes',
defaultRestSecondsSnapshot: 60,
programSnapshotJson: '{}',
exercises: [
WorkoutTemplateExerciseDraft(
snapshotExerciseId: 'exercise-1',
exerciseNameSnapshot: 'Squat',
setsCount: 3,
timeEnabled: true,
repsEnabled: true,
scoreEnabled: false,
targetTimeSeconds: 45,
targetReps: 10,
),
],
);
await tester.pumpWidget(
MaterialApp(home: WorkoutTemplateProgramDetailScreen(program: program)),
);
expect(find.byType(CheckboxListTile), findsNothing);
expect(find.text('Mesures à suivre'), findsNothing);
await tester.enterText(find.byType(TextFormField).first, '5');
await tester.pump();
expect(program.exercises.single.setsCountOverride, 5);
expect(find.text('5 séries · Temps + Répétitions'), findsOneWidget);
},
);
testWidgets(
'lancer une séance alors quune autre est en cours affiche un message',
(tester) async {
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final templateRepository = _FakeWorkoutTemplateRepository([
_workoutTemplate(),
]);
final activeRepository = _FakeActiveSessionRepository()
..session = _activeSession();
final programRepository = _FakeProgramRepository();
final historyRepository = _FakeWorkoutHistoryRepository();
await tester.pumpWidget(
MaterialApp(
home: WorkoutTemplateListScreen(
workoutTemplateUseCases: WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: programRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
programUseCases: ProgramUseCases(
programRepository: programRepository,
exerciseRepository: _FakeExerciseRepository(),
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
activeUseCases: ActiveWorkoutSessionUseCases(
sessionRepository: activeRepository,
templateRepository: templateRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
closeUseCase: CloseWorkoutSessionUseCase(
sessionRepository: activeRepository,
historyRepository: historyRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
historyUseCases: WorkoutHistoryUseCases(
repository: historyRepository,
clock: clock,
),
),
),
);
await tester.pump();
await tester.pump();
await tester.tap(find.text('Lancer'));
await tester.pump();
await tester.pump();
expect(
find.textContaining('Une séance est déjà en cours.'),
findsOneWidget,
);
expect(activeRepository.saveCount, 0);
},
);
}
WorkoutTemplate _workoutTemplate() {
return WorkoutTemplate(
metadata: _metadata('template-1'),
name: 'Séance jambes',
programs: [
WorkoutTemplateProgram(
metadata: _metadata('template-program-1'),
workoutTemplateId: 'template-1',
position: 0,
programNameSnapshot: 'Programme jambes',
defaultRestSecondsSnapshot: 60,
programSnapshotJson: _programSnapshotJson(),
),
],
);
}
ActiveWorkoutSession _activeSession() {
return ActiveWorkoutSession(
metadata: _metadata('session-1'),
sourceWorkoutTemplateId: 'template-active',
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: jsonEncode({
'name': 'Séance active',
'programs': [
{
'id': 'template-program-1',
'programNameSnapshot': 'Programme jambes',
'programSnapshotJson': _programSnapshotJson(),
},
],
'overrides': const [],
}),
);
}
String _programSnapshotJson() {
return jsonEncode({
'exercises': [
{
'id': 'exercise-1',
'exerciseNameSnapshot': 'Squat',
'setsCount': 3,
'timeEnabled': true,
'repsEnabled': false,
'scoreEnabled': false,
'restSecondsOverride': 60,
},
],
});
}
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 _FakeWorkoutTemplateRepository
implements WorkoutTemplateRepository {
_FakeWorkoutTemplateRepository(this.templates);
final List<WorkoutTemplate> templates;
@override
Future<WorkoutTemplate?> findById(String id) async {
return templates.cast<WorkoutTemplate?>().firstWhere(
(template) => template?.metadata.id == id,
orElse: () => null,
);
}
@override
Future<List<WorkoutTemplate>> listActive() async => templates;
@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 _FakeActiveSessionRepository implements ActiveSessionRepository {
ActiveWorkoutSession? session;
var saveCount = 0;
@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 const [];
}
@override
Future<List<ActiveScoreStopwatchState>> listScoreStopwatchStates(
String sessionId,
) async {
return const [];
}
@override
Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
return const [];
}
@override
Future<void> save(ActiveWorkoutSession session) async {
saveCount += 1;
this.session = session;
}
@override
Future<void> saveRestState(ActiveRestState restState) async {}
@override
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {}
@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 {}
}
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 _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 _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 {}
}