feat(web): друзья, личные беседы и вход только по приглашению
Раздел «Друзья» по запросу пользователя: - первая иконка рейки — «Друзья», серверы ниже за разделителем; личные беседы не засоряют список серверов; - сайдбар друзей: беседы с точками статуса и бейджами непрочитанного, список друзей с кликом на беседу; - контент: пустое состояние «Добавь друзей для общения» с поиском, поиск по логину (debounce), добавление в друзья, входящие и исходящие заявки, меню «…» (удалить/заблокировать), поповер последнего входа в часовом поясе пользователя (по умолчанию МСК), блок «Присоединиться по приглашению» с предпросмотром сервера; - личный чат на существующих компонентах: права по can_send канала, заголовок с именем и статусом собеседника, поиск, пины, ack и typing работают; - realtime: READY dm_channels, события DM_CHANNEL_CREATE, RELATIONSHIP_UPDATE, PRESENCE_UPDATE и MESSAGE_CREATE в беседах; - новый пользователь: «У вас пока нет серверов», создание сервера и вход по приглашению (автовступления нет); - 239 тестов Vitest (было 219), тест паритета ключей i18n (527 ключей).
This commit is contained in:
+23
-57
@@ -4,6 +4,7 @@ import { vi } from 'vitest';
|
||||
|
||||
import { GatewayOp } from '@/api/gateway';
|
||||
import {
|
||||
findRequest,
|
||||
installFetch,
|
||||
json,
|
||||
makeReadySnapshot,
|
||||
@@ -227,8 +228,8 @@ describe('основной экран', () => {
|
||||
expect(screen.queryByLabelText('Создать комнату')).toBeNull();
|
||||
});
|
||||
|
||||
it('пустой список серверов предлагает главный сервер и создание своего', async () => {
|
||||
installFetch([
|
||||
it('пустой список серверов предлагает создать свой и вступить по приглашению', async () => {
|
||||
const fetchMock = installFetch([
|
||||
{ match: '/api/v1/users/@me', response: () => json({ user }) },
|
||||
{
|
||||
match: '/api/v1/instance',
|
||||
@@ -249,69 +250,34 @@ describe('основной экран', () => {
|
||||
|
||||
renderApp('/app/empty');
|
||||
|
||||
expect(
|
||||
await screen.findByRole('button', { name: 'Присоединиться к главному серверу' }),
|
||||
).toBeVisible();
|
||||
expect(await screen.findByText('У вас пока нет серверов')).toBeVisible();
|
||||
expect(screen.getByLabelText('Создать сервер')).toBeVisible();
|
||||
expect(await screen.findByTestId('invite-join')).toBeVisible();
|
||||
// Автовступления в «главный сервер» больше нет: только по приглашению.
|
||||
expect(findRequest(fetchMock, { url: '/guilds/g-main/join', method: 'POST' })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('после вступления в главный сервер открывает его в URL', async () => {
|
||||
const { default: userEvent } = await import('@testing-library/user-event');
|
||||
const visitor = userEvent.setup();
|
||||
const mainGuild = {
|
||||
id: 'g-main',
|
||||
name: 'Главный',
|
||||
owner_id: 'user-1',
|
||||
is_main: true,
|
||||
member_count: 5,
|
||||
my_role_ids: [],
|
||||
my_permissions: [],
|
||||
};
|
||||
let joined = false;
|
||||
it('друзья — первая иконка рейки, выше серверов', async () => {
|
||||
installFetch([
|
||||
{ match: '/api/v1/users/@me', response: () => json({ user }) },
|
||||
{
|
||||
match: '/api/v1/instance',
|
||||
response: () =>
|
||||
json({
|
||||
instance: {
|
||||
name: 'glchat-test',
|
||||
registration_enabled: true,
|
||||
allow_guild_creation: true,
|
||||
max_guilds_per_user: 5,
|
||||
main_guild_id: 'g-main',
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
match: '/api/v1/guilds/g-main/join',
|
||||
method: 'POST',
|
||||
response: () => {
|
||||
joined = true;
|
||||
return json({ ok: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
match: '/api/v1/users/@me/guilds',
|
||||
response: () => json({ guilds: joined ? [mainGuild] : [] }),
|
||||
},
|
||||
{
|
||||
match: '/api/v1/guilds/g-main/channels',
|
||||
response: () =>
|
||||
json({ channels: [{ id: 'c-1', name: 'общий', type: 'text', position: 0 }] }),
|
||||
},
|
||||
{ match: '/api/v1/instance', response: () => json({ instance: { name: 'glchat-test' } }) },
|
||||
{ match: '/api/v1/users/@me/guilds', response: () => json({ guilds: [] }) },
|
||||
]);
|
||||
const gateway = installGatewaySocket(snapshot);
|
||||
|
||||
const { router } = renderApp('/app/empty');
|
||||
await visitor.click(
|
||||
await screen.findByRole('button', { name: 'Присоединиться к главному серверу' }),
|
||||
renderApp('/app/g-1/c-general');
|
||||
await screen.findByTestId('guild-rail');
|
||||
await gateway.greet();
|
||||
|
||||
// Друзья идут выше списка серверов, между ними — разделитель.
|
||||
const rail = screen.getByTestId('guild-rail');
|
||||
expect(screen.getByTestId('friends-rail-link').compareDocumentPosition(rail)).toBe(
|
||||
Node.DOCUMENT_POSITION_FOLLOWING,
|
||||
);
|
||||
|
||||
// В снапшоте сервера может не быть — REST-подстраховка обновляет список,
|
||||
// после чего пользователь попадает в первую комнату сервера.
|
||||
await waitFor(() => {
|
||||
expect(router.state.location.pathname).toStrictEqual(expect.stringContaining('/app/g-main'));
|
||||
});
|
||||
expect(within(rail).getAllByRole('link')).toHaveLength(2);
|
||||
expect(screen.getByRole('separator')).toBeVisible();
|
||||
// Личные беседы в рейку не попадают: там только раздел друзей и серверы.
|
||||
expect(screen.getByRole('link', { name: 'Друзья' })).toHaveAttribute('href', '/app/friends');
|
||||
});
|
||||
|
||||
it('создаёт сервер через модальное окно', async () => {
|
||||
|
||||
Reference in New Issue
Block a user