Wait for a toggle's retry timer before jumping past it in the offline queue tests
CI / ci (push) Successful in 5m4s
CI / ci (pull_request) Successful in 4m54s

The four tests that retry a toggle after a failed first try advanced the
fake clock by RETRY_FIRST, but the wait before a second try is RETRY_FIRST
doubled once (2000 ms). The retry only fired because shouldAdvanceTime let
real time push the clock the rest of the way inside waitFor's 1000 ms
timeout, a race that CI lost once (run 3860).

retrySecondTry now waits until that 2 * RETRY_FIRST timer is set, then
advances past it, so the jump can no longer land before the timer. The
no-online-event test also asserts nothing was sent before the timer fires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 12:47:35 -04:00
co-authored by Claude Opus 5.5
parent a7abc28c29
commit 248870897c
+19 -5
View File
@@ -1,7 +1,7 @@
import { QueryClientProvider } from '@tanstack/react-query'
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi, type Mock, type MockInstance } from 'vitest'
import { ApiProvider, createApiClient, createQueryClient, createVerifiedSession } from '../api/index.ts'
import { createNetworkStatus, type NetworkWindow } from '../pwa/networkStatus.ts'
import { HOUSEHOLD_SETTINGS } from '../test/ApiProviders.tsx'
@@ -211,6 +211,17 @@ async function advance(ms: number) {
await act(() => vi.advanceTimersByTimeAsync(ms))
}
// The page's setTimeout, watched: retrySecondTry waits for a timer to be set.
let timers: MockInstance<typeof setTimeout>
// retrySecondTry jumps past the wait before a toggle's second try — RETRY_FIRST
// doubled by its failed first try — once that wait's timer is set, so the jump
// never lands before the timer it is meant to fire.
async function retrySecondTry() {
await waitFor(() => expect(timers).toHaveBeenCalledWith(expect.any(Function), 2 * RETRY_FIRST))
await advance(2 * RETRY_FIRST)
}
function checkbox(name: string) {
return screen.getByRole('checkbox', { name: new RegExp(`^${name}`) })
}
@@ -227,6 +238,7 @@ beforeEach(() => {
// a wait (advance).
vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'], shouldAdvanceTime: true })
vi.setSystemTime(new Date('2026-10-07T16:00:00Z'))
timers = vi.spyOn(globalThis, 'setTimeout')
})
afterEach(() => {
@@ -459,7 +471,7 @@ describe('offline toggle queue, replay', () => {
expect(patches(server.fetchMock)).toHaveLength(1)
expect(queue()).toEqual(['i3 true', 'i1 false'])
await advance(RETRY_FIRST)
await retrySecondTry()
await waitFor(() => expect(queue()).toEqual([]))
expect(patches(server.fetchMock)).toEqual([
`${ITEM('i3')} {"needed":true}`,
@@ -492,7 +504,7 @@ describe('offline toggle queue, replay', () => {
expect(patches(server.fetchMock)).toEqual([])
// The next try sends a fresh /me, which the server answers.
await advance(RETRY_FIRST)
await retrySecondTry()
await waitFor(() => expect(queue()).toEqual([]))
expect(mes(server.fetchMock)).toBe(3)
expect(patches(server.fetchMock)).toEqual([`${ITEM('i3')} {"needed":true}`, `${ITEM('i1')} {"needed":false}`])
@@ -532,7 +544,7 @@ describe('offline toggle queue, replay', () => {
expect(queue()).toEqual(['i3 true', 'i1 false'])
// The next try is refused again and sends a fresh refresh, which answers.
await advance(RETRY_FIRST)
await retrySecondTry()
await waitFor(() => expect(queue()).toEqual([]))
expect(patches(server.fetchMock)).toEqual([
`${ITEM('i3')} {"needed":true}`,
@@ -555,8 +567,10 @@ describe('offline toggle queue, replay', () => {
fireEvent.click(checkbox('Corn tortillas'))
expect(await within(row('Corn tortillas')).findByText('will sync')).toBeInTheDocument()
reachable = true
expect(patches(fetchMock)).toEqual([])
await advance(RETRY_FIRST)
// Nothing says the server is back: only the wait's timer tries it again.
await retrySecondTry()
await waitFor(() => expect(queue()).toEqual([]))
// The first try failed at GET /api/v1/me, before its PATCH.
expect(patches(fetchMock)).toEqual([`${ITEM('i2')} {"needed":false}`])