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
52 changes: 52 additions & 0 deletions workout-tracker/e2e/workout-reload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,58 @@ test.describe('Workout Reload Persistence', () => {
});
});

test.describe('Back Button Timer Cleanup', () => {
/**
* Regression: pressing Back during a rest timer left the timer state in
* IndexedDB. On the next workout load, the recovery code would fire the
* notification and/or show "Time's Up!" immediately.
*/
test('stale timer from previous session does not fire on new workout start', async ({ page }) => {
await page.addInitScript(() => {
(window as any).__vibrateCount = 0;
Object.defineProperty(navigator, 'vibrate', {
value: () => { (window as any).__vibrateCount++; return true; },
writable: true,
configurable: true,
});
});

await page.goto('/');
await page.waitForSelector('#app');
await page.click('#start-workout-btn');
await page.waitForSelector('.workout-screen');

// Seed a stale timer (still has ~2 seconds remaining) — simulating what
// the Back button leaves behind because it doesn't clear IndexedDB timer state.
await page.evaluate(async () => {
const { putTimerState } = await import('/src/db/database.ts');
await putTimerState({
expectedEndTime: Date.now() + 2000, // expires in 2 seconds
durationMs: 90000,
});
});

// Navigate to home (simulating Back button navigation)
await page.evaluate(() => { window.location.hash = 'home'; });
await page.waitForSelector('#start-workout-btn');
await page.evaluate(() => { (window as any).__vibrateCount = 0; });

// Start a new workout — stale timer should be cleared, not re-used
await page.click('#start-workout-btn');
await page.waitForSelector('.workout-screen');

// Wait long enough for the stale timer to expire (>2s)
await page.waitForTimeout(2500);

// No notification should have fired from the stale timer
const vibrateCount = await page.evaluate(() => (window as any).__vibrateCount);
expect(vibrateCount).toBe(0);

// No "Time's Up!" UI should have appeared
await expect(page.locator('[data-testid="timer-expired"]')).not.toBeAttached();
});
});

test.describe('Cancel/Abandon Workout', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
Expand Down
13 changes: 12 additions & 1 deletion workout-tracker/src/ui/workout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export async function renderWorkout(container: HTMLElement): Promise<void> {

// Restore in-progress workout if one exists for this same day
const activeWorkout = await getActiveWorkout();
let resumingActiveWorkout = false;
if (
activeWorkout &&
activeWorkout.templateId === state.templateId &&
Expand All @@ -71,6 +72,15 @@ export async function renderWorkout(container: HTMLElement): Promise<void> {
completedSets.push(...activeWorkout.completedSets);
currentSetIndex = activeWorkout.currentSetIndex;
workoutStartTime = activeWorkout.startedAt;
resumingActiveWorkout = true;
}

// If starting fresh (not resuming), clear any stale timer state that may
// have been left over from a previous session (e.g. user pressed Back
// while a rest timer was running).
if (!resumingActiveWorkout) {
await putTimerState(null);
cancelBackgroundTimerNotification();
}

container.innerHTML = '';
Expand Down Expand Up @@ -498,10 +508,11 @@ export async function renderWorkout(container: HTMLElement): Promise<void> {
}

// Event listeners
document.getElementById('back-btn')?.addEventListener('click', () => {
document.getElementById('back-btn')?.addEventListener('click', async () => {
releaseWakeLock();
if (timerInterval) clearInterval(timerInterval);
cancelBackgroundTimerNotification();
await putTimerState(null);
navigate('home');
});

Expand Down
Loading