feat(bibliothèque): badge Exemple, catégories et états vides bibliothèque (ticket #80)

Ajoute le badge "Exemple" (example_badge.dart) affiché sur les
exercices/programmes/séances-modèles seedés, le filtrage par catégorie
dans la bibliothèque d'exercices et les états vides dédiés. Préserve
isExample lors de l'édition d'un programme ou d'une séance-modèle
existants (ProgramUseCases/WorkoutTemplateUseCases).

Validé GO par Main : dart analyze propre, 173 tests (4 échecs
préexistants sans rapport avec ce lot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 07:39:26 +02:00
parent 24e266c0e9
commit e05405157a
9 changed files with 357 additions and 16 deletions

View File

@ -7,6 +7,165 @@ import 'package:gametime/domain/domain.dart';
import 'package:gametime/presentation/presentation.dart';
void main() {
testWidgets('la liste affiche le badge exemple des séances seedées', (
tester,
) async {
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final templateRepository = _FakeWorkoutTemplateRepository([
_workoutTemplate(isExample: true),
]);
final activeRepository = _FakeActiveSessionRepository();
final programRepository = _FakeProgramRepository();
final historyRepository = _FakeWorkoutHistoryRepository();
await tester.pumpWidget(
MaterialApp(
home: WorkoutTemplateListScreen(
workoutTemplateUseCases: WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: programRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
programUseCases: ProgramUseCases(
programRepository: programRepository,
exerciseRepository: _FakeExerciseRepository(),
templateRepository: templateRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
activeUseCases: ActiveWorkoutSessionUseCases(
sessionRepository: activeRepository,
templateRepository: templateRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
closeUseCase: CloseWorkoutSessionUseCase(
sessionRepository: activeRepository,
historyRepository: historyRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
historyUseCases: WorkoutHistoryUseCases(
repository: historyRepository,
clock: clock,
),
),
),
);
await tester.pump();
await tester.pump();
expect(find.text('Séance jambes'), findsOneWidget);
expect(find.text('Exemple'), findsOneWidget);
expect(find.byTooltip('Supprimer la séance'), findsOneWidget);
expect(find.text('Lancer'), findsOneWidget);
});
testWidgets('la liste séances vide conserve le message dédié', (
tester,
) async {
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final templateRepository = _FakeWorkoutTemplateRepository(const []);
final activeRepository = _FakeActiveSessionRepository();
final programRepository = _FakeProgramRepository();
final historyRepository = _FakeWorkoutHistoryRepository();
await tester.pumpWidget(
MaterialApp(
home: WorkoutTemplateListScreen(
workoutTemplateUseCases: WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: programRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
programUseCases: ProgramUseCases(
programRepository: programRepository,
exerciseRepository: _FakeExerciseRepository(),
templateRepository: templateRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
activeUseCases: ActiveWorkoutSessionUseCases(
sessionRepository: activeRepository,
templateRepository: templateRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
closeUseCase: CloseWorkoutSessionUseCase(
sessionRepository: activeRepository,
historyRepository: historyRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
historyUseCases: WorkoutHistoryUseCases(
repository: historyRepository,
clock: clock,
),
),
),
);
await tester.pump();
await tester.pump();
expect(find.text('Aucune séance'), findsOneWidget);
expect(
find.text(
'Crée une séance-modèle pour lancer ton entraînement plus vite.',
),
findsOneWidget,
);
});
testWidgets('modifier une séance exemple conserve sa provenance', (
tester,
) async {
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
final templateRepository = _FakeWorkoutTemplateRepository([
_workoutTemplate(isExample: true),
]);
final programRepository = _FakeProgramRepository();
await tester.pumpWidget(
MaterialApp(
home: WorkoutTemplateFormScreen(
workoutTemplateUseCases: WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: programRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
programUseCases: ProgramUseCases(
programRepository: programRepository,
exerciseRepository: _FakeExerciseRepository(),
templateRepository: templateRepository,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
),
template: templateRepository.templates.single,
),
),
);
await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Editée');
await tester.tap(find.text('Enregistrer'));
await tester.pumpAndSettle();
expect(templateRepository.templates.single.name, 'Editée');
expect(templateRepository.templates.single.isExample, isTrue);
});
testWidgets(
'la vue programme de séance ne permet pas de changer les mesures actives',
(tester) async {
@ -352,10 +511,11 @@ void main() {
});
}
WorkoutTemplate _workoutTemplate() {
WorkoutTemplate _workoutTemplate({bool isExample = false}) {
return WorkoutTemplate(
metadata: _metadata('template-1'),
name: 'Séance jambes',
isExample: isExample,
programs: [
WorkoutTemplateProgram(
metadata: _metadata('template-program-1'),