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

@ -35,7 +35,7 @@ void main() {
expect(find.text('Fondations basket - 45 min'), findsOneWidget);
expect(find.text('Exemple'), findsOneWidget);
expect(find.byTooltip('Supprimer le programme'), findsOneWidget);
expect(find.byTooltip('Actions'), findsOneWidget);
});
testWidgets('la liste programmes vide conserve le message dédié', (
@ -65,6 +65,98 @@ void main() {
);
});
testWidgets('filtre les programmes par tag et peut effacer les filtres', (
tester,
) async {
final programRepository = _FakeProgramRepository()
..programs.addAll([
Program(
metadata: _metadata('program-1'),
name: 'Programme match',
defaultRestSeconds: 45,
tags: const ['match', 'intense'],
),
Program(
metadata: _metadata('program-2'),
name: 'Programme routine',
defaultRestSeconds: 30,
tags: const ['routine'],
),
]);
final exerciseRepository = _FakeExerciseRepository();
await tester.pumpWidget(
MaterialApp(
home: ProgramListScreen(
programUseCases: _programUseCases(
programRepository,
exerciseRepository,
),
exerciseUseCases: _exerciseUseCases(exerciseRepository),
),
),
);
await tester.pump();
await tester.pump();
expect(find.widgetWithText(FilterChip, 'match'), findsOneWidget);
await tester.tap(find.widgetWithText(FilterChip, 'match'));
await tester.pump();
expect(find.text('Programme match'), findsOneWidget);
expect(find.text('Programme routine'), findsNothing);
expect(find.text('Effacer les filtres'), findsOneWidget);
await tester.tap(find.text('Effacer les filtres'));
await tester.pump();
expect(find.text('Programme match'), findsOneWidget);
expect(find.text('Programme routine'), findsOneWidget);
});
testWidgets('dupliquer un programme ouvre la copie en édition', (
tester,
) async {
final programRepository = _FakeProgramRepository()
..programs.add(
Program(
metadata: _metadata('program-1'),
name: 'Jambes',
defaultRestSeconds: 60,
tags: const ['match'],
),
);
final exerciseRepository = _FakeExerciseRepository();
await tester.pumpWidget(
MaterialApp(
home: ProgramListScreen(
programUseCases: _programUseCases(
programRepository,
exerciseRepository,
),
exerciseUseCases: _exerciseUseCases(exerciseRepository),
),
),
);
await tester.pump();
await tester.pump();
await tester.tap(find.byTooltip('Actions'));
await tester.pumpAndSettle();
await tester.tap(find.text('Dupliquer'));
await tester.pumpAndSettle();
expect(find.text('Modifier le programme'), findsOneWidget);
expect(find.text('Copie de Jambes'), findsOneWidget);
expect(
find.text("Programme dupliqué. Ajuste la copie avant de l'utiliser."),
findsOneWidget,
);
expect(find.widgetWithText(Chip, 'match'), findsOneWidget);
});
testWidgets('modifier un programme exemple conserve sa provenance', (
tester,
) async {
@ -676,7 +768,9 @@ void main() {
await tester.pump();
await tester.pump();
await tester.tap(find.byTooltip('Supprimer le programme'));
await tester.tap(find.byTooltip('Actions'));
await tester.pumpAndSettle();
await tester.tap(find.text('Supprimer'));
await tester.pumpAndSettle();
expect(find.text('Supprimer Jambes ?'), findsOneWidget);
@ -691,7 +785,9 @@ void main() {
expect(find.text('Jambes'), findsOneWidget);
expect(programRepository.saved, isEmpty);
await tester.tap(find.byTooltip('Supprimer le programme'));
await tester.tap(find.byTooltip('Actions'));
await tester.pumpAndSettle();
await tester.tap(find.text('Supprimer'));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(FilledButton, 'Supprimer'));
await tester.pumpAndSettle();