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

@ -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,

View File

@ -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,