0178f8f5cc
iOS Safari reuses a keep-alive socket the server closed while the login
form sat idle during typing, so the first Sign In / Create account POST
dies instantly with NSURLErrorNetworkConnectionLost ("The network
connection was lost"). That rejects the server-action call, hitting the
client-side catch in LoginForm/SignupForm and showing "An unexpected
error occurred"; the second tap lands on a fresh connection and works.
Add lib/retryAction.ts: retryOnTransportError() retries the action once
only when the call throws. A returned { error } (bad password, rate
limit) is a real result and passes straight through. A lost-on-a-stale-
socket POST never reached the server, so retrying it once is safe.
37 lines
1.6 KiB
TypeScript
37 lines
1.6 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { retryOnTransportError } from '@/lib/retryAction';
|
|
|
|
describe('retryOnTransportError', () => {
|
|
it('returns the result without retrying when the call succeeds', async () => {
|
|
const fn = vi.fn().mockResolvedValue({ success: true });
|
|
const result = await retryOnTransportError(fn);
|
|
expect(result).toEqual({ success: true });
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('passes through an application-level { error } without retrying', async () => {
|
|
// A returned error (bad password, rate limit) is a real result, not a
|
|
// transport failure — it must not trigger a retry.
|
|
const fn = vi.fn().mockResolvedValue({ error: 'Invalid email or password' });
|
|
const result = await retryOnTransportError(fn);
|
|
expect(result).toEqual({ error: 'Invalid email or password' });
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('retries once on a thrown transport error and returns the second result', async () => {
|
|
const fn = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new TypeError('The network connection was lost'))
|
|
.mockResolvedValueOnce({ success: true });
|
|
const result = await retryOnTransportError(fn);
|
|
expect(result).toEqual({ success: true });
|
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('rejects after a single retry when both attempts throw', async () => {
|
|
const fn = vi.fn().mockRejectedValue(new TypeError('Failed to fetch'));
|
|
await expect(retryOnTransportError(fn)).rejects.toThrow('Failed to fetch');
|
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|