// @vitest-environment jsdom import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import {act, cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react'; import {MemoryRouter, useLocation} from 'react-router-dom'; import App from './App.jsx'; import {PreferencesProvider} from './context/PreferencesContext.jsx'; vi.mock('react-i18next', () => import('./test/i18n-mock.js')); vi.mock('./hooks/useLibrarySync.js', () => ({default: () => ({status: 'OK', lastSyncedAt: null, isRunning: false})})); vi.mock('./hooks/useForceSync.js', () => ({default: () => ({trigger: vi.fn(), busy: false})})); const DEBOUNCE_WAIT_MS = 700; const b64 = (o) => btoa(JSON.stringify(o)).replace(/=+$/, ''); // Expired by the browser clock. A clock that runs ahead of the server makes the backend still accept it. const EXPIRED_BY_LOCAL_CLOCK = `${b64({alg: 'EdDSA'})}.${b64({exp: Math.floor(Date.now() / 1000) - 60})}.sig`; const page = {content: [{appid: 220, name: 'Half-Life 2', playtimeMinutes: 0, status: 'BACKLOG'}], page: 0, size: 20, totalElements: 1, totalPages: 1}; const wait = (ms) => act(() => new Promise((resolve) => setTimeout(resolve, ms))); const current = {location: null}; function Spy() { current.location = useLocation(); return null; } beforeEach(() => { localStorage.setItem('tbp_jwt', EXPIRED_BY_LOCAL_CLOCK); globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => page})); }); afterEach(() => { cleanup(); localStorage.clear(); }); describe('App', () => { it('keeps a token that only the browser clock calls expired; a 401 from the backend ends the session', async () => { render( , ); await waitFor(() => expect(screen.getByText('Half-Life 2')).toBeTruthy()); expect(localStorage.getItem('tbp_jwt')).toBe(EXPIRED_BY_LOCAL_CLOCK); fetch.mockImplementation(async () => ({ok: false, status: 401, json: async () => ({})})); fireEvent.change(screen.getByRole('searchbox'), {target: {value: 'half'}}); await wait(DEBOUNCE_WAIT_MS); await waitFor(() => expect(current.location.pathname).toBe('/login')); expect(current.location.state).toEqual({sessionExpired: true, from: '/library?q=half&page=0'}); expect(localStorage.getItem('tbp_jwt')).toBeNull(); }); });