// @vitest-environment jsdom import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react'; import {MemoryRouter} from 'react-router-dom'; import {toast} from 'sonner'; import AdminUsersPage from './AdminUsersPage.jsx'; import {PreferencesProvider} from '../context/PreferencesContext.jsx'; vi.mock('react-i18next', () => import('../test/i18n-mock.js')); vi.mock('sonner', () => ({toast: {success: vi.fn(), error: vi.fn()}})); const PLAYER = { id: 7, email: 'player@example.com', displayName: 'Player', role: 'USER', registrationDate: '2026-09-01T10:00:00', lastLoginDate: null, steamLinked: true, blocked: false, deletedAt: null, }; const ADMIN = {...PLAYER, id: 1, email: 'admin@example.com', role: 'PLATFORM_ADMIN', steamLinked: false}; const page = (content, totalPages = 1) => ({content, page: 0, size: 20, totalElements: content.length, totalPages}); const answer = (status, body) => ({ok: status < 300, status, headers: new Headers(), json: async () => body}); const renderPage = () => render(); const calls = (method) => fetch.mock.calls.filter(([, init]) => (init?.method ?? 'GET') === method); describe('AdminUsersPage', () => { beforeEach(() => { localStorage.setItem('tbp_session', '1'); globalThis.fetch = vi.fn(async (url, init) => (init?.method ? answer(204) : answer(200, page([ADMIN, PLAYER])))); }); afterEach(() => { cleanup(); localStorage.clear(); vi.clearAllMocks(); }); it('lists the users and offers no action on an admin', async () => { renderPage(); expect(await screen.findByText('player@example.com')).toBeTruthy(); expect(screen.getByText('admin@example.com')).toBeTruthy(); expect(screen.getAllByRole('button', {name: 'ADMIN.BLOCK'})).toHaveLength(1); expect(screen.getByText('ADMIN.STEAM')).toBeTruthy(); }); it('searches by the typed text from the first page', async () => { renderPage(); await screen.findByText('player@example.com'); fireEvent.change(screen.getByPlaceholderText('ADMIN.SEARCH'), {target: {value: ' play '}}); fireEvent.submit(screen.getByPlaceholderText('ADMIN.SEARCH').closest('form')); await waitFor(() => expect(calls('GET').at(-1)[0]).toContain('query=play&page=0')); }); it('blocks a user after the confirm and loads the list again', async () => { renderPage(); fireEvent.click(await screen.findByRole('button', {name: 'ADMIN.BLOCK'})); fireEvent.click(screen.getAllByRole('button', {name: 'ADMIN.BLOCK'}).at(-1)); await waitFor(() => expect(toast.success).toHaveBeenCalledWith('ADMIN.DONE_BLOCK')); const [url, init] = calls('PUT')[0]; expect(url).toMatch(/\/api\/admin\/users\/7\/blocked$/); expect(JSON.parse(init.body)).toEqual({blocked: true}); expect(calls('GET')).toHaveLength(2); }); it('deletes only after the email is typed', async () => { renderPage(); fireEvent.click(await screen.findByRole('button', {name: 'ADMIN.DELETE'})); const confirm = () => screen.getAllByRole('button', {name: 'ADMIN.DELETE'}).at(-1); expect(confirm().disabled).toBe(true); fireEvent.change(screen.getByLabelText('ADMIN.TYPE_EMAIL'), {target: {value: 'PLAYER@example.com'}}); fireEvent.click(confirm()); await waitFor(() => expect(calls('DELETE')[0][0]).toMatch(/\/api\/admin\/users\/7$/)); }); it('shows the reason when Auth refuses', async () => { fetch.mockImplementation(async (url, init) => (init?.method ? answer(409, {code: 'TARGET_IS_ADMIN'}) : answer(200, page([PLAYER])))); renderPage(); fireEvent.click(await screen.findByRole('button', {name: 'ADMIN.BLOCK'})); fireEvent.click(screen.getAllByRole('button', {name: 'ADMIN.BLOCK'}).at(-1)); await waitFor(() => expect(toast.error).toHaveBeenCalledWith('ADMIN.ERROR_TARGET_IS_ADMIN')); }); it('says no access to a user without the admin role', async () => { fetch.mockResolvedValue(answer(403, {})); renderPage(); expect(await screen.findByText('ADMIN.NO_ACCESS')).toBeTruthy(); }); });