Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,13 @@ class ActiveSessionEngine(
// Playing both was causing multiple sounds to fire at once (sound stacking bug).
// Issue #182: Trigger set completion immediately on WORKOUT_COMPLETE event.
if (executionGuard.isCurrent(eventLease) && coordinator._workoutState.value is WorkoutState.Active) {
// Issue #703: Flush _repCount.value BEFORE handleSetCompletion reads it.
// repCounter.process() has already updated its internal workingReps,
// but coordinator._repCount.value is still stale (the StateFlow write in
// handleRepNotification happens after this callback returns). handleSetCompletion
// -> captureExitSnapshot reads _repCount.value, so we must ensure it reflects
// the final rep count before the snapshot is captured.
coordinator._repCount.value = repCounter.getRepCount()
Logger.d("WORKOUT_COMPLETE event received - triggering immediate set completion")
handleSetCompletion(eventLease)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -917,4 +917,92 @@ class WorkoutExitPersistenceTest {
velocityB = 130.0,
),
)

/**
* Issue #703: Rep count off-by-one when ROM reps precede working set.
*
* When the Nth rep notification triggers WORKOUT_COMPLETE, handleSetCompletion
* reads coordinator._repCount.value which was still stale (N-1) because the
* StateFlow write in handleRepNotification occurs AFTER repCounter.process()
* returns. The fix flushes _repCount.value inside the WORKOUT_COMPLETE handler
* before calling handleSetCompletion.
*/
@Test
fun `issue 703 - working reps persist correctly when ROM reps precede working set`() = runTest {
val harness = DWSMTestHarness(this)
try {
// Setup: reps=7, warmupReps=3 (matching reporter's scenario)
harness.fakeExerciseRepo.addExercise(TestFixtures.benchPress)
harness.fakeBleRepo.simulateConnect("Vee_Test")
harness.dwsm.updateWorkoutParameters(
WorkoutParameters(
programMode = ProgramMode.OldSchool,
reps = 7,
warmupReps = 3,
weightPerCableKg = 25f,
selectedExerciseId = TestFixtures.benchPress.id,
),
)
harness.dwsm.startWorkout(skipCountdown = true)
harness.testScope.testScheduler.advanceUntilIdle()

val lease = harness.activeSessionEngine.currentExecutionLeaseForTest()

// Simulate 3 ROM/warmup reps
repeat(3) { romRep ->
harness.fakeBleRepo.emitRepNotification(
harness.modernRepPacket(
repsSetCount = romRep,
repsSetTotal = 7,
timestamp = harness.nowMs + romRep * 1000L,
repsRomCount = romRep + 1,
repsRomTotal = 3,
),
)
harness.testScope.testScheduler.advanceUntilIdle()
}

// Simulate 7 working reps (repsSetCount 0..6, repsRomCount stays at 3)
repeat(7) { workingRep ->
harness.fakeBleRepo.emitRepNotification(
harness.modernRepPacket(
repsSetCount = workingRep + 1,
repsSetTotal = 7,
timestamp = harness.nowMs + (3 + workingRep) * 1000L,
repsRomCount = 3,
repsRomTotal = 3,
),
)
harness.testScope.testScheduler.advanceUntilIdle()
}

// Verify the persisted session has workingReps=7, not 6
val savedSessions = harness.fakeWorkoutRepo.saveSessionAttempts
assertTrue(savedSessions.isNotEmpty(), "Expected at least one saved session")
val lastSession = savedSessions.last()
assertEquals(
7,
lastSession.workingReps,
"Issue #703: workingReps should be 7, not ${lastSession.workingReps}. " +
"The StateFlow was stale when WORKOUT_COMPLETE fired.",
)
assertEquals(
7,
lastSession.totalReps,
"Issue #703: totalReps should be 7",
)

// Verify completedSet also has correct rep count
val savedCompletedSets = harness.fakeCompletedSetRepo.saved
assertTrue(savedCompletedSets.isNotEmpty(), "Expected at least one completed set")
val lastCompletedSet = savedCompletedSets.last()
assertEquals(
7,
lastCompletedSet.actualReps,
"Issue #703: CompletedSet.actualReps should be 7, not ${lastCompletedSet.actualReps}",
)
} finally {
harness.cleanup()
}
}
}
Loading