feat(profile): системные бейджи рядом с ником

- участники сервера отдают список бейджей из badges_json (AGENT.md 7.2):
  список расширяемый, назначается только системой
- клиент: каталог бейджей (щит администратора инстанса, корона владельца,
  ранний сторонник, проверенный), неизвестные показываются нейтральной меткой
- бейджи видны в ленте, списке участников и друзьях вместо текстовой плашки
- тесты: Go (бейджи в списке участников) и Vitest (каталог и отрисовка)
This commit is contained in:
2026-09-21 18:10:39 +03:00
parent 0f0060e8a4
commit b43452aa04
19 changed files with 266 additions and 19 deletions
+2
View File
@@ -101,6 +101,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: true,
}),
resolveWebhook: () => ({
@@ -109,6 +110,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: false,
}),
resolveMention: (userId) => ({
+2
View File
@@ -31,6 +31,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: true,
}),
resolveWebhook: () => ({
@@ -39,6 +40,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: false,
}),
resolveMention: (userId) => ({ name: userId === 'user-2' ? 'Bob' : userId, isMe: false }),
+2
View File
@@ -72,6 +72,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: true,
}),
resolveWebhook: () => ({
@@ -80,6 +81,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: false,
}),
resolveMention: (userId) => ({ name: userId, isMe: false }),
+3
View File
@@ -19,6 +19,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: true,
}),
resolveWebhook: () => ({
@@ -27,6 +28,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: false,
}),
resolveMention: (userId) => ({ name: userId === 'user-1' ? 'Аля' : 'Bob', isMe: false }),
@@ -223,6 +225,7 @@ describe('сообщения вебхуков', () => {
avatarUrl: message.webhook_avatar,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: false,
}),
};
+29
View File
@@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
import { AnimatedImage } from '@/components/ui/AnimatedImage';
import { FramedAvatar, Nickname } from '@/components/profile/Cosmetics';
import { SystemBadges } from '@/components/profile/SystemBadges';
import { nickColorStyle, nickEffectClass } from '@/components/profile/cosmeticsStyle';
import {
bodyOf,
@@ -498,3 +499,31 @@ describe('фон комнаты', () => {
expect(image?.getAttribute('data-animated')).toBe('animated');
});
});
describe('системные бейджи', () => {
it('показывает щит администратора инстанса и корону владельца', () => {
render(<SystemBadges badges={['instance_admin', 'early_supporter']} isOwner isInstanceAdmin />);
expect(screen.getByTestId('system-badge-instance_admin')).toHaveTextContent('🛡');
expect(screen.getByTestId('system-badge-guild_owner')).toHaveTextContent('👑');
expect(screen.getByTestId('system-badge-early_supporter')).toHaveAttribute(
'title',
'Ранний сторонник',
);
// Администратор инстанса помечен даже без записи в badges_json.
expect(screen.getByLabelText('Администратор инстанса')).toBeInTheDocument();
});
it('неизвестный бейдж показывается нейтральной меткой, а не пропадает', () => {
render(<SystemBadges badges={['future_badge']} />);
const badge = screen.getByTestId('system-badge-future_badge');
expect(badge).toHaveTextContent('•');
expect(badge).toHaveAttribute('title', 'future_badge');
});
it('без бейджей ничего не рисует', () => {
const { container } = render(<SystemBadges />);
expect(container.querySelector('[data-testid="system-badges"]')).toBeNull();
});
});
+2
View File
@@ -29,6 +29,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: userId === 'user-2',
}),
resolveWebhook: () => ({
@@ -37,6 +38,7 @@ const authors: AuthorDirectory = {
avatarFileId: undefined,
colorHex: null,
isInstanceAdmin: false,
isOwner: false,
isMember: false,
}),
resolveMention: (userId) => ({ name: userId === 'user-2' ? 'Боб' : userId, isMe: false }),