feat(web): интерфейс звонков в беседах (Фаза 7)
В шапке беседы — меню звонка: аудио, видео, «Присоединиться» к идущему и история. Когда голос на инстансе выключен, меню нет вовсе: обещать звонок, который не поднимется, нельзя. Входящий звонок показывается карточкой поверх любого экрана с принять и отклонить и рингтоном; идущий звонок — маркером в списке бесед и в шапке, а в области переписки вместо ленты открывается панель с плитками участников и кнопками микрофона, звука, камеры, шаринга и выхода. История звонков — кто звонил, когда, сколько длился разговор и кто не ответил. Тесты (vitest): начало звонка из шапки с подключением к комнате LiveKit, входящий звонок и его принятие, отклонение, маркер «идёт звонок» по событию и его снятие после завершения, флаги микрофона, камеры и экрана, история с пропущенными и длительностью, выключенный голос и маркер после перезагрузки (active_call в READY).
This commit is contained in:
@@ -61,3 +61,67 @@ export function installFakeAudio(): FakeAudio[] {
|
||||
vi.stubGlobal('Audio', StubAudio);
|
||||
return instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подставной `AudioContext` для рингтона: jsdom не умеет Web Audio, поэтому
|
||||
* осцилляторы записываются, а не звучат. По ним видно, что сигнал запустили,
|
||||
* сколько раз и на каких частотах.
|
||||
*/
|
||||
export class FakeOscillator {
|
||||
type = 'sine';
|
||||
frequency = { value: 0 };
|
||||
startCalls = 0;
|
||||
stopCalls = 0;
|
||||
|
||||
connect(): void {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
this.startCalls += 1;
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.stopCalls += 1;
|
||||
}
|
||||
}
|
||||
|
||||
export class FakeAudioContext {
|
||||
currentTime = 0;
|
||||
destination = {};
|
||||
readonly oscillators: FakeOscillator[] = [];
|
||||
|
||||
createOscillator(): FakeOscillator {
|
||||
const oscillator = new FakeOscillator();
|
||||
this.oscillators.push(oscillator);
|
||||
return oscillator;
|
||||
}
|
||||
|
||||
createGain(): { gain: Record<string, () => void>; connect: () => void } {
|
||||
return {
|
||||
gain: { setValueAtTime: () => undefined, linearRampToValueAtTime: () => undefined },
|
||||
connect: () => undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Ставит подставной `AudioContext` и возвращает последний созданный контекст. */
|
||||
export function installFakeAudioContext(): { current: () => FakeAudioContext } {
|
||||
const created: FakeAudioContext[] = [];
|
||||
class StubAudioContext extends FakeAudioContext {
|
||||
constructor() {
|
||||
super();
|
||||
created.push(this);
|
||||
}
|
||||
}
|
||||
vi.stubGlobal('AudioContext', StubAudioContext);
|
||||
return {
|
||||
current: () => {
|
||||
const last = created.at(-1);
|
||||
if (last === undefined) {
|
||||
throw new Error('AudioContext ещё не создавался');
|
||||
}
|
||||
return last;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user