mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Cover the audio menu's surfaces before joining and during a call
The lobby footer reads the same device behaviours as the in-call one, so the menu already reaches every platform before joining, and stays gated to desktop during a call. These are the checks that hold it there: the view model offers the menu pre-join on desktop, Android and iOS, and the microphone button stands alone with no chevron once a call starts on mobile. Spec: FEATURES_SPEC/2026-09_Audio_Quick_Menu.md, slice 5 — AC18, AC19. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("no audio menu during a call on mobile", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.getByTestId("home_callName").fill("Audio menu");
|
||||||
|
await page.getByTestId("home_displayName").fill("Mobile");
|
||||||
|
await page.getByTestId("home_go").click();
|
||||||
|
await expect(page.getByTestId("lobby_joinCall")).toBeVisible();
|
||||||
|
|
||||||
|
// Before joining, the microphone chevron exists and opens the full menu.
|
||||||
|
const chevron = page.getByRole("button", { name: "Microphone" });
|
||||||
|
await expect(chevron).toBeVisible();
|
||||||
|
await chevron.click();
|
||||||
|
// On mobile the menu is a drawer named after its title rather than headed
|
||||||
|
// by it.
|
||||||
|
const menu = page.getByRole("menu", { name: "Audio controls" });
|
||||||
|
await expect(menu).toBeVisible();
|
||||||
|
await expect(menu.getByRole("meter")).toBeVisible();
|
||||||
|
await expect(menu.getByRole("slider")).toBeVisible();
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
await expect(menu).not.toBeVisible();
|
||||||
|
|
||||||
|
// During the call, the microphone button stands alone: no chevron, no menu.
|
||||||
|
await page.getByTestId("lobby_joinCall").click();
|
||||||
|
await expect(page.getByTestId("incall_mute")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("incall_leave")).toBeVisible();
|
||||||
|
await expect(chevron).toHaveCount(0);
|
||||||
|
});
|
||||||
@@ -361,10 +361,29 @@ export const MobileLayout: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Devices and controls of the audio menu, as the lobby offers them. */
|
||||||
|
const audioMenuArgs = {
|
||||||
|
audioOptions: [
|
||||||
|
{ label: { type: "name", name: "MacBook Pro Microphone" }, id: "1" },
|
||||||
|
{ label: { type: "name", name: "Jabra Evolve 65" }, id: "2" },
|
||||||
|
],
|
||||||
|
selectedAudio: "1",
|
||||||
|
selectAudioButtonOption: fn(),
|
||||||
|
audioOutputOptions: [
|
||||||
|
{ label: { type: "default", name: "MacBook Pro Speakers" }, id: "" },
|
||||||
|
{ label: { type: "name", name: "Jabra Evolve 65" }, id: "2" },
|
||||||
|
],
|
||||||
|
selectedAudioOutput: "",
|
||||||
|
selectAudioOutputOption: fn(),
|
||||||
|
soundEffectVolume: 0.5,
|
||||||
|
setSoundEffectVolume: fn(),
|
||||||
|
} satisfies Partial<FooterSnapshot>;
|
||||||
|
|
||||||
export const Lobby: Story = {
|
export const Lobby: Story = {
|
||||||
...Default,
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
|
...audioMenuArgs,
|
||||||
showLogo: false,
|
showLogo: false,
|
||||||
openSettings: undefined,
|
openSettings: undefined,
|
||||||
layout: null,
|
layout: null,
|
||||||
@@ -379,6 +398,7 @@ export const LobbyMobile: Story = {
|
|||||||
...Default,
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
|
...audioMenuArgs,
|
||||||
showLogo: false,
|
showLogo: false,
|
||||||
|
|
||||||
layout: null,
|
layout: null,
|
||||||
@@ -429,19 +449,7 @@ export const WithAudioMenu: Story = {
|
|||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
audioEnabled: true,
|
audioEnabled: true,
|
||||||
audioOptions: [
|
...audioMenuArgs,
|
||||||
{ label: { type: "name", name: "MacBook Pro Microphone" }, id: "1" },
|
|
||||||
{ label: { type: "name", name: "Jabra Evolve 65" }, id: "2" },
|
|
||||||
],
|
|
||||||
selectedAudio: "1",
|
|
||||||
selectAudioButtonOption: fn(),
|
|
||||||
audioOutputOptions: [
|
|
||||||
{ label: { type: "default", name: "MacBook Pro Speakers" }, id: "" },
|
|
||||||
{ label: { type: "name", name: "Jabra Evolve 65" }, id: "2" },
|
|
||||||
],
|
|
||||||
selectedAudioOutput: "",
|
|
||||||
selectAudioOutputOption: fn(),
|
|
||||||
setSoundEffectVolume: fn(),
|
|
||||||
},
|
},
|
||||||
play: async ({ args, canvasElement }) => {
|
play: async ({ args, canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import type {
|
|||||||
AudioOutputDeviceLabel,
|
AudioOutputDeviceLabel,
|
||||||
DeviceLabel,
|
DeviceLabel,
|
||||||
} from "../state/MediaDevices";
|
} from "../state/MediaDevices";
|
||||||
import { createCallFooterViewModel } from "./CallFooterViewModel";
|
import {
|
||||||
|
createCallFooterViewModel,
|
||||||
|
createLobbyFooterViewModel,
|
||||||
|
} from "./CallFooterViewModel";
|
||||||
import { HeaderStyle } from "../UrlParams";
|
import { HeaderStyle } from "../UrlParams";
|
||||||
import { type FooterSnapshot } from "./CallFooter";
|
import { type FooterSnapshot } from "./CallFooter";
|
||||||
import { type ViewModel } from "../state/ViewModel";
|
import { type ViewModel } from "../state/ViewModel";
|
||||||
@@ -148,6 +151,23 @@ describe("createCallFooterViewModel", () => {
|
|||||||
expect(vm.setSoundEffectVolume$.value).toBeUndefined();
|
expect(vm.setSoundEffectVolume$.value).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("audio menu is present pre-join on every platform", () => {
|
||||||
|
for (const platform of ["desktop", "android", "ios"]) {
|
||||||
|
platformMock.mockReturnValue(platform);
|
||||||
|
const vm = createLobbyFooterViewModel(
|
||||||
|
testScope(),
|
||||||
|
mockMuteStates(),
|
||||||
|
twoOutputsMediaDevices,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(vm.audioOutputOptions$.value).toHaveLength(2);
|
||||||
|
expect(vm.selectAudioOutputOption$.value).toBeDefined();
|
||||||
|
expect(vm.setSoundEffectVolume$.value).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("reads and writes the sound-effect volume setting on desktop", () => {
|
it("reads and writes the sound-effect volume setting on desktop", () => {
|
||||||
const vm = createVm("desktop", gridLayout);
|
const vm = createVm("desktop", gridLayout);
|
||||||
expect(vm.soundEffectVolume$.value).toBe(0.5);
|
expect(vm.soundEffectVolume$.value).toBe(0.5);
|
||||||
|
|||||||
Reference in New Issue
Block a user