import { beforeEach, describe, expect, it } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { findRequest, installFetch, installFailingFetch, json, makeUser, renderApp, resetStores, type FetchRoute, } from './helpers'; /** * Страница приглашения `/invite/{code}` (AGENT.md 7.9): splash сервера или * переопределение приглашения, карточка сервера, пригласивший и присоединение. */ const user = makeUser(); function guildPreview(overrides: Record = {}) { return { id: 'g-9', name: 'Сообщество', icon_file_id: 'icon-9', splash_file_id: 'splash-9', description: 'Обсуждаем всё подряд', member_count: 12, is_member: false, inviter: { id: 'user-2', username: 'bob', display_name: 'Боб', avatar_file_id: 'av-2' }, ...overrides, }; } function routes( options: { guild?: Record; background?: string } = {}, ): FetchRoute[] { return [ { match: '/api/v1/invites/abc123', method: 'POST', response: () => json({ guild_id: 'g-9' }), }, { match: '/api/v1/invites/abc123', response: () => json({ invite: { code: 'abc123', guild_id: 'g-9', ...(options.background === undefined ? {} : { background_file_id: options.background }), }, guild: options.guild ?? guildPreview(), }), }, { match: '/api/v1/users/@me', response: () => json({ user }) }, { match: '/api/v1/instance', response: () => json({ instance: { name: 'glchat-test' } }) }, { match: '/api/v1/users/@me/guilds', response: () => json({ guilds: [] }) }, { match: '/api/v1/guilds/g-9/channels', response: () => json({ channels: [] }) }, { match: '/api/v1/guilds/g-9', response: () => json({ guild: { id: 'g-9', name: 'Сообщество' } }), }, ]; } beforeEach(() => { resetStores(); }); describe('страница приглашения', () => { it('показывает splash сервера, карточку и пригласившего', async () => { installFetch(routes()); renderApp('/invite/abc123'); const page = await screen.findByTestId('invite-page'); expect(await screen.findByTestId('invite-guild-name')).toHaveTextContent('Сообщество'); expect(screen.getByText('Обсуждаем всё подряд')).toBeVisible(); expect(screen.getByText('Участников: 12')).toBeVisible(); expect(screen.getByTestId('invite-inviter')).toHaveTextContent('Боб'); // Splash сервера становится фоном страницы. const background = screen.getByTestId('invite-background').parentElement; expect(background?.querySelector('img')?.getAttribute('src')).toBe('/files/splash-9'); expect(page).toBeInTheDocument(); }); it('переопределение приглашения важнее splash сервера', async () => { installFetch(routes({ background: 'invite-bg' })); renderApp('/invite/abc123'); await screen.findByTestId('invite-page'); const background = await screen.findByTestId('invite-background'); expect(background.parentElement?.querySelector('img')?.getAttribute('src')).toBe( '/files/invite-bg', ); }); it('присоединяет по кнопке и открывает сервер', async () => { const fetchMock = installFetch(routes()); const { router } = renderApp('/invite/abc123'); await userEvent.click(await screen.findByTestId('invite-accept')); await waitFor(() => { expect(findRequest(fetchMock, { url: '/invites/abc123', method: 'POST' })).toBeDefined(); }); await waitFor(() => { expect(router.state.location.pathname).toBe('/app/g-9'); }); }); it('для участника показывает «Открыть сервер» вместо присоединения', async () => { installFetch(routes({ guild: guildPreview({ is_member: true }) })); renderApp('/invite/abc123'); expect(await screen.findByTestId('invite-open')).toBeVisible(); expect(screen.queryByTestId('invite-accept')).toBeNull(); }); it('недействительное приглашение объясняет ошибку', async () => { installFailingFetch(); renderApp('/invite/broken'); expect(await screen.findByTestId('invite-page')).toBeInTheDocument(); expect(await screen.findByText('Приглашение не работает')).toBeVisible(); }); }); describe('страница приглашения для гостя', () => { it('показывает карточку и ведёт на вход с возвратом', async () => { installFetch([ { match: '/api/v1/invites/abc123', response: () => json({ invite: { code: 'abc123', guild_id: 'g-9' }, guild: guildPreview({ is_member: false }), }), }, { match: '/api/v1/users/@me', response: () => json({ error: { code: 'auth.session_expired', message: 'no' } }, 401), }, { match: '/api/v1/instance', response: () => json({ instance: { name: 'glchat-test' } }) }, ]); renderApp('/invite/abc123'); expect(await screen.findByTestId('invite-guild-name')).toHaveTextContent('Сообщество'); await userEvent.click(screen.getByTestId('invite-login')); expect(await screen.findByLabelText('Почта', { exact: true })).toBeVisible(); }); });