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
@@ -6,8 +6,11 @@ import GameLibrary from './GameLibrary.jsx';
|
||||
import {PreferencesProvider} from '../context/PreferencesContext.jsx';
|
||||
import {CurrentUserProvider} from '../context/CurrentUserContext.jsx';
|
||||
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
||||
import {renderWithProviders} from '../test/renderWithProviders.jsx';
|
||||
import {toast} from 'sonner';
|
||||
|
||||
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||
vi.mock('sonner', () => ({toast: {success: vi.fn(), error: vi.fn()}}));
|
||||
vi.mock('../hooks/useLibrarySync.js', () => ({default: () => ({status: 'OK', lastSyncedAt: null, isRunning: false})}));
|
||||
vi.mock('../hooks/useForceSync.js', () => ({default: () => ({trigger: vi.fn(), busy: false})}));
|
||||
|
||||
@@ -70,6 +73,36 @@ describe('GameLibrary search', () => {
|
||||
expect(screen.queryByText(/\/5$/)).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the newest search when an older answer comes last', async () => {
|
||||
const later = [];
|
||||
const games = (name) => json(200, {...page, content: [{...page.content[0], name}], totalElements: 1, totalPages: 1});
|
||||
fetchRoutes({
|
||||
'GET /api/users/me': json(200, {settings: {}}),
|
||||
'GET /api/library/games-count': json(200, {count: 1, hasSteamId: true}),
|
||||
'GET /api/library/games': (url, init) => {
|
||||
const q = new URL(url, 'http://x').searchParams.get('q');
|
||||
if (!q) return games('Half-Life 2');
|
||||
// The first search answers only after the second one.
|
||||
return new Promise((resolve, reject) => {
|
||||
later.push({q, answer: () => resolve(games(q === 'ha' ? 'Old answer' : 'New answer'))});
|
||||
init?.signal?.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')));
|
||||
});
|
||||
},
|
||||
});
|
||||
renderLibrary();
|
||||
await screen.findByText('Half-Life 2');
|
||||
|
||||
typeInto('ha');
|
||||
await waitFor(() => expect(later).toHaveLength(1));
|
||||
typeInto('half');
|
||||
await waitFor(() => expect(later).toHaveLength(2));
|
||||
await act(async () => later[1].answer());
|
||||
await act(async () => later[0].answer());
|
||||
|
||||
expect(screen.getByText('New answer')).toBeTruthy();
|
||||
expect(screen.queryByText('Old answer')).toBeNull();
|
||||
});
|
||||
|
||||
it('writes the trimmed search to the URL after the delay and resets the page', async () => {
|
||||
const lib = renderLibrary('/library?page=3');
|
||||
|
||||
@@ -216,3 +249,21 @@ describe('GameLibrary with games added by hand', () => {
|
||||
expect(screen.getByText('LIBRARY.RESYNC')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GameLibrary session', () => {
|
||||
it('opens the login page on a 401 that the refresh cannot fix, without an error', async () => {
|
||||
fetchRoutes({
|
||||
'GET /api/users/me': json(200, {settings: {}}),
|
||||
'GET /api/library/games': json(401, {}),
|
||||
'GET /api/library/games-count': json(401, {}),
|
||||
'POST /api/auth/refresh': json(401, {}),
|
||||
});
|
||||
const {router} = renderWithProviders(<GameLibrary/>, {path: '/library', entries: ['/library?q=zelda'],
|
||||
guarded: true, routes: {'/login': <p>login</p>}});
|
||||
|
||||
await waitFor(() => expect(router.state.location.pathname).toBe('/login'));
|
||||
expect(router.state.location.state).toEqual({sessionExpired: true, from: '/library?q=zelda'});
|
||||
expect(toast.error).not.toHaveBeenCalled();
|
||||
expect(screen.queryByText('LIBRARY.ERROR_LOAD')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user