feat(gestion): suppression en cascade exercice/programme/séance-modèle (ticket #37)

Ajoute la suppression avec gestion des dépendances en cascade sur les
écrans bibliothèque d'exercices, programme et séance-modèle, avec les
use cases et le bootstrap applicatif associés. flutter analyze propre,
58/58 tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 10:33:06 +02:00
parent 7434790da2
commit 1a0d01f038
10 changed files with 881 additions and 26 deletions

View File

@ -233,11 +233,83 @@ void main() {
findsOneWidget,
);
});
testWidgets(
'supprimer un programme demande confirmation puis le retire de la liste',
(tester) async {
final programRepository = _FakeProgramRepository()
..programs.add(
Program(
metadata: _metadata('program-1'),
name: 'Jambes',
defaultRestSeconds: 60,
),
);
final templateRepository = _FakeWorkoutTemplateRepository()
..templates.add(
WorkoutTemplate(
metadata: _metadata('template-1'),
name: 'Séance jambes',
programs: [
WorkoutTemplateProgram(
metadata: _metadata('template-program-1'),
workoutTemplateId: 'template-1',
sourceProgramId: 'program-1',
position: 0,
programNameSnapshot: 'Jambes',
defaultRestSecondsSnapshot: 60,
programSnapshotJson: '{"exercises":[]}',
),
],
),
);
final exerciseRepository = _FakeExerciseRepository();
await tester.pumpWidget(
MaterialApp(
home: ProgramListScreen(
programUseCases: _programUseCases(
programRepository,
exerciseRepository,
templateRepository: templateRepository,
),
exerciseUseCases: _exerciseUseCases(exerciseRepository),
),
),
);
await tester.pump();
await tester.pump();
await tester.tap(find.byTooltip('Supprimer le programme'));
await tester.pumpAndSettle();
expect(find.text('Supprimer Jambes ?'), findsOneWidget);
expect(
find.textContaining('Le supprimer le retirera de ces séances.'),
findsOneWidget,
);
await tester.tap(find.text('Annuler'));
await tester.pumpAndSettle();
expect(find.text('Jambes'), findsOneWidget);
expect(programRepository.saved, isEmpty);
await tester.tap(find.byTooltip('Supprimer le programme'));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(FilledButton, 'Supprimer'));
await tester.pumpAndSettle();
expect(find.text('Jambes'), findsNothing);
expect(programRepository.saved.single.metadata.deletedAt, isNotNull);
},
);
}
ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) {
return ExerciseUseCases(
repository: repository,
programRepository: _FakeProgramRepository(),
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
@ -246,17 +318,55 @@ ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) {
ProgramUseCases _programUseCases(
_FakeProgramRepository programRepository,
_FakeExerciseRepository exerciseRepository,
) {
_FakeExerciseRepository exerciseRepository, {
_FakeWorkoutTemplateRepository? templateRepository,
}) {
return ProgramUseCases(
programRepository: programRepository,
exerciseRepository: exerciseRepository,
templateRepository: templateRepository ?? _FakeWorkoutTemplateRepository(),
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
}
final class _FakeWorkoutTemplateRepository
implements WorkoutTemplateRepository {
final templates = <WorkoutTemplate>[];
@override
Future<WorkoutTemplate?> findById(String id) async => null;
@override
Future<List<WorkoutTemplate>> listActive() async {
return templates
.where((template) => template.metadata.deletedAt == null)
.toList();
}
@override
Future<void> replaceComposition(
WorkoutTemplate template,
DateTime deletedAt,
) async {
templates.removeWhere((item) => item.metadata.id == template.metadata.id);
templates.add(template);
}
@override
Future<void> save(WorkoutTemplate template) async {
templates.removeWhere((item) => item.metadata.id == template.metadata.id);
templates.add(template);
}
@override
Future<void> saveOverride(WorkoutTemplateExerciseOverride override) async {}
@override
Future<void> saveProgram(WorkoutTemplateProgram program) async {}
}
EntityMetadata _metadata(String id) {
return EntityMetadata(
id: id,
@ -313,6 +423,7 @@ final class _FakeExerciseRepository implements ExerciseRepository {
final class _FakeProgramRepository implements ProgramRepository {
final programs = <Program>[];
final saved = <Program>[];
@override
Future<Program?> findById(String id) async {
@ -325,7 +436,11 @@ final class _FakeProgramRepository implements ProgramRepository {
}
@override
Future<List<Program>> listActive() async => programs;
Future<List<Program>> listActive() async {
return programs
.where((program) => program.metadata.deletedAt == null)
.toList();
}
@override
Future<void> replaceExercises(Program program, DateTime deletedAt) async {
@ -335,6 +450,8 @@ final class _FakeProgramRepository implements ProgramRepository {
@override
Future<void> save(Program program) async {
saved.add(program);
programs.removeWhere((item) => item.metadata.id == program.metadata.id);
programs.add(program);
}