bcfc275bb3
Раздел «Друзья» по запросу пользователя: - первая иконка рейки — «Друзья», серверы ниже за разделителем; личные беседы не засоряют список серверов; - сайдбар друзей: беседы с точками статуса и бейджами непрочитанного, список друзей с кликом на беседу; - контент: пустое состояние «Добавь друзей для общения» с поиском, поиск по логину (debounce), добавление в друзья, входящие и исходящие заявки, меню «…» (удалить/заблокировать), поповер последнего входа в часовом поясе пользователя (по умолчанию МСК), блок «Присоединиться по приглашению» с предпросмотром сервера; - личный чат на существующих компонентах: права по can_send канала, заголовок с именем и статусом собеседника, поиск, пины, ack и typing работают; - realtime: READY dm_channels, события DM_CHANNEL_CREATE, RELATIONSHIP_UPDATE, PRESENCE_UPDATE и MESSAGE_CREATE в беседах; - новый пользователь: «У вас пока нет серверов», создание сервера и вход по приглашению (автовступления нет); - 239 тестов Vitest (было 219), тест паритета ключей i18n (527 ключей).
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import en from '@/i18n/locales/en.json';
|
|
import ru from '@/i18n/locales/ru.json';
|
|
|
|
/** Плоский список пар «ключ → значение». */
|
|
function flatEntries(value: unknown, prefix = ''): [string, unknown][] {
|
|
if (typeof value !== 'object' || value === null) {
|
|
return [];
|
|
}
|
|
const entries: [string, unknown][] = [];
|
|
for (const [key, nested] of Object.entries(value)) {
|
|
const path = prefix === '' ? key : `${prefix}.${key}`;
|
|
if (typeof nested === 'object' && nested !== null) {
|
|
entries.push(...flatEntries(nested, path));
|
|
} else {
|
|
entries.push([path, nested]);
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
/** Плоский список ключей вида `friends.empty.title`. */
|
|
function flatKeys(value: unknown): string[] {
|
|
return flatEntries(value)
|
|
.map(([key]) => key)
|
|
.sort();
|
|
}
|
|
|
|
/**
|
|
* Паритет переводов: русский — основной, английский должен повторять его
|
|
* ключ в ключ, иначе часть интерфейса останется без строки.
|
|
*/
|
|
describe('переводы', () => {
|
|
it('ключи ru и en совпадают', () => {
|
|
expect(flatKeys(en)).toEqual(flatKeys(ru));
|
|
});
|
|
|
|
it('нет пустых строк', () => {
|
|
const empty = flatEntries(ru)
|
|
.filter(([, value]) => typeof value === 'string' && value.trim() === '')
|
|
.map(([key]) => key);
|
|
expect(empty).toEqual([]);
|
|
});
|
|
});
|