Ajoute la persistance de l'exécution par étapes avec résultats par passage (ports, use cases, entités, modèle Drift avec migration) et l'éditeur d'exercice avec séquence d'étapes sur exercise_library_screen.dart. Développés dans le même worktree partagé par DevBackend et DevFrontend ; commit combiné plutôt que séparé car les fakes de repository de test corrigés (nouvelles interfaces #58) touchent plusieurs écrans sans lien avec l'éditeur lui-même (historique, accueil, exécution, séance-modèle). Corrige au passage un ListTile sans ancêtre Material dans le nouvel éditeur d'étapes (Material(type: MaterialType.transparency)). flutter pub get OK, build_runner OK, analyze propre (mêmes infos préexistantes), 93/93 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -216,6 +216,204 @@ void main() {
|
||||
expect(find.text('Aucune image sélectionnée'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('activer les étapes permet d’ajouter une étape temps', (
|
||||
tester,
|
||||
) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 3200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await _pumpExerciseForm(tester, exerciseRepository);
|
||||
await tester.tap(
|
||||
find.widgetWithText(SwitchListTile, 'Rythmer cet exercice avec des étapes'),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Nom de l’étape').last,
|
||||
'Dribble main droite',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Durée par défaut (s)').last,
|
||||
'12',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('1. Dribble main droite'), findsOneWidget);
|
||||
expect(find.text('Temps · 12 s'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ajouter une étape répétitions avec score manuel', (
|
||||
tester,
|
||||
) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 3600));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await _pumpExerciseForm(tester, exerciseRepository);
|
||||
await tester.tap(
|
||||
find.widgetWithText(SwitchListTile, 'Rythmer cet exercice avec des étapes'),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
await tester.tap(_stepTypeRadio('Répétitions').last);
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Nom de l’étape').last,
|
||||
'Pompes',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Répétitions par défaut').last,
|
||||
'10',
|
||||
);
|
||||
await tester.tap(find.widgetWithText(SwitchListTile, 'Score d’étape'));
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Score à saisir').last,
|
||||
'Réussites',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Unité').last,
|
||||
'paniers',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Score par défaut').last,
|
||||
'0',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('1. Pompes'), findsOneWidget);
|
||||
expect(find.text('Répétitions · 10'), findsOneWidget);
|
||||
expect(find.text('Mode de score'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('réordonner les étapes via Monter Descendre', (tester) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 4200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await _pumpExerciseForm(tester, exerciseRepository);
|
||||
await tester.tap(
|
||||
find.widgetWithText(SwitchListTile, 'Rythmer cet exercice avec des étapes'),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Nom de l’étape').at(0),
|
||||
'Première',
|
||||
);
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Nom de l’étape').at(1),
|
||||
'Deuxième',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byTooltip('Actions de l’étape 2'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Monter'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('1. Deuxième'), findsOneWidget);
|
||||
expect(find.text('2. Première'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('supprimer une étape demande confirmation', (tester) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 3200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await _pumpExerciseForm(tester, exerciseRepository);
|
||||
await tester.tap(
|
||||
find.widgetWithText(SwitchListTile, 'Rythmer cet exercice avec des étapes'),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byTooltip('Actions de l’étape 1'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Supprimer'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Supprimer cette étape ?'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Supprimer'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Nouvelle étape'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('la limite de 8 étapes bloque l’ajout d’une neuvième', (
|
||||
tester,
|
||||
) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 8000));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await _pumpExerciseForm(tester, exerciseRepository);
|
||||
await tester.tap(
|
||||
find.widgetWithText(SwitchListTile, 'Rythmer cet exercice avec des étapes'),
|
||||
);
|
||||
await tester.pump();
|
||||
for (var index = 0; index < 8; index++) {
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
expect(find.text('Limite atteinte'), findsOneWidget);
|
||||
expect(find.text('Un exercice peut contenir jusqu’à 8 étapes.'), findsOneWidget);
|
||||
expect(find.text('Ajouter une étape'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('sauvegarder transmet les étapes au use case', (tester) async {
|
||||
final exerciseRepository = _FakeExerciseRepository();
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(400, 3600));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
await _pumpExerciseForm(tester, exerciseRepository);
|
||||
await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Combo');
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
|
||||
'30',
|
||||
);
|
||||
await tester.tap(
|
||||
find.widgetWithText(SwitchListTile, 'Rythmer cet exercice avec des étapes'),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Ajouter une étape'));
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Nom de l’étape').last,
|
||||
'Dribble main droite',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Durée par défaut (s)').last,
|
||||
'12',
|
||||
);
|
||||
|
||||
await tester.ensureVisible(find.text('Enregistrer'));
|
||||
await tester.tap(find.text('Enregistrer'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final saved = exerciseRepository.saved.single;
|
||||
expect(saved.steps, hasLength(1));
|
||||
expect(saved.steps.single.name, 'Dribble main droite');
|
||||
expect(saved.steps.single.type, ExerciseStepType.time);
|
||||
expect(saved.steps.single.defaultTargetValue, 12);
|
||||
});
|
||||
|
||||
testWidgets('désigner une image comme icône l’indique visuellement', (
|
||||
tester,
|
||||
) async {
|
||||
@ -335,6 +533,30 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pumpExerciseForm(
|
||||
WidgetTester tester,
|
||||
_FakeExerciseRepository exerciseRepository,
|
||||
) {
|
||||
return tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ExerciseFormScreen(
|
||||
exerciseUseCases: _exerciseUseCases(exerciseRepository),
|
||||
mediaUseCases: _mediaUseCases(exerciseRepository),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Finder _stepTypeRadio(String title) {
|
||||
return find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is RadioListTile<ExerciseStepType> &&
|
||||
widget.title is Text &&
|
||||
(widget.title as Text).data == title,
|
||||
description: 'RadioListTile<ExerciseStepType> "$title"',
|
||||
);
|
||||
}
|
||||
|
||||
ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) {
|
||||
return ExerciseUseCases(
|
||||
repository: repository,
|
||||
|
||||
Reference in New Issue
Block a user