feat(exercice): valeurs par défaut d'exercice (tickets #39/#40, parent #30)
Étend le modèle Drift (tables.dart, app_database.dart/.g.dart), les entités/use cases/repositories et les écrans bibliothèque d'exercices et programme pour supporter des valeurs par défaut par exercice (répétitions/temps/poids proposés à la création d'une série). flutter analyze propre, 51/51 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -38,6 +38,7 @@ void main() {
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 10,
|
||||
);
|
||||
final mediaRepository = _FakeMediaAssetRepository()
|
||||
..asset = MediaAsset(
|
||||
|
||||
@ -18,6 +18,144 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Exercise use case rejects active measures without default targets',
|
||||
() async {
|
||||
final useCase = _exerciseUseCase(_FakeExerciseRepository());
|
||||
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Squat',
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: false,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Pompes',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Charge',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: true,
|
||||
scoreLabel: 'Charge',
|
||||
scoreUnit: 'kg',
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('Exercise use case rejects non-positive default targets', () async {
|
||||
final useCase = _exerciseUseCase(_FakeExerciseRepository());
|
||||
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Squat',
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetTimeSeconds: 0,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Pompes',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: -1,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Charge',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: true,
|
||||
scoreLabel: 'Charge',
|
||||
scoreUnit: 'kg',
|
||||
defaultTargetScore: 0,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Exercise use case accepts stopwatch score without default target',
|
||||
() async {
|
||||
final repository = _FakeExerciseRepository();
|
||||
final useCase = _exerciseUseCase(repository);
|
||||
|
||||
final exercise = await useCase.create(
|
||||
name: 'Sprint',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
scoreLabel: 'Temps réalisé',
|
||||
);
|
||||
|
||||
expect(exercise.defaultTargetScoreTimeMs, isNull);
|
||||
expect(repository.saved.single.metadata.id, exercise.metadata.id);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Exercise use case rejects non-positive stopwatch score default target',
|
||||
() async {
|
||||
final useCase = _exerciseUseCase(_FakeExerciseRepository());
|
||||
|
||||
await expectLater(
|
||||
useCase.create(
|
||||
name: 'Sprint',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: true,
|
||||
scoreInputMode: ScoreInputMode.stopwatch,
|
||||
scoreLabel: 'Temps réalisé',
|
||||
defaultTargetScoreTimeMs: 0,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('Exercise update validates default targets too', () async {
|
||||
final repository = _FakeExerciseRepository()
|
||||
..exercise = Exercise(
|
||||
metadata: _metadata('exercise-1'),
|
||||
name: 'Squat',
|
||||
hasTimeMeasure: false,
|
||||
hasRepsMeasure: true,
|
||||
hasScoreMeasure: false,
|
||||
defaultTargetReps: 10,
|
||||
);
|
||||
final useCase = _exerciseUseCase(repository);
|
||||
|
||||
await expectLater(
|
||||
useCase.update(
|
||||
id: 'exercise-1',
|
||||
name: 'Squat',
|
||||
hasTimeMeasure: true,
|
||||
hasRepsMeasure: false,
|
||||
hasScoreMeasure: false,
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('Workout template use case rejects active measure overrides', () async {
|
||||
final useCase = WorkoutTemplateUseCases(
|
||||
templateRepository: _FakeWorkoutTemplateRepository(),
|
||||
@ -474,6 +612,38 @@ final class _FakeIds implements IdGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
ExerciseUseCases _exerciseUseCase(_FakeExerciseRepository repository) {
|
||||
return ExerciseUseCases(
|
||||
repository: repository,
|
||||
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
|
||||
ids: _FakeIds(),
|
||||
originDeviceId: 'device-1',
|
||||
);
|
||||
}
|
||||
|
||||
final class _FakeExerciseRepository implements ExerciseRepository {
|
||||
Exercise? exercise;
|
||||
final saved = <Exercise>[];
|
||||
|
||||
@override
|
||||
Future<Exercise?> findById(String id) async {
|
||||
return exercise?.metadata.id == id ? exercise : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isReferencedByProgram(String id) async => false;
|
||||
|
||||
@override
|
||||
Future<List<Exercise>> listActive() async =>
|
||||
exercise == null ? const [] : [exercise!];
|
||||
|
||||
@override
|
||||
Future<void> save(Exercise exercise) async {
|
||||
this.exercise = exercise;
|
||||
saved.add(exercise);
|
||||
}
|
||||
}
|
||||
|
||||
final class _FakeWorkoutTemplateRepository
|
||||
implements WorkoutTemplateRepository {
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user