566e1cf981
- миграция 00015: переопределение splash у приглашения (AGENT.md 7.9)
- GET /invites/{code} доступен без сессии: гость по ссылке видит карточку
сервера, описание, число участников и пригласившего
- POST/DELETE /invites/{code}/background: своя картинка страницы под правом
создателя или MANAGE_GUILD, файл удаляется при замене и снятии
- клиент: маршрут /invite/:code с фоном (переопределение → splash → баннер),
карточкой сервера, кнопками «Присоединиться»/«Открыть сервер» и входом с
возвратом на страницу (?next=)
- тесты: Go (карточка, загрузка, права, снятие) и Vitest (splash, гость,
присоединение, недействительная ссылка)
154 lines
5.6 KiB
TypeScript
154 lines
5.6 KiB
TypeScript
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<string, unknown> = {}) {
|
|
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<string, unknown>; 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();
|
|
});
|
|
});
|