chore(wip): lot #165-169 validé QA + rework foreground #163 (KO - preuve device/toolchain insuffisante)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 17:59:43 +02:00
parent 917777e18b
commit bc533d6c45
32 changed files with 898 additions and 123 deletions

View File

@ -585,6 +585,104 @@ void main() {
viewModel.dispose();
});
testWidgets('shows reps target and direct completion on a reps-only step', (
tester,
) async {
final client = _FakeNativeWatchBridgeClient();
final viewModel = WatchSessionViewModel(nativeClient: client);
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(192, 192);
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
MaterialApp(
theme: watchTheme(),
home: WatchSessionScreen(viewModel: viewModel),
),
);
client.emitProjection(_repsStepProjection());
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(find.text('Répétitions'), findsOneWidget);
expect(find.text('12'), findsOneWidget);
expect(find.text('SÉRIE'), findsNothing);
expect(find.text('Prêt pour la série suivante'), findsNothing);
expect(find.byTooltip('Valider létape'), findsOneWidget);
expect(tester.takeException(), isNull);
await tester.tap(find.byTooltip('Valider létape'));
await tester.pump();
expect(
client.sentCommands.single.type,
WatchCommandType.completeCurrentStep,
);
await tester.pumpWidget(const SizedBox.shrink());
viewModel.dispose();
});
testWidgets(
'keeps optimistic score visible after timeout until explicit rejection',
(tester) async {
final client = _FakeNativeWatchBridgeClient();
final viewModel = WatchSessionViewModel(
nativeClient: client,
waitingThreshold: const Duration(milliseconds: 50),
commandTimeout: const Duration(milliseconds: 120),
);
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(192, 192);
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
MaterialApp(
theme: watchTheme(),
home: WatchSessionScreen(viewModel: viewModel),
),
);
client.emitProjection(_manualScoreProjection());
await tester.pump();
await tester.tap(find.byTooltip('Ajouter'));
await tester.pump();
final commandId = client.sentCommands.single.commandId;
expect(find.text('4'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 150));
expect(find.text('4'), findsOneWidget);
expect(find.byKey(const ValueKey('score-pending-dot')), findsOneWidget);
expect(client.resyncRequests, greaterThanOrEqualTo(2));
client.emitProjection(_manualScoreProjection());
await tester.pump();
expect(find.text('4'), findsOneWidget);
client.emitAck(
WatchCommandAckEvent(
commandId: commandId,
status: WatchCommandAck.rejectedNotApplicable,
sessionId: 'session-1',
),
);
await tester.pump();
expect(find.text('3'), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
viewModel.dispose();
},
);
testWidgets('vibrates once when a countdown timer reaches zero', (
tester,
) async {
@ -762,6 +860,26 @@ WatchSessionProjection _readyProjection() {
);
}
WatchSessionProjection _repsStepProjection() {
return WatchSessionProjection(
deviceSessionId: 'session-1',
revision: 7,
projectedAtEpochMs: DateTime.utc(2026, 7, 27, 10).millisecondsSinceEpoch,
phase: WatchSessionPhase.running,
phoneReachable: true,
seriesIndex: 1,
seriesTotal: 3,
exerciseName: 'Pompes',
stepIndex: 1,
stepTotal: 2,
stepName: 'Pompes strictes',
stepType: WatchStepType.reps,
stepTargetValue: 12,
primaryAction: WatchPrimaryAction.pauseSession,
secondaryActions: const [WatchSecondaryAction.skipCurrentStep],
);
}
WatchSessionProjection _secondaryRestActionProjection() {
return WatchSessionProjection(
deviceSessionId: 'session-1',