Public Access
fix: abort old loads, stale sync state, warn before unsaved edits are lost
This commit is contained in:
1 parent
cbafa3d540
commit
9ddc50f819
28 files changed
+753
-121
No files matched your search
@@ -0,0 +1,91 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
import {act, cleanup, renderHook} from '@testing-library/react';
|
||||
import useLibrarySync from './useLibrarySync.js';
|
||||
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
||||
|
||||
const status = (lastSyncStatus, lastSyncedAt) => json(200, {lastSyncStatus, lastSyncedAt});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
localStorage.setItem('tbp_session', '1');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.useRealTimers();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('useLibrarySync', () => {
|
||||
it('shows a sync that runs longer than the poll limit as stale, not as running forever', async () => {
|
||||
const fetch = fetchRoutes({'GET /api/library/sync-status': status('RUNNING', null)});
|
||||
const {result} = renderHook(() => useLibrarySync({enabled: true, onComplete: () => {}}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
expect(result.current.isRunning).toBe(true);
|
||||
|
||||
await act(() => vi.advanceTimersByTimeAsync(125_000));
|
||||
|
||||
expect(result.current.isRunning).toBe(false);
|
||||
expect(result.current.stale).toBe(true);
|
||||
const calls = fetch.mock.calls.length;
|
||||
await act(() => vi.advanceTimersByTimeAsync(30_000));
|
||||
expect(fetch.mock.calls.length).toBe(calls);
|
||||
});
|
||||
|
||||
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});
|
||||
const onComplete = vi.fn();
|
||||
const {result} = renderHook(() => useLibrarySync({enabled: true, onComplete}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
|
||||
act(() => result.current.watch());
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
// The sync was so fast that the next poll already sees it done.
|
||||
answer = status('OK', '2026-09-30T08:05:00Z');
|
||||
await act(() => vi.advanceTimersByTimeAsync(3_000));
|
||||
await act(() => vi.advanceTimersByTimeAsync(30_000));
|
||||
|
||||
expect(onComplete).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reports the end of a running sync once', async () => {
|
||||
let answer = status('RUNNING', '2026-09-30T08:00:00Z');
|
||||
fetchRoutes({'GET /api/library/sync-status': () => answer});
|
||||
const onComplete = vi.fn();
|
||||
const {result} = renderHook(() => useLibrarySync({enabled: true, onComplete}));
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
|
||||
answer = status('FAILED', '2026-09-30T08:01:00Z');
|
||||
await act(() => vi.advanceTimersByTimeAsync(3_000));
|
||||
await act(() => vi.advanceTimersByTimeAsync(30_000));
|
||||
|
||||
expect(onComplete).toHaveBeenCalledTimes(1);
|
||||
expect(result.current.status).toBe('FAILED');
|
||||
});
|
||||
|
||||
it('fires onComplete only once when expectInitialSync flips after the watched run ended', async () => {
|
||||
let answer = status('OK', '2026-09-30T08:00:00Z');
|
||||
fetchRoutes({'GET /api/library/sync-status': () => answer});
|
||||
const onComplete = vi.fn();
|
||||
const {result, rerender} = renderHook(
|
||||
({expectInitialSync}) => useLibrarySync({enabled: true, expectInitialSync, onComplete}),
|
||||
{initialProps: {expectInitialSync: false}});
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
|
||||
act(() => result.current.watch());
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
answer = status('OK', '2026-09-30T08:05:00Z');
|
||||
await act(() => vi.advanceTimersByTimeAsync(3_000));
|
||||
expect(onComplete).toHaveBeenCalledTimes(1);
|
||||
|
||||
// onComplete loads the list: isLoading goes true and back to false, and with 0 games expectInitialSync follows.
|
||||
rerender({expectInitialSync: true});
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
rerender({expectInitialSync: false});
|
||||
await act(() => vi.advanceTimersByTimeAsync(0));
|
||||
|
||||
expect(onComplete).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user