Consolide le lot applicatif watch companion validé : - télémétrie fréquence cardiaque live remontée montre -> téléphone (collecteur watch, adapter Wear Data Layer, persistance Drift, propagation aux écrans historique/programme/profil/exécution) - notifications de séance en arrière-plan côté téléphone (service foreground de statut + passerelle applicative) - finitions montre : chrono d'étape, score d'étape, retrait du bouton "lancer une séance", thème, icônes et polices watch_app Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
127 lines
3.7 KiB
Dart
127 lines
3.7 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:gametime/application/application.dart';
|
|
import 'package:watch_bridge_contract/watch_bridge_contract.dart';
|
|
|
|
void main() {
|
|
test('builds active timer notification content', () {
|
|
final content = buildSessionNotificationContent(
|
|
_projection(
|
|
exerciseName: 'Squats bulgares',
|
|
dominantTimer: _timer(label: 'Chrono étape', accumulatedMs: 134000),
|
|
),
|
|
);
|
|
|
|
expect(content.title, 'Squats bulgares');
|
|
expect(content.primaryLine, '02:14');
|
|
expect(content.secondaryLine, 'Série 2/4');
|
|
});
|
|
|
|
test('builds manual score notification content', () {
|
|
final content = buildSessionNotificationContent(
|
|
_projection(
|
|
exerciseName: 'Lancers francs',
|
|
hasManualScore: true,
|
|
currentManualScoreValue: 8,
|
|
),
|
|
);
|
|
|
|
expect(content.title, 'Lancers francs');
|
|
expect(content.primaryLine, 'Série 2/4 · 8');
|
|
});
|
|
|
|
test('builds rest notification content with next exercise', () {
|
|
final now = DateTime.utc(2026, 7, 26, 10);
|
|
final content = buildSessionNotificationContent(
|
|
_projection(
|
|
phase: WatchSessionPhase.restRunning,
|
|
exerciseName: 'Squats',
|
|
nextExerciseName: 'Fentes',
|
|
dominantTimer: _timer(
|
|
kind: WatchTimerKind.rest,
|
|
displayMode: WatchTimerDisplayMode.countdown,
|
|
runState: WatchTimerRunState.running,
|
|
accumulatedMs: 10000,
|
|
targetMs: 60000,
|
|
startedAtEpochMs: now.millisecondsSinceEpoch,
|
|
),
|
|
),
|
|
now: now.add(const Duration(seconds: 5)),
|
|
);
|
|
|
|
expect(content.title, 'Repos');
|
|
expect(content.primaryLine, '00:45 restant · Ensuite : Fentes');
|
|
});
|
|
|
|
test('prefixes paused content without ticking', () {
|
|
final content = buildSessionNotificationContent(
|
|
_projection(
|
|
phase: WatchSessionPhase.paused,
|
|
dominantTimer: _timer(
|
|
runState: WatchTimerRunState.paused,
|
|
accumulatedMs: 42000,
|
|
),
|
|
),
|
|
now: DateTime.utc(2026, 7, 26, 10),
|
|
);
|
|
|
|
expect(content.primaryLine, 'En pause · 00:42');
|
|
});
|
|
|
|
test('falls back to current step name without timer or score', () {
|
|
final content = buildSessionNotificationContent(
|
|
_projection(stepName: 'Gainage'),
|
|
);
|
|
|
|
expect(content.primaryLine, 'Gainage');
|
|
});
|
|
}
|
|
|
|
WatchSessionProjection _projection({
|
|
WatchSessionPhase phase = WatchSessionPhase.running,
|
|
String exerciseName = 'Pompes',
|
|
String? stepName,
|
|
WatchTimerProjection? dominantTimer,
|
|
bool hasManualScore = false,
|
|
double? currentManualScoreValue,
|
|
String? nextExerciseName,
|
|
}) {
|
|
return WatchSessionProjection(
|
|
deviceSessionId: 'session-1',
|
|
revision: 1,
|
|
projectedAtEpochMs: DateTime.utc(2026, 7, 26, 10).millisecondsSinceEpoch,
|
|
phase: phase,
|
|
phoneReachable: true,
|
|
seriesIndex: 2,
|
|
seriesTotal: 4,
|
|
exerciseName: exerciseName,
|
|
stepName: stepName,
|
|
dominantTimer: dominantTimer,
|
|
primaryAction: WatchPrimaryAction.pauseSession,
|
|
nextExerciseName: nextExerciseName,
|
|
hasManualScore: hasManualScore,
|
|
currentManualScoreValue: currentManualScoreValue,
|
|
canDecrementScore: (currentManualScoreValue ?? 0) > 0,
|
|
);
|
|
}
|
|
|
|
WatchTimerProjection _timer({
|
|
WatchTimerKind kind = WatchTimerKind.step,
|
|
String label = 'Chrono',
|
|
WatchTimerDisplayMode displayMode = WatchTimerDisplayMode.elapsed,
|
|
WatchTimerRunState runState = WatchTimerRunState.stopped,
|
|
int accumulatedMs = 0,
|
|
int? startedAtEpochMs,
|
|
int? targetMs,
|
|
}) {
|
|
return WatchTimerProjection(
|
|
kind: kind,
|
|
label: label,
|
|
displayMode: displayMode,
|
|
runState: runState,
|
|
referenceEpochMs: DateTime.utc(2026, 7, 26, 10).millisecondsSinceEpoch,
|
|
accumulatedMs: accumulatedMs,
|
|
startedAtEpochMs: startedAtEpochMs,
|
|
targetMs: targetMs,
|
|
);
|
|
}
|