chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)
Regroupe l'état de travail en cours réalisé dans un même worktree sur plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164), mélangeant des tickets QA et inProgress. Ne constitue pas une feature terminée : commit de sauvegarde avant triage/split par ticket en branches feature/* dédiées. Exclut les dossiers d'environnement de build locaux et le heap dump parasite (.gitignore mis à jour). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gametime/application/application.dart';
|
||||
import 'package:gametime/domain/domain.dart';
|
||||
import 'package:gametime/presentation/share_inbox_screen.dart';
|
||||
import 'package:gametime/presentation/share_screen.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('la boîte de réception affiche les partages et les actions', (
|
||||
@ -71,6 +72,108 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('un pack reçu affiche un détail et importe toutes les séances', (
|
||||
tester,
|
||||
) async {
|
||||
final remoteShareApi = _FakeRemoteShareApi()
|
||||
..inboxItems = [
|
||||
_shareItem(
|
||||
shareId: 'share-pack',
|
||||
resourceType: ShareResourceType.pack,
|
||||
name: 'Pack reprise',
|
||||
status: ShareInboxStatus.pending,
|
||||
),
|
||||
];
|
||||
final inboxRepository = _FakeShareInboxRepository();
|
||||
final templateRepository = _FakeWorkoutTemplateRepository();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ShareInboxScreen(
|
||||
shareUseCases: _shareUseCases(
|
||||
remoteShareApi: remoteShareApi,
|
||||
inboxRepository: inboxRepository,
|
||||
templateRepository: templateRepository,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Pack reprise'), findsOneWidget);
|
||||
expect(find.text('Pack · 2 séances'), findsOneWidget);
|
||||
expect(find.text('Voir le pack'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Voir le pack'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Pack reçu'), findsOneWidget);
|
||||
expect(find.text('Séance tirs'), findsOneWidget);
|
||||
expect(find.text('Séance jambes'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Importer'));
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(templateRepository.saved, hasLength(2));
|
||||
expect(
|
||||
templateRepository.saved.map((template) => template.name),
|
||||
containsAll(['Séance tirs', 'Séance jambes']),
|
||||
);
|
||||
expect(inboxRepository.items.single.status, ShareInboxStatus.accepted);
|
||||
expect(find.text('Pack importé.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('composer un pack ouvre le formulaire de partage pack', (
|
||||
tester,
|
||||
) async {
|
||||
final remoteShareApi = _FakeRemoteShareApi();
|
||||
final templateRepository = _FakeWorkoutTemplateRepository()
|
||||
..templates.add(_workoutTemplate('template-1', 'Séance tirs'))
|
||||
..templates.add(_workoutTemplate('template-2', 'Séance jambes'));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: SharePackSelectionScreen(
|
||||
shareUseCases: _shareUseCases(
|
||||
remoteShareApi: remoteShareApi,
|
||||
inboxRepository: _FakeShareInboxRepository(),
|
||||
templateRepository: templateRepository,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextField, 'Nom du pack'),
|
||||
'Pack reprise',
|
||||
);
|
||||
await tester.tap(find.text('Séance tirs'));
|
||||
await tester.tap(find.text('Séance jambes'));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Continuer'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Partager le pack'), findsOneWidget);
|
||||
expect(find.text('Pack reprise'), findsOneWidget);
|
||||
expect(find.text('2 séances'), findsOneWidget);
|
||||
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextFormField, 'Emails destinataires'),
|
||||
'coach@example.com',
|
||||
);
|
||||
await tester.tap(find.text('Envoyer le partage'));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
expect(remoteShareApi.lastResourceType, ShareResourceType.pack);
|
||||
expect(remoteShareApi.lastPayload?['name'], 'Pack reprise');
|
||||
expect(remoteShareApi.lastPayload?['workouts'], hasLength(2));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'accepter un partage en cache hors connexion importe une copie locale',
|
||||
(tester) async {
|
||||
@ -129,6 +232,7 @@ ShareUseCases _shareUseCases({
|
||||
required _FakeShareInboxRepository inboxRepository,
|
||||
_FakePendingShareActionRepository? pendingActionRepository,
|
||||
_FakeProgramRepository? programRepository,
|
||||
_FakeWorkoutTemplateRepository? templateRepository,
|
||||
}) {
|
||||
return ShareUseCases(
|
||||
tokenStore: tokenStore ?? _FakeAuthTokenStore('token-1'),
|
||||
@ -138,7 +242,7 @@ ShareUseCases _shareUseCases({
|
||||
pendingActionRepository ?? _FakePendingShareActionRepository(),
|
||||
localChanges: _FakeLocalSyncChangeRepository(),
|
||||
programRepository: programRepository ?? _FakeProgramRepository(),
|
||||
templateRepository: _FakeWorkoutTemplateRepository(),
|
||||
templateRepository: templateRepository ?? _FakeWorkoutTemplateRepository(),
|
||||
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
|
||||
ids: _FakeIds(),
|
||||
originDeviceId: 'device-1',
|
||||
@ -180,6 +284,13 @@ ShareInboxItem _shareItem({
|
||||
},
|
||||
],
|
||||
},
|
||||
ShareResourceType.pack => {
|
||||
'name': name,
|
||||
'workouts': [
|
||||
_templatePayload('template-1', 'Séance tirs'),
|
||||
_templatePayload('template-2', 'Séance jambes'),
|
||||
],
|
||||
},
|
||||
};
|
||||
return ShareInboxItem(
|
||||
shareId: shareId,
|
||||
@ -191,6 +302,23 @@ ShareInboxItem _shareItem({
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Object?> _templatePayload(String id, String name) {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'programs': [
|
||||
{
|
||||
'id': '$id-program',
|
||||
'position': 0,
|
||||
'programNameSnapshot': 'Programme',
|
||||
'defaultRestSecondsSnapshot': 60,
|
||||
'programSnapshotJson': jsonEncode({'exercises': const []}),
|
||||
},
|
||||
],
|
||||
'overrides': const [],
|
||||
};
|
||||
}
|
||||
|
||||
final class _FakeClock implements Clock {
|
||||
const _FakeClock(this.value);
|
||||
|
||||
@ -233,6 +361,8 @@ final class _FakeRemoteShareApi implements RemoteShareApi {
|
||||
List<ShareInboxItem> inboxItems = const [];
|
||||
var acceptCalls = 0;
|
||||
var declineCalls = 0;
|
||||
ShareResourceType? lastResourceType;
|
||||
Map<String, Object?>? lastPayload;
|
||||
|
||||
@override
|
||||
Future<RemoteSyncedItem> acceptShare(String shareId, String token) async {
|
||||
@ -271,6 +401,8 @@ final class _FakeRemoteShareApi implements RemoteShareApi {
|
||||
required List<String> recipientEmails,
|
||||
required String token,
|
||||
}) async {
|
||||
lastResourceType = resourceType;
|
||||
lastPayload = payload;
|
||||
return const RemoteShareSendResult(
|
||||
shareId: 'share-1',
|
||||
recipientUserIds: ['recipient-1'],
|
||||
@ -388,11 +520,18 @@ final class _FakeProgramRepository implements ProgramRepository {
|
||||
|
||||
final class _FakeWorkoutTemplateRepository
|
||||
implements WorkoutTemplateRepository {
|
||||
@override
|
||||
Future<WorkoutTemplate?> findById(String id) async => null;
|
||||
final templates = <WorkoutTemplate>[];
|
||||
final saved = <WorkoutTemplate>[];
|
||||
|
||||
@override
|
||||
Future<List<WorkoutTemplate>> listActive() async => const [];
|
||||
Future<WorkoutTemplate?> findById(String id) async {
|
||||
return templates
|
||||
.where((template) => template.metadata.id == id)
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<WorkoutTemplate>> listActive() async => List.of(templates);
|
||||
|
||||
@override
|
||||
Future<void> replaceComposition(
|
||||
@ -401,7 +540,16 @@ final class _FakeWorkoutTemplateRepository
|
||||
) async {}
|
||||
|
||||
@override
|
||||
Future<void> save(WorkoutTemplate template) async {}
|
||||
Future<void> save(WorkoutTemplate template) async {
|
||||
saved.add(template);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveAll(List<WorkoutTemplate> templates) async {
|
||||
for (final template in templates) {
|
||||
await save(template);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveOverride(WorkoutTemplateExerciseOverride override) async {}
|
||||
@ -409,3 +557,31 @@ final class _FakeWorkoutTemplateRepository
|
||||
@override
|
||||
Future<void> saveProgram(WorkoutTemplateProgram program) async {}
|
||||
}
|
||||
|
||||
WorkoutTemplate _workoutTemplate(String id, String name) {
|
||||
return WorkoutTemplate(
|
||||
metadata: EntityMetadata(
|
||||
id: id,
|
||||
createdAt: DateTime.utc(2026, 7, 17),
|
||||
updatedAt: DateTime.utc(2026, 7, 17),
|
||||
originDeviceId: 'device-1',
|
||||
),
|
||||
name: name,
|
||||
programs: [
|
||||
WorkoutTemplateProgram(
|
||||
metadata: EntityMetadata(
|
||||
id: '$id-program',
|
||||
createdAt: DateTime.utc(2026, 7, 17),
|
||||
updatedAt: DateTime.utc(2026, 7, 17),
|
||||
originDeviceId: 'device-1',
|
||||
),
|
||||
workoutTemplateId: id,
|
||||
sourceProgramId: null,
|
||||
position: 0,
|
||||
programNameSnapshot: 'Programme',
|
||||
defaultRestSecondsSnapshot: 60,
|
||||
programSnapshotJson: jsonEncode({'exercises': const []}),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user