feat(bibliotheque): tags de filtrage et duplication rapide côté UI (ticket #83)

Lot frontend F1+F2 : composant library_tags.dart (filtre/suggestions de tags), intégration dans exercise_library_screen, program_screen et workout_template_screen (tags + action duplication rapide), tests associés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 10:22:47 +02:00
parent e57cfdf54d
commit 1b5fc935bd
7 changed files with 1220 additions and 27 deletions

View File

@ -21,6 +21,12 @@ void main() {
);
await tester.enterText(find.byType(TextFormField).first, 'Gainage');
await tester.dragUntilVisible(
find.widgetWithText(SwitchListTile, 'Temps'),
find.byType(ListView),
const Offset(0, -200),
);
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(SwitchListTile, 'Temps'));
await tester.pump();
await tester.dragUntilVisible(
@ -73,6 +79,103 @@ void main() {
expect(find.text('Exemple'), findsOneWidget);
});
testWidgets('ajouter un tag à un exercice le sauvegarde normalisé', (
tester,
) async {
final exerciseRepository = _FakeExerciseRepository();
await tester.pumpWidget(
MaterialApp(
home: ExerciseFormScreen(
exerciseUseCases: _exerciseUseCases(exerciseRepository),
mediaUseCases: _mediaUseCases(exerciseRepository),
tagSuggestions: const ['match'],
),
),
);
await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Tirs');
await tester.tap(find.text('Ajouter un tag'));
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextField, 'Nom du tag'),
' Match ',
);
await tester.tap(find.widgetWithText(FilledButton, 'Ajouter'));
await tester.pumpAndSettle();
expect(find.widgetWithText(Chip, 'match'), findsOneWidget);
await tester.dragUntilVisible(
find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
find.byType(ListView),
const Offset(0, -200),
);
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
'30',
);
await tester.dragUntilVisible(
find.text('Enregistrer'),
find.byType(ListView),
const Offset(0, -200),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Enregistrer'));
await tester.pumpAndSettle();
expect(exerciseRepository.saved.single.tags, ['match']);
});
testWidgets('filtre les exercices par tag et efface tous les filtres', (
tester,
) async {
final exerciseRepository = _FakeExerciseRepository()
..exercises.addAll([
Exercise(
metadata: _metadata('exercise-1'),
name: 'Tirs match',
hasTimeMeasure: true,
hasRepsMeasure: false,
hasScoreMeasure: false,
tags: const ['match', 'extérieur'],
),
Exercise(
metadata: _metadata('exercise-2'),
name: 'Dribble routine',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
tags: const ['routine'],
),
]);
await tester.pumpWidget(
MaterialApp(
home: ExerciseLibraryScreen(
exerciseUseCases: _exerciseUseCases(exerciseRepository),
mediaUseCases: _mediaUseCases(exerciseRepository),
),
),
);
await tester.pump();
await tester.pump();
await tester.tap(find.widgetWithText(FilterChip, 'match'));
await tester.pump();
expect(find.text('Tirs match'), findsOneWidget);
expect(find.text('Dribble routine'), findsNothing);
expect(find.text('Effacer les filtres'), findsOneWidget);
await tester.tap(find.text('Effacer les filtres'));
await tester.pump();
expect(find.text('Tirs match'), findsOneWidget);
expect(find.text('Dribble routine'), findsOneWidget);
});
testWidgets('basculer en chrono intégré masque lunité et affiche le badge', (
tester,
) async {