feat(web): профиль с аватаром, панель в углу, настройки сервера, скрытые ID
Правки пользователя: - в «Профиль → Основной» сверху квадратный аватар слева, справа от него настройки имени, «О себе», состояния и эмодзи во всю высоту; смена аватара — клик по нему, форматы (JPEG, PNG, WebP, GIF до 8 МБ) — в подсказке; - убраны дубли: часовой пояс только в «Язык и регион», статус — только в мини-профиле панели; - панель пользователя переехала в нижний левый угол под сайдбар, подсказка о выходе из сервера — на её прежнее место; панель ужата под 240 px; - шестерёнка сервера открывает новый пункт настроек «Сервер» (список серверов, общее, участники, роли, опасная зона), а «Профиль → Сервер» оставлен только для ника и «Применить ко всем серверам»; - окно настроек получило статический размер 70% рабочей области со скроллом; - ID пользователей скрыты везде, кроме раздела «Инстанс» у администратора; - список друзей свёрнут под кнопкой «Друзья (N)» и раскрывается снизу вверх; - 249 тестов Vitest (было 239), check/lint/test/build зелёные.
This commit is contained in:
@@ -109,6 +109,18 @@ async function dispatchEvent(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Раскрывает список друзей: по умолчанию он свёрнут под кнопкой
|
||||
* «Друзья (N)» и разворачивается снизу вверх.
|
||||
*/
|
||||
async function expandFriends(): Promise<HTMLElement> {
|
||||
const toggle = await screen.findByTestId('friends-list-toggle');
|
||||
if (toggle.getAttribute('aria-expanded') !== 'true') {
|
||||
await userEvent.click(toggle);
|
||||
}
|
||||
return screen.findByTestId('friends-list');
|
||||
}
|
||||
|
||||
describe('раздел друзей', () => {
|
||||
it('первой иконкой рейки открывает раздел друзей', async () => {
|
||||
const visitor = userEvent.setup();
|
||||
@@ -259,7 +271,8 @@ describe('раздел друзей', () => {
|
||||
expect(
|
||||
findRequest(fetchMock, { url: '/users/@me/relationships/user-3/accept', method: 'POST' }),
|
||||
).toBeDefined();
|
||||
expect(await screen.findByTestId('friends-list')).toHaveTextContent('Кэрол');
|
||||
await expandFriends();
|
||||
expect(screen.getByTestId('friends-list')).toHaveTextContent('Кэрол');
|
||||
|
||||
const outgoing = screen.getByTestId('friends-outgoing');
|
||||
await visitor.click(within(outgoing).getByRole('button', { name: 'Отменить' }));
|
||||
@@ -307,6 +320,84 @@ describe('раздел друзей', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('список друзей свёрнут по умолчанию и раскрывается кнопкой', async () => {
|
||||
const visitor = userEvent.setup();
|
||||
const fetchMock = installFetch(
|
||||
baseRoutes({ ...empty, friends: [bob] }, [
|
||||
{
|
||||
match: '/api/v1/users/@me/channels',
|
||||
method: 'POST',
|
||||
response: () => json({ channel: directChannel('dm-1') }),
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const { router } = renderApp('/app/friends');
|
||||
|
||||
// Свёрнут: списка в DOM нет, кнопка сообщает состояние и число друзей.
|
||||
const toggle = await screen.findByTestId('friends-list-toggle');
|
||||
expect(toggle).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(toggle).toHaveTextContent('Друзья (1)');
|
||||
expect(screen.queryByTestId('friends-list')).toBeNull();
|
||||
expect(screen.getByTestId('friends-list-region')).toHaveAttribute('data-state', 'closed');
|
||||
|
||||
// Развёрнутый список въезжает снизу: класс анимации и состояние open.
|
||||
await visitor.click(toggle);
|
||||
expect(toggle).toHaveAttribute('aria-expanded', 'true');
|
||||
const region = screen.getByTestId('friends-list-region');
|
||||
expect(region).toHaveAttribute('data-state', 'open');
|
||||
expect(region).toHaveClass('gl-reveal-up');
|
||||
|
||||
// Развёрнутый список работает как раньше: переход в беседу по клику.
|
||||
const list = await screen.findByTestId('friends-list');
|
||||
await visitor.click(within(list).getByTestId('friend-name-user-2'));
|
||||
await waitFor(() => {
|
||||
expect(router.state.location.pathname).toBe('/app/friends/dm-1');
|
||||
});
|
||||
expect(findRequest(fetchMock, { url: '/users/@me/channels', method: 'POST' })?.body).toEqual({
|
||||
recipient_id: 'user-2',
|
||||
});
|
||||
|
||||
// На странице беседы списка нет: состояние раскрытия переживает возврат.
|
||||
expect(window.localStorage.getItem('glchat.friends.expanded')).toBe('true');
|
||||
await visitor.click(screen.getByTestId('friends-rail-link'));
|
||||
await waitFor(() => {
|
||||
expect(router.state.location.pathname).toBe('/app/friends');
|
||||
});
|
||||
|
||||
// Свёрнутое состояние тоже запоминается.
|
||||
await visitor.click(await screen.findByTestId('friends-list-toggle'));
|
||||
expect(screen.queryByTestId('friends-list')).toBeNull();
|
||||
expect(window.localStorage.getItem('glchat.friends.expanded')).toBe('false');
|
||||
});
|
||||
|
||||
it('заявки в друзья остаются видимыми при свёрнутом списке', async () => {
|
||||
installFetch(
|
||||
baseRoutes({
|
||||
...empty,
|
||||
friends: [bob],
|
||||
incoming: [
|
||||
relationship({
|
||||
user_id: 'user-3',
|
||||
username: 'carol',
|
||||
display_name: 'Кэрол',
|
||||
relationship: 'incoming',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
renderApp('/app/friends');
|
||||
|
||||
expect(await screen.findByTestId('friends-incoming')).toHaveTextContent('Кэрол');
|
||||
expect(await screen.findByTestId('friends-list-toggle')).toHaveAttribute(
|
||||
'aria-expanded',
|
||||
'false',
|
||||
);
|
||||
// Бейджи непрочитанного в беседах живут в сайдбаре и от списка не зависят.
|
||||
expect(screen.queryByTestId('friends-list')).toBeNull();
|
||||
});
|
||||
|
||||
it('рисует цветные точки статусов в списке друзей', async () => {
|
||||
installFetch(
|
||||
baseRoutes({
|
||||
@@ -348,7 +439,7 @@ describe('раздел друзей', () => {
|
||||
);
|
||||
|
||||
renderApp('/app/friends');
|
||||
const list = await screen.findByTestId('friends-list');
|
||||
const list = await expandFriends();
|
||||
|
||||
const dots: [string, string, string][] = [
|
||||
['user-2', 'online', 'bg-success'],
|
||||
@@ -393,7 +484,7 @@ describe('раздел друзей', () => {
|
||||
);
|
||||
|
||||
renderApp('/app/friends');
|
||||
await screen.findByTestId('friends-list');
|
||||
await expandFriends();
|
||||
|
||||
// Наведение на имя: 10:00 UTC — это 13:00 в Москве.
|
||||
await visitor.hover(screen.getByTestId('friend-name-user-2'));
|
||||
@@ -441,7 +532,7 @@ describe('раздел друзей', () => {
|
||||
);
|
||||
|
||||
renderApp('/app/friends');
|
||||
await screen.findByTestId('friends-list');
|
||||
await expandFriends();
|
||||
|
||||
await visitor.hover(screen.getByTestId('friend-name-user-2'));
|
||||
// 10:00 UTC — 15:00 в Екатеринбурге (UTC+5).
|
||||
@@ -480,7 +571,7 @@ describe('раздел друзей', () => {
|
||||
);
|
||||
|
||||
const { router } = renderApp('/app/friends');
|
||||
const list = await screen.findByTestId('friends-list');
|
||||
const list = await expandFriends();
|
||||
await visitor.click(within(list).getByRole('button', { name: 'Написать' }));
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -664,7 +755,7 @@ describe('события шлюза о друзьях', () => {
|
||||
hasFriend = true;
|
||||
await dispatchEvent(gateway, 'RELATIONSHIP_UPDATE', { user_id: 'user-2' });
|
||||
|
||||
const list = await screen.findByTestId('friends-list');
|
||||
const list = await expandFriends();
|
||||
expect(list).toHaveTextContent('Боб');
|
||||
});
|
||||
|
||||
@@ -685,7 +776,7 @@ describe('события шлюза о друзьях', () => {
|
||||
);
|
||||
|
||||
renderApp('/app/friends');
|
||||
const list = await screen.findByTestId('friends-list');
|
||||
const list = await expandFriends();
|
||||
await gateway.greet();
|
||||
expect(within(list).getByTestId('friend-status-user-2')).toHaveAttribute(
|
||||
'data-status',
|
||||
|
||||
Reference in New Issue
Block a user