Ajoute la configuration du score chronométré sur exercise_library_screen.dart et program_screen.dart, s'appuyant sur les fondations domain du ticket #25. flutter analyze propre, 41/41 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
280 lines
7.6 KiB
Dart
280 lines
7.6 KiB
Dart
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(
|
||
'ajoute un exercice au programme avec sa configuration par défaut',
|
||
(tester) async {
|
||
final programRepository = _FakeProgramRepository();
|
||
final exerciseRepository = _FakeExerciseRepository()
|
||
..exercises.add(
|
||
Exercise(
|
||
metadata: _metadata('exercise-1'),
|
||
name: 'Squat',
|
||
hasTimeMeasure: true,
|
||
hasRepsMeasure: true,
|
||
hasScoreMeasure: false,
|
||
),
|
||
);
|
||
|
||
await tester.pumpWidget(
|
||
MaterialApp(
|
||
home: ProgramFormScreen(
|
||
programUseCases: _programUseCases(
|
||
programRepository,
|
||
exerciseRepository,
|
||
),
|
||
exerciseUseCases: _exerciseUseCases(exerciseRepository),
|
||
),
|
||
),
|
||
);
|
||
|
||
await tester.enterText(
|
||
find.widgetWithText(TextFormField, 'Nom'),
|
||
'Jambes',
|
||
);
|
||
await tester.tap(find.text('Ajouter un exercice'));
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.text('Squat'));
|
||
await tester.pumpAndSettle();
|
||
|
||
expect(find.text('Squat'), findsOneWidget);
|
||
expect(find.text('Repos 60 s'), findsOneWidget);
|
||
expect(find.text('Exercice ajouté'), findsOneWidget);
|
||
expect(find.byTooltip("Personnaliser l'exercice"), findsOneWidget);
|
||
expect(find.byType(CheckboxListTile), findsNothing);
|
||
|
||
await tester.tap(find.text('Enregistrer'));
|
||
await tester.pumpAndSettle();
|
||
|
||
final savedExercise = programRepository.programs.single.exercises.single;
|
||
expect(savedExercise.setsCount, 3);
|
||
expect(savedExercise.timeEnabled, isTrue);
|
||
expect(savedExercise.repsEnabled, isTrue);
|
||
expect(savedExercise.scoreEnabled, isFalse);
|
||
expect(savedExercise.restSecondsOverride, 60);
|
||
},
|
||
);
|
||
|
||
testWidgets(
|
||
'limite les mesures aux mesures autorisées par l’exercice source',
|
||
(tester) async {
|
||
final exerciseRepository = _FakeExerciseRepository()
|
||
..exercises.add(
|
||
Exercise(
|
||
metadata: _metadata('exercise-1'),
|
||
name: 'Pompes',
|
||
hasTimeMeasure: false,
|
||
hasRepsMeasure: true,
|
||
hasScoreMeasure: false,
|
||
),
|
||
);
|
||
|
||
await tester.pumpWidget(
|
||
MaterialApp(
|
||
home: ProgramFormScreen(
|
||
programUseCases: _programUseCases(
|
||
_FakeProgramRepository(),
|
||
exerciseRepository,
|
||
),
|
||
exerciseUseCases: _exerciseUseCases(exerciseRepository),
|
||
),
|
||
),
|
||
);
|
||
|
||
await tester.tap(find.text('Ajouter un exercice'));
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.text('Pompes'));
|
||
await tester.pumpAndSettle();
|
||
|
||
await tester.tap(find.byTooltip("Personnaliser l'exercice"));
|
||
await tester.pumpAndSettle();
|
||
|
||
expect(find.text("Personnaliser l'exercice"), findsOneWidget);
|
||
expect(
|
||
find.widgetWithText(SwitchListTile, 'Répétitions'),
|
||
findsOneWidget,
|
||
);
|
||
expect(find.widgetWithText(SwitchListTile, 'Temps'), findsNothing);
|
||
expect(find.widgetWithText(SwitchListTile, 'Score'), findsNothing);
|
||
},
|
||
);
|
||
|
||
testWidgets('affiche l’objectif de chrono pour un score en mode chrono', (
|
||
tester,
|
||
) async {
|
||
final exerciseRepository = _FakeExerciseRepository()
|
||
..exercises.add(
|
||
Exercise(
|
||
metadata: _metadata('exercise-1'),
|
||
name: 'Suicides',
|
||
hasTimeMeasure: true,
|
||
hasRepsMeasure: true,
|
||
hasScoreMeasure: true,
|
||
scoreInputMode: ScoreInputMode.stopwatch,
|
||
scoreLabel: 'Temps réalisé',
|
||
),
|
||
);
|
||
|
||
await tester.pumpWidget(
|
||
MaterialApp(
|
||
home: ProgramFormScreen(
|
||
programUseCases: _programUseCases(
|
||
_FakeProgramRepository(),
|
||
exerciseRepository,
|
||
),
|
||
exerciseUseCases: _exerciseUseCases(exerciseRepository),
|
||
),
|
||
),
|
||
);
|
||
|
||
await tester.tap(find.text('Ajouter un exercice'));
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.text('Suicides'));
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.byTooltip("Personnaliser l'exercice"));
|
||
await tester.pumpAndSettle();
|
||
|
||
expect(find.text('Score chrono'), findsOneWidget);
|
||
await tester.dragUntilVisible(
|
||
find.text(
|
||
"Temps sert d'objectif de durée ; Score chrono enregistre "
|
||
'le temps réalisé.',
|
||
),
|
||
find.byType(ListView),
|
||
const Offset(0, -200),
|
||
);
|
||
expect(
|
||
find.text(
|
||
"Temps sert d'objectif de durée ; Score chrono enregistre "
|
||
'le temps réalisé.',
|
||
),
|
||
findsOneWidget,
|
||
);
|
||
await tester.dragUntilVisible(
|
||
find.text('Objectif de chrono'),
|
||
find.byType(ListView),
|
||
const Offset(0, -200),
|
||
);
|
||
expect(
|
||
find.widgetWithText(TextFormField, 'Objectif de chrono'),
|
||
findsOneWidget,
|
||
);
|
||
expect(find.widgetWithText(TextFormField, 'Cible score'), findsNothing);
|
||
expect(
|
||
find.text('Le résultat réel sera mesuré pendant la série.'),
|
||
findsOneWidget,
|
||
);
|
||
});
|
||
}
|
||
|
||
ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) {
|
||
return ExerciseUseCases(
|
||
repository: repository,
|
||
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
|
||
ids: _FakeIds(),
|
||
originDeviceId: 'device-1',
|
||
);
|
||
}
|
||
|
||
ProgramUseCases _programUseCases(
|
||
_FakeProgramRepository programRepository,
|
||
_FakeExerciseRepository exerciseRepository,
|
||
) {
|
||
return ProgramUseCases(
|
||
programRepository: programRepository,
|
||
exerciseRepository: exerciseRepository,
|
||
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
|
||
ids: _FakeIds(),
|
||
originDeviceId: 'device-1',
|
||
);
|
||
}
|
||
|
||
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 _FakeExerciseRepository implements ExerciseRepository {
|
||
final exercises = <Exercise>[];
|
||
final saved = <Exercise>[];
|
||
|
||
@override
|
||
Future<Exercise?> findById(String id) async {
|
||
for (final exercise in exercises) {
|
||
if (exercise.metadata.id == id) {
|
||
return exercise;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
@override
|
||
Future<bool> isReferencedByProgram(String id) async => false;
|
||
|
||
@override
|
||
Future<List<Exercise>> listActive() async => exercises;
|
||
|
||
@override
|
||
Future<void> save(Exercise exercise) async {
|
||
saved.add(exercise);
|
||
}
|
||
}
|
||
|
||
final class _FakeProgramRepository implements ProgramRepository {
|
||
final programs = <Program>[];
|
||
|
||
@override
|
||
Future<Program?> findById(String id) async {
|
||
for (final program in programs) {
|
||
if (program.metadata.id == id) {
|
||
return program;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
@override
|
||
Future<List<Program>> listActive() async => programs;
|
||
|
||
@override
|
||
Future<void> replaceExercises(Program program, DateTime deletedAt) async {
|
||
programs.removeWhere((item) => item.metadata.id == program.metadata.id);
|
||
programs.add(program);
|
||
}
|
||
|
||
@override
|
||
Future<void> save(Program program) async {
|
||
programs.add(program);
|
||
}
|
||
|
||
@override
|
||
Future<void> saveExercise(ProgramExercise exercise) async {}
|
||
}
|