Public Access
Add /search to find games by name on IGDB. A game in the library opens its game page. Other hits open a detail view with an Add button. The query stays in the URL. To fit the new nav icon on 360 px phones, the header padding is smaller and the points chip hides while the Questions icon shows.
229 lines
10 KiB
React
229 lines
10 KiB
React
// @vitest-environment jsdom
|
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
import {cleanup, fireEvent, screen} from '@testing-library/react';
|
|
import {toast} from 'sonner';
|
|
import {useTranslation} from 'react-i18next';
|
|
import AppHeader from './AppHeader.jsx';
|
|
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
|
import {renderWithProviders} from '../test/renderWithProviders.jsx';
|
|
|
|
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
|
vi.mock('sonner', () => ({toast: {success: vi.fn(), error: vi.fn()}}));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
localStorage.clear();
|
|
useTranslation().i18n.language = 'de';
|
|
});
|
|
|
|
const loggedInAs = (me, routes = {}, options = {}) => {
|
|
localStorage.setItem('tbp_session', '1');
|
|
fetchRoutes({'GET /api/users/me': json(200, me), 'GET /api/library/steam-profile': json(200, {}),
|
|
'GET /api/inbox': json(200, {results: [], unread: 0}),
|
|
'GET /api/library/questions/count': json(200, {count: 0}),
|
|
'GET /api/library/removed-games': json(200, {results: []}), ...routes});
|
|
renderWithProviders(<AppHeader isAuthenticated onLogout={() => {}}/>, options);
|
|
};
|
|
|
|
const openMenu = () => fireEvent.keyDown(screen.getByRole('button', {name: 'NAV.MENU'}), {key: 'Enter'});
|
|
|
|
describe('AppHeader', () => {
|
|
it('says so when Auth refuses the language switch', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER', settings: {language: 'de'}},
|
|
{'PUT /api/users/settings': json(500, {})});
|
|
|
|
await screen.findByRole('button', {name: 'NAV.MENU'});
|
|
openMenu();
|
|
fireEvent.click(await screen.findByRole('menuitem', {name: /NAV.LANGUAGE/}));
|
|
|
|
await vi.waitFor(() => expect(toast.error).toHaveBeenCalledWith('SETTINGS.ERROR_SAVE'));
|
|
});
|
|
|
|
// jsdom applies no media queries, so the test hides the name the way the 520px breakpoint does.
|
|
it('keeps a name on the home link when a narrow phone hides the app name', () => {
|
|
fetchRoutes({});
|
|
renderWithProviders(<AppHeader isAuthenticated={false} onLogout={() => {}}/>);
|
|
screen.getByText('ToBePlayed').style.display = 'none';
|
|
|
|
expect(screen.getByRole('link', {name: 'ToBePlayed'})).toBeTruthy();
|
|
expect(screen.queryByTitle('NAV.COMMUNITY')).toBeNull();
|
|
});
|
|
|
|
it('links to the following page when logged in', () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
|
|
expect(screen.getByTitle('NAV.FOLLOWING').getAttribute('href')).toBe('/following');
|
|
});
|
|
|
|
it('links to the game search when logged in', () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
|
|
expect(screen.getByTitle('NAV.SEARCH').getAttribute('href')).toBe('/search');
|
|
});
|
|
|
|
it('links to the community stats when logged in', () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
|
|
const link = screen.getByTitle('NAV.COMMUNITY');
|
|
|
|
expect(link.getAttribute('href')).toBe('/');
|
|
expect(link.getAttribute('aria-current')).toBe('page');
|
|
});
|
|
|
|
it('shows the community link as an icon without a text label', () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
|
|
expect(screen.getByTitle('NAV.COMMUNITY').textContent).toBe('');
|
|
});
|
|
|
|
it('marks community active only on the landing page', () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {}, {entries: ['/dashboard']});
|
|
|
|
expect(screen.getByTitle('NAV.COMMUNITY').getAttribute('aria-current')).toBeNull();
|
|
expect(screen.getByTitle('NAV.DASHBOARD').getAttribute('aria-current')).toBe('page');
|
|
});
|
|
|
|
it('shows how many inbox notes are unread', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {'GET /api/inbox': json(200, {results: [], unread: 3})});
|
|
|
|
const link = await screen.findByRole('link', {name: 'NAV.INBOX_UNREAD:3'});
|
|
|
|
expect(link.getAttribute('href')).toBe('/inbox');
|
|
expect(link.textContent).toBe('3');
|
|
});
|
|
|
|
it('shows no count without unread notes', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
|
|
const link = await screen.findByRole('link', {name: 'NAV.INBOX'});
|
|
|
|
expect(link.textContent).toBe('');
|
|
});
|
|
|
|
it('shows how many questions are open', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {'GET /api/library/questions/count': json(200, {count: 12})});
|
|
|
|
const link = await screen.findByRole('link', {name: 'NAV.QUESTIONS_OPEN:12'});
|
|
|
|
expect(link.getAttribute('href')).toBe('/questions');
|
|
expect(link.textContent).toBe('NAV.QUESTIONS9+');
|
|
});
|
|
|
|
it('hides the questions icon below sm while no question is open', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
|
|
expect((await screen.findByTitle('NAV.QUESTIONS')).className).toContain('max-sm:hidden');
|
|
});
|
|
|
|
it('keeps the questions icon at every width while questions are open', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {'GET /api/library/questions/count': json(200, {count: 2})});
|
|
|
|
expect((await screen.findByRole('link', {name: 'NAV.QUESTIONS_OPEN:2'})).className).not.toContain('max-sm:hidden');
|
|
});
|
|
|
|
it('keeps the questions icon at every width while the user has removed games', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'},
|
|
{'GET /api/library/removed-games': json(200, {results: [{appid: 1, name: 'Game'}]})});
|
|
|
|
await vi.waitFor(() => expect(screen.getByTitle('NAV.QUESTIONS').className).not.toContain('max-sm:hidden'));
|
|
});
|
|
|
|
it('skipsTheRemovedGamesCallWhileQuestionsAreOpen', async () => {
|
|
const removed = vi.fn(() => json(200, {results: []}));
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {'GET /api/library/questions/count': json(200, {count: 2}),
|
|
'GET /api/library/removed-games': removed});
|
|
|
|
await screen.findByRole('link', {name: 'NAV.QUESTIONS_OPEN:2'});
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
expect(removed).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('keeps the questions icon below sm on the questions page, without a badge', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {'GET /api/library/questions/count': json(200, {count: 0})},
|
|
{entries: ['/questions']});
|
|
|
|
expect((await screen.findByTitle('NAV.QUESTIONS')).className).not.toContain('max-sm:hidden');
|
|
});
|
|
|
|
it('shows no question count on the questions page, whose title has it', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'}, {'GET /api/library/questions/count': json(200, {count: 3})},
|
|
{entries: ['/questions']});
|
|
|
|
await vi.waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/library/questions/count'), expect.anything()));
|
|
|
|
expect(screen.getByTitle('NAV.QUESTIONS').textContent).toBe('NAV.QUESTIONS');
|
|
expect(screen.queryByRole('link', {name: 'NAV.QUESTIONS_OPEN:3'})).toBeNull();
|
|
});
|
|
|
|
it('offers the admin page and "view as" to an admin', async () => {
|
|
loggedInAs({role: 'PLATFORM_ADMIN', activeRole: 'PLATFORM_ADMIN'},
|
|
{'GET /api/admin/users/pending-count': json(200, {count: 0})});
|
|
await screen.findByRole('button', {name: 'NAV.MENU'});
|
|
await vi.waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/users/me'), expect.anything()));
|
|
|
|
openMenu();
|
|
|
|
expect(await screen.findByText('NAV.ADMIN')).toBeTruthy();
|
|
expect(screen.getByText('NAV.ADMIN_NEWS')).toBeTruthy();
|
|
expect(screen.getByText('NAV.ADMIN_BROADCASTS')).toBeTruthy();
|
|
expect(screen.getByText('NAV.VIEW_AS.MENU')).toBeTruthy();
|
|
expect(screen.queryByText('NAV.VIEW_AS.BADGE')).toBeNull();
|
|
expect(screen.queryByLabelText('NAV.PENDING_REQUESTS:0')).toBeNull();
|
|
});
|
|
|
|
it('shows the admin how many registrations wait', async () => {
|
|
loggedInAs({role: 'PLATFORM_ADMIN', activeRole: 'PLATFORM_ADMIN'},
|
|
{'GET /api/admin/users/pending-count': json(200, {count: 3})});
|
|
|
|
expect(await screen.findByLabelText('NAV.PENDING_REQUESTS:3')).toBeTruthy();
|
|
openMenu();
|
|
expect((await screen.findByRole('menuitem', {name: /^NAV\.ADMIN ?3$/})).textContent).toContain('3');
|
|
});
|
|
|
|
it('shows no count when Auth does not know the call yet', async () => {
|
|
loggedInAs({role: 'PLATFORM_ADMIN', activeRole: 'PLATFORM_ADMIN'},
|
|
{'GET /api/admin/users/pending-count': json(404, {})});
|
|
await vi.waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('pending-count'), expect.anything()));
|
|
|
|
expect(screen.queryByLabelText(/NAV.PENDING_REQUESTS/)).toBeNull();
|
|
});
|
|
|
|
it('shows the badge and hides the admin page while an admin views as a user', async () => {
|
|
loggedInAs({role: 'PLATFORM_ADMIN', activeRole: 'USER'});
|
|
|
|
expect(await screen.findByText('NAV.VIEW_AS.BADGE')).toBeTruthy();
|
|
openMenu();
|
|
expect(await screen.findByText('NAV.VIEW_AS.MENU')).toBeTruthy();
|
|
expect(screen.queryByText('NAV.ADMIN')).toBeNull();
|
|
expect(screen.queryByText('NAV.ADMIN_BROADCASTS')).toBeNull();
|
|
});
|
|
|
|
it('offersTheCatalogLinkQueueAndAuditLogToAnAuditorOnly', async () => {
|
|
loggedInAs({role: 'AUDITOR', activeRole: 'AUDITOR'});
|
|
await vi.waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/users/me'), expect.anything()));
|
|
openMenu();
|
|
|
|
expect(await screen.findByText('NAV.GAME_CATALOG')).toBeTruthy();
|
|
expect(screen.getByText('NAV.LINK_QUEUE')).toBeTruthy();
|
|
expect(screen.getByText('NAV.AUDIT_LOG')).toBeTruthy();
|
|
expect(screen.queryByText('NAV.ADMIN')).toBeNull();
|
|
});
|
|
|
|
it('offers nothing of that to a user', async () => {
|
|
loggedInAs({role: 'USER', activeRole: 'USER'});
|
|
await vi.waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/users/me'), expect.anything()));
|
|
|
|
openMenu();
|
|
|
|
expect(await screen.findByText('NAV.SETTINGS')).toBeTruthy();
|
|
expect(screen.getByText('NAV.WHATS_NEW')).toBeTruthy();
|
|
expect(screen.queryByText('NAV.ADMIN')).toBeNull();
|
|
expect(screen.queryByText('NAV.LINK_QUEUE')).toBeNull();
|
|
expect(screen.queryByText('NAV.GAME_CATALOG')).toBeNull();
|
|
expect(screen.queryByText('NAV.ADMIN_NEWS')).toBeNull();
|
|
expect(screen.queryByText('NAV.ADMIN_BROADCASTS')).toBeNull();
|
|
expect(screen.queryByText('NAV.VIEW_AS.MENU')).toBeNull();
|
|
});
|
|
});
|