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:
@ -98,6 +98,44 @@ void main() {
|
||||
expect(env.repository.stepResults, hasLength(1));
|
||||
});
|
||||
|
||||
test('routes completeCurrentStep to completed reps step', () async {
|
||||
final session = _session(
|
||||
steps: [_step(type: ExerciseStepType.reps, defaultTargetValue: 12)],
|
||||
);
|
||||
final env = _env(
|
||||
session: session,
|
||||
projection: _projection(
|
||||
stepType: WatchStepType.reps,
|
||||
stepTargetValue: 12,
|
||||
),
|
||||
);
|
||||
env.repository.stepProgressStates['step-state'] = _stepState(
|
||||
sessionId: session.metadata.id,
|
||||
);
|
||||
|
||||
final ack = await env.dispatch(WatchCommandType.completeCurrentStep);
|
||||
|
||||
expect(ack, WatchCommandAck.accepted);
|
||||
expect(env.repository.stepResults.single.status, SetResultStatus.completed);
|
||||
expect(env.repository.stepResults.single.actualReps, 12);
|
||||
});
|
||||
|
||||
test(
|
||||
'rejects completeCurrentStep when current projection is not reps',
|
||||
() async {
|
||||
final session = _session(steps: [_step()]);
|
||||
final env = _env(session: session, projection: _projection());
|
||||
env.repository.stepProgressStates['step-state'] = _stepState(
|
||||
sessionId: session.metadata.id,
|
||||
);
|
||||
|
||||
final ack = await env.dispatch(WatchCommandType.completeCurrentStep);
|
||||
|
||||
expect(ack, WatchCommandAck.rejectedNotApplicable);
|
||||
expect(env.repository.stepResults, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
test('routes skipCurrentPassage to step use case', () async {
|
||||
final session = _session(
|
||||
targetReps: 2,
|
||||
@ -542,6 +580,8 @@ WatchSessionProjection _projection({
|
||||
double? currentManualScoreValue,
|
||||
bool canDecrementScore = false,
|
||||
WatchManualScoreScope? manualScoreScope,
|
||||
WatchStepType? stepType,
|
||||
int? stepTargetValue,
|
||||
}) {
|
||||
return WatchSessionProjection(
|
||||
deviceSessionId: deviceSessionId,
|
||||
@ -557,6 +597,8 @@ WatchSessionProjection _projection({
|
||||
hasManualScore: hasManualScore,
|
||||
currentManualScoreValue: currentManualScoreValue,
|
||||
canDecrementScore: canDecrementScore,
|
||||
stepType: stepType,
|
||||
stepTargetValue: stepTargetValue,
|
||||
manualScoreScope:
|
||||
manualScoreScope ??
|
||||
(hasManualScore ? WatchManualScoreScope.series : null),
|
||||
@ -617,6 +659,8 @@ ActiveWorkoutSession _session({
|
||||
ExerciseStep _step({
|
||||
String id = 'step-1',
|
||||
int position = 0,
|
||||
ExerciseStepType type = ExerciseStepType.time,
|
||||
int defaultTargetValue = 1,
|
||||
bool hasScore = false,
|
||||
String? scoreLabel,
|
||||
String? scoreUnit,
|
||||
@ -626,8 +670,8 @@ ExerciseStep _step({
|
||||
id: id,
|
||||
position: position,
|
||||
name: 'Step ${position + 1}',
|
||||
type: ExerciseStepType.time,
|
||||
defaultTargetValue: 1,
|
||||
type: type,
|
||||
defaultTargetValue: defaultTargetValue,
|
||||
hasScore: hasScore,
|
||||
scoreLabel: scoreLabel,
|
||||
scoreUnit: scoreUnit,
|
||||
@ -701,6 +745,8 @@ final class _FakeProjectionSource implements WatchProjectionSource {
|
||||
seriesIndex: projection.seriesIndex,
|
||||
seriesTotal: projection.seriesTotal,
|
||||
exerciseName: projection.exerciseName,
|
||||
stepType: projection.stepType,
|
||||
stepTargetValue: projection.stepTargetValue,
|
||||
primaryAction: projection.primaryAction,
|
||||
secondaryActions: projection.secondaryActions,
|
||||
hasManualScore: projection.hasManualScore,
|
||||
|
||||
@ -40,6 +40,20 @@ void main() {
|
||||
expect(projection.primaryAction, WatchPrimaryAction.startCurrentExercise);
|
||||
});
|
||||
|
||||
test('projects reps step target for direct watch validation', () async {
|
||||
final repository = _FakeActiveSessionRepository()
|
||||
..session = _session(
|
||||
steps: [_step(type: ExerciseStepType.reps, defaultTargetValue: 12)],
|
||||
);
|
||||
final projector = _projector(repository, _clock());
|
||||
|
||||
final projection = await projector.project(revision: 1);
|
||||
|
||||
expect(projection.stepType, WatchStepType.reps);
|
||||
expect(projection.stepTargetValue, 12);
|
||||
expect(projection.dominantTimer, isNull);
|
||||
});
|
||||
|
||||
test('projects running with step timer before stopwatch score', () async {
|
||||
final now = DateTime.utc(2026, 7, 25, 12);
|
||||
final session = _session(
|
||||
@ -299,6 +313,42 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'projects nextTimerReady for a stopped timed step after watch skip with chaining enabled',
|
||||
() async {
|
||||
final now = DateTime.utc(2026, 7, 25, 12);
|
||||
final session = _session(
|
||||
autoStartNextTimedStepSnapshot: true,
|
||||
steps: [
|
||||
_step(id: 'step-1'),
|
||||
_step(id: 'step-2', position: 1),
|
||||
],
|
||||
);
|
||||
final repository = _FakeActiveSessionRepository()
|
||||
..session = session
|
||||
..stepProgressStates['step-state'] = _stepState(
|
||||
sessionId: session.metadata.id,
|
||||
stepId: 'step-2',
|
||||
stepIndex: 1,
|
||||
status: ActiveExerciseStepProgressStatus.stoppedTimer,
|
||||
lastTransitionAt: now,
|
||||
);
|
||||
final projector = _projector(repository, _clock(now));
|
||||
|
||||
final projection = await projector.project(revision: 4);
|
||||
|
||||
expect(projection.phase, WatchSessionPhase.nextTimerReady);
|
||||
expect(projection.stepIndex, 2);
|
||||
expect(projection.stepName, 'Step 2');
|
||||
expect(projection.statusLabel, 'Chrono suivant prêt');
|
||||
expect(
|
||||
projection.primaryAction,
|
||||
WatchPrimaryAction.startPreparedTimedStep,
|
||||
);
|
||||
expect(projection.dominantTimer?.runState, WatchTimerRunState.stopped);
|
||||
},
|
||||
);
|
||||
|
||||
test('projects restRunning after finishing a set with rest', () async {
|
||||
final now = DateTime.utc(2026, 7, 25, 12);
|
||||
final session = _session(currentSetIndex: 1);
|
||||
@ -406,7 +456,7 @@ void main() {
|
||||
final projection = await projector.project(revision: 6);
|
||||
|
||||
expect(projection.phase, WatchSessionPhase.betweenSetsReady);
|
||||
expect(projection.statusLabel, 'Prêt pour la série suivante');
|
||||
expect(projection.statusLabel, isNull);
|
||||
expect(projection.primaryAction, WatchPrimaryAction.startCurrentExercise);
|
||||
},
|
||||
);
|
||||
@ -594,6 +644,7 @@ ActiveWorkoutSession _session({
|
||||
ExerciseStep _step({
|
||||
String id = 'step-1',
|
||||
int position = 0,
|
||||
ExerciseStepType type = ExerciseStepType.time,
|
||||
int defaultTargetValue = 1,
|
||||
bool hasScore = false,
|
||||
String? scoreLabel,
|
||||
@ -604,7 +655,7 @@ ExerciseStep _step({
|
||||
id: id,
|
||||
position: position,
|
||||
name: 'Step ${position + 1}',
|
||||
type: ExerciseStepType.time,
|
||||
type: type,
|
||||
defaultTargetValue: defaultTargetValue,
|
||||
hasScore: hasScore,
|
||||
scoreLabel: scoreLabel,
|
||||
|
||||
@ -71,7 +71,7 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('le détail affiche la fréquence cardiaque quand elle existe', (
|
||||
testWidgets('le détail affiche les stats montre quand elles existent', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
@ -79,8 +79,11 @@ void main() {
|
||||
home: HistoryDetailScreen(
|
||||
history: _history(
|
||||
id: 'history-1',
|
||||
minHeartRateBpm: 88,
|
||||
averageHeartRateBpm: 126.4,
|
||||
maxHeartRateBpm: 171,
|
||||
totalDistanceMeters: 1234,
|
||||
totalCaloriesKcal: 83,
|
||||
),
|
||||
historyUseCases: _historyUseCases(_FakeWorkoutHistoryRepository()),
|
||||
workoutTemplateUseCases: _workoutTemplateUseCases(),
|
||||
@ -90,11 +93,22 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Stats montre'), findsOneWidget);
|
||||
expect(find.text('Fréquence cardiaque'), findsOneWidget);
|
||||
expect(find.text('Min'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('Moyenne'), findsOneWidget);
|
||||
expect(find.text('Max'), findsOneWidget);
|
||||
expect(find.text('Max'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('Distance'), findsOneWidget);
|
||||
expect(find.text('Calories'), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(const ValueKey('history-watch-stats-graph')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('88 bpm', findRichText: true), findsOneWidget);
|
||||
expect(find.text('126 bpm', findRichText: true), findsOneWidget);
|
||||
expect(find.text('171 bpm', findRichText: true), findsOneWidget);
|
||||
expect(find.text('1.23 km', findRichText: true), findsOneWidget);
|
||||
expect(find.text('83 kcal', findRichText: true), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('le détail affiche le score chrono comme temps réalisé', (
|
||||
@ -256,8 +270,11 @@ WorkoutHistory _history({
|
||||
bool stopwatchScore = false,
|
||||
bool withStepResults = false,
|
||||
bool emptySnapshot = false,
|
||||
int? minHeartRateBpm,
|
||||
double? averageHeartRateBpm,
|
||||
int? maxHeartRateBpm,
|
||||
double? totalDistanceMeters,
|
||||
double? totalCaloriesKcal,
|
||||
}) {
|
||||
final start = startedAt ?? DateTime.utc(2026, 7, 17, 10);
|
||||
return WorkoutHistory(
|
||||
@ -268,8 +285,11 @@ WorkoutHistory _history({
|
||||
startedAt: start,
|
||||
endedAt: start.add(const Duration(minutes: 30)),
|
||||
totalActiveMs: 1800000,
|
||||
minHeartRateBpm: minHeartRateBpm,
|
||||
averageHeartRateBpm: averageHeartRateBpm,
|
||||
maxHeartRateBpm: maxHeartRateBpm,
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
totalCaloriesKcal: totalCaloriesKcal,
|
||||
completed: true,
|
||||
historySnapshotJson: jsonEncode({
|
||||
'sessionId': 'session-1',
|
||||
|
||||
Reference in New Issue
Block a user