Public Access
feat(onboarding): remove dead ends for new users (#55)
Registration shows the address used and can start over. Confirm page says an admin approves; no login link. Welcome puts next steps first; Steam link returns to the dashboard. Dashboard and library explain an empty or private library. First sync shows queued, polls slower up to 10 min, then a calm text. Login: simple checks, resend offer after 401, password toggles. Only a real expired link counts as invalid; typed passwords stay. Default language follows the browser; texts are platform-neutral.
This commit is contained in:
1 parent
a60010e055
commit
f9f0d94e2c
45 files changed
+979
-171
No files matched your search
@@ -24,7 +24,7 @@ describe('useLibrarySync', () => {
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
expect(result.current.isRunning).toBe(true);
|
||||
|
||||
await act(() => vi.advanceTimersByTimeAsync(125_000));
|
||||
await act(() => vi.advanceTimersByTimeAsync(605_000));
|
||||
|
||||
expect(result.current.isRunning).toBe(false);
|
||||
expect(result.current.stale).toBe(true);
|
||||
@@ -33,6 +33,70 @@ describe('useLibrarySync', () => {
|
||||
expect(fetch.mock.calls.length).toBe(calls);
|
||||
});
|
||||
|
||||
it('isNotQueuedWhenDisabled', async () => {
|
||||
fetchRoutes({});
|
||||
const {result} = renderHook(() => useLibrarySync({enabled: false, expectInitialSync: true, onComplete: () => {}}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
|
||||
expect(result.current.queued).toBe(false);
|
||||
});
|
||||
|
||||
it('is queued while a first sync is expected and Core has no status row', async () => {
|
||||
fetchRoutes({'GET /api/library/sync-status': status(null, null)});
|
||||
const {result} = renderHook(() => useLibrarySync({enabled: true, expectInitialSync: true, onComplete: () => {}}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
|
||||
expect(result.current.queued).toBe(true);
|
||||
});
|
||||
|
||||
it('is not queued without expectInitialSync, or once a row exists', async () => {
|
||||
fetchRoutes({'GET /api/library/sync-status': status(null, null)});
|
||||
const first = renderHook(() => useLibrarySync({enabled: true, onComplete: () => {}}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
expect(first.result.current.queued).toBe(false);
|
||||
cleanup();
|
||||
|
||||
fetchRoutes({'GET /api/library/sync-status': status('RUNNING', null)});
|
||||
const second = renderHook(() => useLibrarySync({enabled: true, expectInitialSync: true, onComplete: () => {}}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
expect(second.result.current.queued).toBe(false);
|
||||
});
|
||||
|
||||
it('polls every 3 s for 15 s while queued, then every 15 s, and stops after 10 minutes', async () => {
|
||||
const fetch = fetchRoutes({'GET /api/library/sync-status': status(null, null)});
|
||||
renderHook(() => useLibrarySync({enabled: true, expectInitialSync: true, onComplete: () => {}}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
expect(fetch.mock.calls.length).toBe(1);
|
||||
|
||||
await act(() => vi.advanceTimersByTimeAsync(15_000));
|
||||
expect(fetch.mock.calls.length).toBe(6);
|
||||
|
||||
await act(() => vi.advanceTimersByTimeAsync(60_000));
|
||||
expect(fetch.mock.calls.length).toBe(10);
|
||||
|
||||
await act(() => vi.advanceTimersByTimeAsync(600_000));
|
||||
const calls = fetch.mock.calls.length;
|
||||
await act(() => vi.advanceTimersByTimeAsync(60_000));
|
||||
expect(fetch.mock.calls.length).toBe(calls);
|
||||
});
|
||||
|
||||
it('still sees a first sync that starts after one minute', async () => {
|
||||
let answer = status(null, null);
|
||||
fetchRoutes({'GET /api/library/sync-status': () => answer});
|
||||
const onComplete = vi.fn();
|
||||
const {result} = renderHook(() => useLibrarySync({enabled: true, expectInitialSync: true, onComplete}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(60_000));
|
||||
expect(result.current.isRunning).toBe(false);
|
||||
|
||||
answer = status('RUNNING', null);
|
||||
await act(() => vi.advanceTimersByTimeAsync(15_000));
|
||||
expect(result.current.isRunning).toBe(true);
|
||||
|
||||
answer = status('OK', '2026-10-21T08:00:00Z');
|
||||
await act(() => vi.advanceTimersByTimeAsync(15_000));
|
||||
expect(onComplete).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('after watch() reports the end of a sync once, also when it never saw it running', async () => {
|
||||
let answer = status('OK', '2026-09-30T08:00:00Z');
|
||||
fetchRoutes({'GET /api/library/sync-status': () => answer});
|
||||
|
||||
Reference in new issue
Block a user