merge(main): étapes d'exercice jamais affichées en séance (ticket #72)

Fusionne feature/bugfix-72-program-steps-snapshot — pub get OK, dart
format appliqué (aucun changement), analyze propre (mêmes infos
préexistantes), 135/135 tests verts (dont 2 nouveaux tests de
non-régression exerçant le vrai flux UI ProgramFormScreen), build APK
debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 08:10:46 +02:00
7 changed files with 181 additions and 18 deletions

View File

@ -1,6 +1,6 @@
--- ---
issueRef: "#71" issueRef: "#71"
version: 3 version: 4
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784490850142 updatedAt: 1784526657344
--- ---

View File

@ -2,15 +2,15 @@
id: "b7216520-cf5b-4e65-aeed-1839980d1fe6" id: "b7216520-cf5b-4e65-aeed-1839980d1fe6"
number: 71 number: 71
title: "[Bug] selection de score par défaut à 0" title: "[Bug] selection de score par défaut à 0"
status: "open" status: "qa"
priority: "critical" priority: "critical"
sprint: null sprint: null
links: [] links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}] agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"} createdBy: {"kind":"user"}
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784490730564 createdAt: 1784490730564
updatedAt: 1784490850142 updatedAt: 1784526657344
version: 3 version: 4
--- ---
Je dois pouvoir mettre un score par défaut à 0 lors de la création d'un exercice. Actuellement quand je met un score à 0, l'application me retourne une StateException car il y avisiblement un check qui se fait un le score > 0 Je dois pouvoir mettre un score par défaut à 0 lors de la création d'un exercice. Actuellement quand je met un score à 0, l'application me retourne une StateException car il y avisiblement un check qui se fait un le score > 0

View File

@ -1,6 +1,6 @@
--- ---
issueRef: "#74" issueRef: "#74"
version: 3 version: 4
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784493327494 updatedAt: 1784526657602
--- ---

View File

@ -2,16 +2,16 @@
id: "c18d9e4a-f6b4-4f6e-bfb5-b77c958f5534" id: "c18d9e4a-f6b4-4f6e-bfb5-b77c958f5534"
number: 74 number: 74
title: "[Bug] Serveur non lancable" title: "[Bug] Serveur non lancable"
status: "open" status: "qa"
priority: "critical" priority: "critical"
sprint: null sprint: null
links: [] links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}] agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"} createdBy: {"kind":"user"}
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784493302766 createdAt: 1784493302766
updatedAt: 1784493327494 updatedAt: 1784526657602
version: 3 version: 4
--- ---
J'ai cette erreur quand j'essaie de build le serveur: J'ai cette erreur quand j'essaie de build le serveur:
docker compose up --build docker compose up --build

View File

@ -799,13 +799,13 @@
"issueRef": "#71", "issueRef": "#71",
"path": "71", "path": "71",
"title": "[Bug] selection de score par défaut à 0", "title": "[Bug] selection de score par défaut à 0",
"status": "open", "status": "qa",
"priority": "critical", "priority": "critical",
"sprint": null, "sprint": null,
"assignedAgentIds": [ "assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784490850142 "updatedAt": 1784526657344
}, },
{ {
"issueRef": "#72", "issueRef": "#72",
@ -835,13 +835,13 @@
"issueRef": "#74", "issueRef": "#74",
"path": "74", "path": "74",
"title": "[Bug] Serveur non lancable", "title": "[Bug] Serveur non lancable",
"status": "open", "status": "qa",
"priority": "critical", "priority": "critical",
"sprint": null, "sprint": null,
"assignedAgentIds": [ "assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784493327494 "updatedAt": 1784526657602
} }
] ]
} }

View File

@ -915,6 +915,7 @@ final class _ProgramExerciseDraft {
this.exerciseImageMediaIdSnapshot, this.exerciseImageMediaIdSnapshot,
this.exerciseImageMediaIdsSnapshot = const [], this.exerciseImageMediaIdsSnapshot = const [],
this.exerciseVideoMediaIdSnapshot, this.exerciseVideoMediaIdSnapshot,
this.exerciseStepsSnapshot = const [],
this.exerciseArchivedSnapshot = false, this.exerciseArchivedSnapshot = false,
required this.availableTimeSnapshot, required this.availableTimeSnapshot,
required this.availableRepsSnapshot, required this.availableRepsSnapshot,
@ -944,6 +945,7 @@ final class _ProgramExerciseDraft {
exercise.iconMediaId ?? exercise.imageMediaId, exercise.iconMediaId ?? exercise.imageMediaId,
exerciseImageMediaIdsSnapshot: exercise.imageMediaIds, exerciseImageMediaIdsSnapshot: exercise.imageMediaIds,
exerciseVideoMediaIdSnapshot: exercise.videoMediaId, exerciseVideoMediaIdSnapshot: exercise.videoMediaId,
exerciseStepsSnapshot: exercise.steps,
exerciseArchivedSnapshot: exercise.archivedAt != null, exerciseArchivedSnapshot: exercise.archivedAt != null,
availableTimeSnapshot: exercise.hasTimeMeasure, availableTimeSnapshot: exercise.hasTimeMeasure,
availableRepsSnapshot: exercise.hasRepsMeasure, availableRepsSnapshot: exercise.hasRepsMeasure,
@ -975,6 +977,7 @@ final class _ProgramExerciseDraft {
exerciseImageMediaIdSnapshot: exercise.exerciseImageMediaIdSnapshot, exerciseImageMediaIdSnapshot: exercise.exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshot: exercise.exerciseImageMediaIdsSnapshot, exerciseImageMediaIdsSnapshot: exercise.exerciseImageMediaIdsSnapshot,
exerciseVideoMediaIdSnapshot: exercise.exerciseVideoMediaIdSnapshot, exerciseVideoMediaIdSnapshot: exercise.exerciseVideoMediaIdSnapshot,
exerciseStepsSnapshot: exercise.exerciseStepsSnapshot,
exerciseArchivedSnapshot: exercise.exerciseArchivedSnapshot, exerciseArchivedSnapshot: exercise.exerciseArchivedSnapshot,
availableTimeSnapshot: exercise.availableTimeSnapshot, availableTimeSnapshot: exercise.availableTimeSnapshot,
availableRepsSnapshot: exercise.availableRepsSnapshot, availableRepsSnapshot: exercise.availableRepsSnapshot,
@ -1004,6 +1007,7 @@ final class _ProgramExerciseDraft {
final String? exerciseImageMediaIdSnapshot; final String? exerciseImageMediaIdSnapshot;
final List<String> exerciseImageMediaIdsSnapshot; final List<String> exerciseImageMediaIdsSnapshot;
final String? exerciseVideoMediaIdSnapshot; final String? exerciseVideoMediaIdSnapshot;
final List<ExerciseStep> exerciseStepsSnapshot;
final bool exerciseArchivedSnapshot; final bool exerciseArchivedSnapshot;
final bool availableTimeSnapshot; final bool availableTimeSnapshot;
final bool availableRepsSnapshot; final bool availableRepsSnapshot;
@ -1029,6 +1033,7 @@ final class _ProgramExerciseDraft {
exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot, exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot, exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot,
exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot, exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot,
exerciseStepsSnapshot: exerciseStepsSnapshot,
exerciseArchivedSnapshot: exerciseArchivedSnapshot, exerciseArchivedSnapshot: exerciseArchivedSnapshot,
availableTimeSnapshot: availableTimeSnapshot, availableTimeSnapshot: availableTimeSnapshot,
availableRepsSnapshot: availableRepsSnapshot, availableRepsSnapshot: availableRepsSnapshot,
@ -1057,6 +1062,7 @@ final class _ProgramExerciseDraft {
exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot, exerciseImageMediaIdSnapshot: exerciseImageMediaIdSnapshot,
exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot, exerciseImageMediaIdsSnapshot: exerciseImageMediaIdsSnapshot,
exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot, exerciseVideoMediaIdSnapshot: exerciseVideoMediaIdSnapshot,
exerciseStepsSnapshot: exerciseStepsSnapshot,
exerciseArchivedSnapshot: exerciseArchivedSnapshot, exerciseArchivedSnapshot: exerciseArchivedSnapshot,
availableTimeSnapshot: availableTimeSnapshot, availableTimeSnapshot: availableTimeSnapshot,
availableRepsSnapshot: availableRepsSnapshot, availableRepsSnapshot: availableRepsSnapshot,

View File

@ -164,6 +164,148 @@ void main() {
}, },
); );
testWidgets(
'ajouter un exercice avec étapes conserve le snapshot des étapes',
(tester) async {
final steps = [
_step(
id: 'step-1',
position: 0,
name: 'Course',
type: ExerciseStepType.time,
),
_step(
id: 'step-2',
position: 1,
name: 'Tirs',
type: ExerciseStepType.reps,
),
];
final programRepository = _FakeProgramRepository();
final exerciseRepository = _FakeExerciseRepository()
..exercises.add(
Exercise(
metadata: _metadata('exercise-steps'),
name: 'Circuit tirs',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
defaultTargetReps: 2,
steps: steps,
),
);
await tester.pumpWidget(
MaterialApp(
home: ProgramFormScreen(
programUseCases: _programUseCases(
programRepository,
exerciseRepository,
),
exerciseUseCases: _exerciseUseCases(exerciseRepository),
),
),
);
await tester.enterText(
find.widgetWithText(TextFormField, 'Nom'),
'Programme étapes',
);
await tester.tap(find.text('Ajouter un exercice'));
await tester.pumpAndSettle();
await tester.tap(find.text('Circuit tirs'));
await tester.pumpAndSettle();
await tester.tap(find.text('Enregistrer'));
await tester.pumpAndSettle();
final savedSteps = programRepository
.programs
.single
.exercises
.single
.exerciseStepsSnapshot;
expect(savedSteps, hasLength(2));
expect(savedSteps.map((step) => step.name), ['Course', 'Tirs']);
expect(savedSteps.map((step) => step.type), [
ExerciseStepType.time,
ExerciseStepType.reps,
]);
},
);
testWidgets(
'réenregistrer un programme existant conserve les étapes du snapshot',
(tester) async {
final steps = [
_step(
id: 'step-1',
position: 0,
name: 'Course',
type: ExerciseStepType.time,
),
_step(
id: 'step-2',
position: 1,
name: 'Tirs',
type: ExerciseStepType.reps,
),
];
final programRepository = _FakeProgramRepository()
..programs.add(
Program(
metadata: _metadata('program-steps'),
name: 'Programme étapes',
defaultRestSeconds: 45,
exercises: [
ProgramExercise(
metadata: _metadata('program-exercise-steps'),
programId: 'program-steps',
sourceExerciseId: 'exercise-steps',
position: 0,
exerciseNameSnapshot: 'Circuit tirs',
exerciseStepsSnapshot: steps,
availableTimeSnapshot: false,
availableRepsSnapshot: true,
availableScoreSnapshot: false,
setsCount: 3,
timeEnabled: false,
repsEnabled: true,
scoreEnabled: false,
targetReps: 2,
),
],
),
);
final exerciseRepository = _FakeExerciseRepository();
await tester.pumpWidget(
MaterialApp(
home: ProgramFormScreen(
programUseCases: _programUseCases(
programRepository,
exerciseRepository,
),
exerciseUseCases: _exerciseUseCases(exerciseRepository),
program: programRepository.programs.single,
),
),
);
await tester.tap(find.text('Enregistrer'));
await tester.pumpAndSettle();
final savedSteps = programRepository
.programs
.single
.exercises
.single
.exerciseStepsSnapshot;
expect(savedSteps, hasLength(2));
expect(savedSteps.map((step) => step.name), ['Course', 'Tirs']);
expect(savedSteps.map((step) => step.position), [0, 1]);
},
);
testWidgets('affiche lobjectif de chrono pour un score en mode chrono', ( testWidgets('affiche lobjectif de chrono pour un score en mode chrono', (
tester, tester,
) async { ) async {
@ -510,6 +652,21 @@ EntityMetadata _metadata(String id) {
); );
} }
ExerciseStep _step({
required String id,
required int position,
required String name,
required ExerciseStepType type,
}) {
return ExerciseStep(
id: id,
position: position,
name: name,
type: type,
defaultTargetValue: type == ExerciseStepType.time ? 30 : 5,
);
}
final class _FakeClock implements Clock { final class _FakeClock implements Clock {
const _FakeClock(this.value); const _FakeClock(this.value);