diff --git a/web/e2e/voice.spec.ts b/web/e2e/voice.spec.ts
index d795ffc..31197cd 100644
--- a/web/e2e/voice.spec.ts
+++ b/web/e2e/voice.spec.ts
@@ -340,14 +340,14 @@ test.describe('голос: два клиента в одной комнате',
await expect(session.page.getByTestId(`voice-tile-${fixture.guestId}`)).toBeVisible();
}
- // Пинг участника виден в плитке и обновляется: своё значение измеряется
- // по статистике WebRTC, чужое приходит по data-каналу LiveKit.
+ // Пинг участника показывается при наведении на индикатор связи: своё
+ // значение измеряется по статистике WebRTC, чужое приходит по data-каналу.
for (const session of [adminSession, guestSession]) {
- const ownLabel = session.page
+ const ownQuality = session.page
.getByTestId(`voice-tile-${session === adminSession ? fixture.adminId : fixture.guestId}`)
- .getByTestId('voice-quality-label');
+ .getByTestId('voice-quality');
await expect
- .poll(async () => (await ownLabel.textContent()) ?? '', { timeout: 30_000 })
+ .poll(async () => (await ownQuality.getAttribute('title')) ?? '', { timeout: 30_000 })
.toMatch(/\d+ мс/u);
}
// Каждый видит пинг другого участника.
@@ -356,8 +356,8 @@ test.describe('голос: два клиента в одной комнате',
async () =>
(await adminSession.page
.getByTestId(`voice-tile-${fixture.guestId}`)
- .getByTestId('voice-quality-label')
- .textContent()) ?? '',
+ .getByTestId('voice-quality')
+ .getAttribute('title')) ?? '',
{ timeout: 30_000 },
)
.toMatch(/\d+ мс/u);
diff --git a/web/src/components/voice/VoiceIndicators.tsx b/web/src/components/voice/VoiceIndicators.tsx
index 4e75716..b1ffb6e 100644
--- a/web/src/components/voice/VoiceIndicators.tsx
+++ b/web/src/components/voice/VoiceIndicators.tsx
@@ -28,7 +28,8 @@ export function VoiceQualityDot({
if (quality === null) {
return null;
}
- const label = qualityLabel(t, quality, latencyMs);
+ // Пинг показываем только в подсказке: в плитке он не занимает место.
+ const label = `${qualityLabel(t, quality, latencyMs)} — ${t('voice.latencyHint')}`;
return (
-
- {qualityLabel(t, quality, latencyMs)}
-
- );
-}
-
/**
* Иконка состояния участника: перечёркнутая, когда флаг выключен.
* Подпись обязательна — по ней состояние читают и тесты, и скринридер.
diff --git a/web/src/components/voice/VoiceRoomPanel.tsx b/web/src/components/voice/VoiceRoomPanel.tsx
index b31a028..1e0121b 100644
--- a/web/src/components/voice/VoiceRoomPanel.tsx
+++ b/web/src/components/voice/VoiceRoomPanel.tsx
@@ -6,7 +6,7 @@ import { Avatar } from '@/components/ui/Avatar';
import { Menu, MenuGroupLabel, MenuItem } from '@/components/ui/Menu';
import { Card, Button } from '@/components/ui/primitives';
import { RemoteAudio, RemoteVideo } from '@/components/voice/RemoteAudio';
-import { VoiceFlags, VoiceQualityBadge } from '@/components/voice/VoiceIndicators';
+import { VoiceFlags, VoiceQualityDot } from '@/components/voice/VoiceIndicators';
import { VoiceOccupantMenu } from '@/components/voice/VoiceOccupantMenu';
import { VoiceSoundboard } from '@/components/voice/VoiceSoundboard';
import { VoiceStatsButton } from '@/components/voice/VoiceStatsButton';
@@ -110,7 +110,7 @@ function VoiceTile({
{occupant.name}
-
+
{occupant.isLocal ? (
<>
{/* Статистика своей публикации: разрешение, fps, кодек, канал (7.14). */}
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index 5492ed8..b643bd2 100644
--- a/web/src/i18n/locales/en.json
+++ b/web/src/i18n/locales/en.json
@@ -346,7 +346,7 @@
}
},
"latency": "{{ms}} ms",
- "latencyHint": "Participant ping to the instance",
+ "latencyHint": "latency to the voice server",
"latencyUnknown": "ping unknown"
},
"friends": {
diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json
index 01e6e52..204c896 100644
--- a/web/src/i18n/locales/ru.json
+++ b/web/src/i18n/locales/ru.json
@@ -346,7 +346,7 @@
}
},
"latency": "{{ms}} мс",
- "latencyHint": "Пинг участника до инстанса",
+ "latencyHint": "задержка до голосового сервера",
"latencyUnknown": "пинг неизвестен"
},
"friends": {
diff --git a/web/tests/voice.test.tsx b/web/tests/voice.test.tsx
index 4e3b0b1..dbb44a8 100644
--- a/web/tests/voice.test.tsx
+++ b/web/tests/voice.test.tsx
@@ -198,7 +198,7 @@ describe('голосовая комната: вход', () => {
expect(voice.joinCount()).toBe(0);
});
- it('показывает пинг участника рядом с качеством связи', { timeout: 20_000 }, async () => {
+ it('показывает пинг участника в подсказке индикатора связи', { timeout: 20_000 }, async () => {
await bootVoice({ seated: [{ user_id: 'user-2', channel_id: 'c-voice' }] });
await clickChannel('Голосовая');
@@ -211,19 +211,24 @@ describe('голосовая комната: вход', () => {
['pair', { type: 'candidate-pair', state: 'succeeded', currentRoundTripTime: 0.042 }],
]);
// Первый такт может прийтись на момент, когда микрофон ещё не опубликован,
- // поэтому ждём следующего (интервал публикации пинга — секунда).
+ // поэтому ждём следующего (интервал публикации пинга — секунда). Пинг виден
+ // только при наведении: он попадает в подсказку индикатора связи.
+ const ownQuality = () =>
+ within(screen.getByTestId('voice-tile-user-1')).getByTestId('voice-quality');
await waitFor(
() => {
- expect(
- within(screen.getByTestId('voice-tile-user-1')).getByTestId('voice-quality-label'),
- ).toHaveTextContent('42 мс');
+ expect(ownQuality()).toHaveAttribute(
+ 'title',
+ expect.stringContaining('Отличное соединение, 42 мс'),
+ );
},
{ timeout: 5_000 },
);
- const ownLabel = within(screen.getByTestId('voice-tile-user-1')).getByTestId(
- 'voice-quality-label',
+ expect(ownQuality()).toHaveAttribute('data-latency', '42');
+ expect(ownQuality()).toHaveAttribute(
+ 'aria-label',
+ expect.stringContaining('задержка до голосового сервера'),
);
- expect(ownLabel).toHaveTextContent('Отличное соединение, 42 мс');
// Второй участник подключается к комнате и присылает свой пинг.
const remote = room.addRemote('user-2', 'Bob');
@@ -237,8 +242,9 @@ describe('голосовая комната: вход', () => {
const remoteTile = await screen.findByTestId('voice-tile-user-2');
await waitFor(
() => {
- expect(within(remoteTile).getByTestId('voice-quality-label')).toHaveTextContent(
- 'Отличное соединение, 128 мс',
+ expect(within(remoteTile).getByTestId('voice-quality')).toHaveAttribute(
+ 'title',
+ expect.stringContaining('Отличное соединение, 128 мс'),
);
},
{ timeout: 5_000 },