mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-18 18:59:23 +00:00
Add tests
This commit is contained in:
@@ -8,14 +8,6 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { type FC, type JSX, type Ref, useMemo } from "react";
|
import { type FC, type JSX, type Ref, useMemo } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
import {
|
|
||||||
MicOffSolidIcon,
|
|
||||||
MicOnSolidIcon,
|
|
||||||
MicOnIcon,
|
|
||||||
VideoCallSolidIcon,
|
|
||||||
VideoCallIcon,
|
|
||||||
VideoCallOffSolidIcon,
|
|
||||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
|
||||||
|
|
||||||
import LogoMark from "../icons/LogoMark.svg?react";
|
import LogoMark from "../icons/LogoMark.svg?react";
|
||||||
import LogoType from "../icons/LogoType.svg?react";
|
import LogoType from "../icons/LogoType.svg?react";
|
||||||
@@ -147,9 +139,7 @@ export const CallFooter: FC<FooterProps> = ({
|
|||||||
<MediaMuteAndSwitchButton
|
<MediaMuteAndSwitchButton
|
||||||
title={"Mic Source"}
|
title={"Mic Source"}
|
||||||
key="audio"
|
key="audio"
|
||||||
IconEnabled={MicOnSolidIcon}
|
iconsAndLabels="video"
|
||||||
IconDisabled={MicOffSolidIcon}
|
|
||||||
IconOptions={MicOnIcon}
|
|
||||||
enabled={audioEnabled ?? false}
|
enabled={audioEnabled ?? false}
|
||||||
onMuteClick={toggleAudio}
|
onMuteClick={toggleAudio}
|
||||||
data-testid="incall_mute"
|
data-testid="incall_mute"
|
||||||
@@ -174,9 +164,7 @@ export const CallFooter: FC<FooterProps> = ({
|
|||||||
<MediaMuteAndSwitchButton
|
<MediaMuteAndSwitchButton
|
||||||
title={"Camera Source"}
|
title={"Camera Source"}
|
||||||
key="audio"
|
key="audio"
|
||||||
IconEnabled={VideoCallSolidIcon}
|
iconsAndLabels="audio"
|
||||||
IconDisabled={VideoCallOffSolidIcon}
|
|
||||||
IconOptions={VideoCallIcon}
|
|
||||||
enabled={videoEnabled ?? false}
|
enabled={videoEnabled ?? false}
|
||||||
onMuteClick={toggleVideo}
|
onMuteClick={toggleVideo}
|
||||||
data-testid="incall_mute"
|
data-testid="incall_mute"
|
||||||
|
|||||||
@@ -5,15 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { AdvancedSettingsIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
MicOnSolidIcon,
|
import { fn, userEvent, within, expect } from "storybook/test";
|
||||||
MicOffSolidIcon,
|
|
||||||
VideoCallSolidIcon,
|
|
||||||
VideoCallOffSolidIcon,
|
|
||||||
AdvancedSettingsIcon,
|
|
||||||
VideoCallIcon,
|
|
||||||
MicOnIcon,
|
|
||||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { MediaMuteAndSwitchButton } from "./MediaMuteAndSwitchButton";
|
import { MediaMuteAndSwitchButton } from "./MediaMuteAndSwitchButton";
|
||||||
@@ -28,23 +21,29 @@ type Story = StoryObj<typeof meta>;
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "SomeMenu",
|
title: "SomeMenu",
|
||||||
IconEnabled: AdvancedSettingsIcon,
|
iconsAndLabels: {
|
||||||
IconDisabled: AdvancedSettingsIcon,
|
IconEnabled: AdvancedSettingsIcon,
|
||||||
|
IconDisabled: AdvancedSettingsIcon,
|
||||||
|
enabledLabel: "Enabled",
|
||||||
|
disabledLabel: "Disabled",
|
||||||
|
optionsButtonLabel: "Options",
|
||||||
|
},
|
||||||
enabled: true,
|
enabled: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: "option 1", id: "1" },
|
{ label: "option 1", id: "1" },
|
||||||
{ label: "option 2", id: "2" },
|
{ label: "option 2", id: "2" },
|
||||||
],
|
],
|
||||||
selectedOption: "1",
|
selectedOption: "1",
|
||||||
|
onMuteClick: fn(),
|
||||||
|
onSelect: fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AudioMute: Story = {
|
export const AudioMute: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
...Default.args,
|
||||||
title: "Microphone",
|
title: "Microphone",
|
||||||
IconEnabled: MicOnSolidIcon,
|
iconsAndLabels: "audio",
|
||||||
IconDisabled: MicOffSolidIcon,
|
|
||||||
IconOptions: MicOnIcon,
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
options: [
|
options: [
|
||||||
{ label: "Microphone 1", id: "1" },
|
{ label: "Microphone 1", id: "1" },
|
||||||
@@ -52,14 +51,20 @@ export const AudioMute: Story = {
|
|||||||
],
|
],
|
||||||
selectedOption: "2",
|
selectedOption: "2",
|
||||||
},
|
},
|
||||||
|
play: async ({ args, canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
// Both the mute button and the chevron trigger currently share the aria-label "Edit"
|
||||||
|
// (both are TODO placeholders in the component). The mute button is first in the DOM.
|
||||||
|
const muteButton = canvas.getByLabelText("Unmute microphone");
|
||||||
|
await userEvent.click(muteButton);
|
||||||
|
await expect(args.onMuteClick).toHaveBeenCalled();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AudioUnmute: Story = {
|
export const AudioUnmute: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "Microphone",
|
title: "Microphone",
|
||||||
IconEnabled: MicOnSolidIcon,
|
iconsAndLabels: "audio",
|
||||||
IconDisabled: MicOffSolidIcon,
|
|
||||||
IconOptions: MicOnIcon,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: "Microphone 1", id: "1" },
|
{ label: "Microphone 1", id: "1" },
|
||||||
@@ -72,9 +77,7 @@ export const AudioUnmute: Story = {
|
|||||||
export const VideoMute: Story = {
|
export const VideoMute: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "Camera",
|
title: "Camera",
|
||||||
IconEnabled: VideoCallSolidIcon,
|
iconsAndLabels: "video",
|
||||||
IconDisabled: VideoCallOffSolidIcon,
|
|
||||||
IconOptions: VideoCallIcon,
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
options: [
|
options: [
|
||||||
{ label: "Camera 1", id: "1" },
|
{ label: "Camera 1", id: "1" },
|
||||||
@@ -87,9 +90,7 @@ export const VideoMute: Story = {
|
|||||||
export const VideoUnmute: Story = {
|
export const VideoUnmute: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "Camera",
|
title: "Camera",
|
||||||
IconEnabled: VideoCallSolidIcon,
|
iconsAndLabels: "video",
|
||||||
IconDisabled: VideoCallOffSolidIcon,
|
|
||||||
IconOptions: VideoCallIcon,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: "Camera 1", id: "1" },
|
{ label: "Camera 1", id: "1" },
|
||||||
|
|||||||
204
src/components/MediaMuteAndSwitchButton.test.tsx
Normal file
204
src/components/MediaMuteAndSwitchButton.test.tsx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, expect, test, vi } from "vitest";
|
||||||
|
import { act, render, screen, type RenderResult } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { type JSX, useState } from "react";
|
||||||
|
|
||||||
|
import { MediaMuteAndSwitchButton } from "./MediaMuteAndSwitchButton";
|
||||||
|
|
||||||
|
describe("MediaMuteAndSwitchButton", () => {
|
||||||
|
test("renders", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MediaMuteAndSwitchButton title={"Switcher"} />,
|
||||||
|
);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders correct audio and video labels", () => {
|
||||||
|
const renderLabels = (
|
||||||
|
type: "video" | "audio",
|
||||||
|
enabled: boolean,
|
||||||
|
): RenderResult => {
|
||||||
|
return render(
|
||||||
|
<MediaMuteAndSwitchButton
|
||||||
|
title={"Switcher"}
|
||||||
|
iconsAndLabels={type}
|
||||||
|
enabled={enabled}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderAudioEndabled = renderLabels("audio", true);
|
||||||
|
const renderAudioDisabled = renderLabels("audio", false);
|
||||||
|
const renderVideoEnabled = renderLabels("video", true);
|
||||||
|
const renderVideoDisabled = renderLabels("video", false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
renderAudioEndabled.getByRole("button", { name: "Mute microphone" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
renderAudioDisabled.getByRole("button", { name: "Unmute microphone" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
renderVideoEnabled.getByRole("button", { name: "Start video" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
renderVideoDisabled.getByRole("button", { name: "Stop video" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls mute on mute press", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onMute = vi.fn();
|
||||||
|
const { getByRole } = render(
|
||||||
|
<MediaMuteAndSwitchButton
|
||||||
|
title={"Switcher"}
|
||||||
|
onMuteClick={onMute}
|
||||||
|
iconsAndLabels="audio"
|
||||||
|
enabled={true}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(getByRole("button", { name: "Mute microphone" }));
|
||||||
|
|
||||||
|
expect(onMute).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls select callback on menu click", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onSelect = vi.fn();
|
||||||
|
const { getByRole } = render(
|
||||||
|
<MediaMuteAndSwitchButton
|
||||||
|
title="Switcher"
|
||||||
|
iconsAndLabels="audio"
|
||||||
|
enabled={true}
|
||||||
|
options={[
|
||||||
|
{ label: "Microphone 1", id: "mic1" },
|
||||||
|
{ label: "Microphone 2", id: "mic2" },
|
||||||
|
]}
|
||||||
|
selectedOption="mic1"
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(getByRole("button", { name: "Microphone" }));
|
||||||
|
await user.click(screen.getByRole("menuitem", { name: "Microphone 2" }));
|
||||||
|
|
||||||
|
expect(onSelect).toHaveBeenCalledWith("mic2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders menu spinner until selection updates for the component", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const { promise, resolve } = Promise.withResolvers<void>();
|
||||||
|
const onSelectPressed = vi.fn();
|
||||||
|
const onOptionUpdated = vi.fn();
|
||||||
|
function Wrapper(): JSX.Element {
|
||||||
|
const [selectedOption, setSelectedOption] = useState("mic1");
|
||||||
|
return (
|
||||||
|
<MediaMuteAndSwitchButton
|
||||||
|
title="Switcher"
|
||||||
|
iconsAndLabels="audio"
|
||||||
|
enabled={true}
|
||||||
|
options={[
|
||||||
|
{ label: "Microphone 1", id: "mic1" },
|
||||||
|
{ label: "Microphone 2", id: "mic2" },
|
||||||
|
]}
|
||||||
|
selectedOption={selectedOption}
|
||||||
|
onSelect={(id) => {
|
||||||
|
onSelectPressed();
|
||||||
|
void promise.then(() => {
|
||||||
|
setSelectedOption(id);
|
||||||
|
onOptionUpdated();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getByRole } = render(<Wrapper />);
|
||||||
|
|
||||||
|
await user.click(getByRole("button", { name: "Microphone" }));
|
||||||
|
await user.click(screen.getByRole("menuitem", { name: "Microphone 2" }));
|
||||||
|
|
||||||
|
expect(onSelectPressed).toHaveBeenCalled();
|
||||||
|
expect(onOptionUpdated).not.toHaveBeenCalled();
|
||||||
|
// After clicking, plannedSelection="mic2" but selectedOption is still "mic1",
|
||||||
|
// so a spinner should appear on the mic2 item
|
||||||
|
const mic2Item = screen.getByRole("menuitem", { name: "Microphone 2" });
|
||||||
|
expect(mic2Item.querySelector(".rotate")).toBeTruthy();
|
||||||
|
|
||||||
|
// The currently-selected mic1 item should not have a spinner
|
||||||
|
const mic1Item = screen.getByRole("menuitem", { name: "Microphone 1" });
|
||||||
|
expect(mic1Item.querySelector(".rotate")).toBeNull();
|
||||||
|
await act(async () => {
|
||||||
|
// resolve the promise that acutally updates the select option.
|
||||||
|
resolve();
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onOptionUpdated).toHaveBeenCalled();
|
||||||
|
// Spinner should now be gone since the selection has caught up
|
||||||
|
const mic2ItemAfter = screen.getByRole("menuitem", {
|
||||||
|
name: "Microphone 2",
|
||||||
|
});
|
||||||
|
expect(mic2ItemAfter.querySelector(".rotate")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders menu with toggle control and calls toggle callback", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onSelect = vi.fn();
|
||||||
|
const { getByRole } = render(
|
||||||
|
<MediaMuteAndSwitchButton
|
||||||
|
title="Switcher"
|
||||||
|
iconsAndLabels="audio"
|
||||||
|
enabled={true}
|
||||||
|
toggles={[{ label: "Background blur", id: "bg_blur", enabled: false }]}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(getByRole("button", { name: "Microphone" }));
|
||||||
|
|
||||||
|
const toggle = screen.getByRole("menuitemcheckbox", {
|
||||||
|
name: "Background blur",
|
||||||
|
});
|
||||||
|
expect(toggle).toBeInTheDocument();
|
||||||
|
expect(toggle).toHaveAttribute("aria-checked", "false");
|
||||||
|
|
||||||
|
await user.click(toggle);
|
||||||
|
|
||||||
|
expect(onSelect).toHaveBeenCalledWith("bg_blur");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders check icon to mark the selected menu item", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const { getByRole } = render(
|
||||||
|
<MediaMuteAndSwitchButton
|
||||||
|
title="Switcher"
|
||||||
|
iconsAndLabels="audio"
|
||||||
|
enabled={true}
|
||||||
|
options={[
|
||||||
|
{ label: "Microphone 1", id: "mic1" },
|
||||||
|
{ label: "Microphone 2", id: "mic2" },
|
||||||
|
]}
|
||||||
|
selectedOption="mic2"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// open menu
|
||||||
|
await user.click(getByRole("button", { name: "Microphone" }));
|
||||||
|
|
||||||
|
// The selected item (mic2) renders both an IconOptions SVG and a CheckIcon SVG
|
||||||
|
const mic1Item = screen.getByRole("menuitem", { name: "Microphone 2" });
|
||||||
|
expect(mic1Item.querySelectorAll("svg").length).toBe(2);
|
||||||
|
|
||||||
|
// The unselected item (mic1) only renders its IconOptions SVG
|
||||||
|
const mic2Item = screen.getByRole("menuitem", { name: "Microphone 1" });
|
||||||
|
expect(mic2Item.querySelectorAll("svg").length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,11 +16,19 @@ import { t } from "i18next";
|
|||||||
import {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
|
MicOffSolidIcon,
|
||||||
|
MicOnIcon,
|
||||||
|
MicOnSolidIcon,
|
||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
|
VideoCallIcon,
|
||||||
|
VideoCallOffSolidIcon,
|
||||||
|
VideoCallSolidIcon,
|
||||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import styles from "./MediaMuteAndSwitchButton.module.css";
|
import styles from "./MediaMuteAndSwitchButton.module.css";
|
||||||
|
|
||||||
export interface MenuOptions {
|
export interface MenuOptions {
|
||||||
label: string;
|
label: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -31,6 +39,18 @@ export interface ToggleOption {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IconsAndLabels {
|
||||||
|
/** The Icon used if the mute button is enabled */
|
||||||
|
IconEnabled: ComponentType<React.SVGAttributes<SVGElement>>;
|
||||||
|
/** The Icon used if the mute button is disabled */
|
||||||
|
IconDisabled: ComponentType<React.SVGAttributes<SVGElement>>;
|
||||||
|
/** The icon used for the different options */
|
||||||
|
IconOptions?: ComponentType<React.SVGAttributes<SVGElement>>;
|
||||||
|
enabledLabel: string;
|
||||||
|
disabledLabel: string;
|
||||||
|
optionsButtonLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MediaMuteAndSwitchButtonProps {
|
export interface MediaMuteAndSwitchButtonProps {
|
||||||
/** The title used in the Switcher modal. */
|
/** The title used in the Switcher modal. */
|
||||||
title: string;
|
title: string;
|
||||||
@@ -38,16 +58,11 @@ export interface MediaMuteAndSwitchButtonProps {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
/** Callback if the mute button is clicked */
|
/** Callback if the mute button is clicked */
|
||||||
onMuteClick?: () => void;
|
onMuteClick?: () => void;
|
||||||
/** The Icon used if the mute button is enabled */
|
iconsAndLabels?: "video" | "audio" | IconsAndLabels;
|
||||||
IconEnabled: ComponentType<React.SVGAttributes<SVGElement>>;
|
|
||||||
/** The Icon used if the mute button is disabled */
|
|
||||||
IconDisabled: ComponentType<React.SVGAttributes<SVGElement>>;
|
|
||||||
/** The options available for the media device selector modal */
|
/** The options available for the media device selector modal */
|
||||||
options?: MenuOptions[];
|
options?: MenuOptions[];
|
||||||
/** The option that will currently be rendered as the selected option */
|
/** The option that will currently be rendered as the selected option */
|
||||||
selectedOption?: string;
|
selectedOption?: string;
|
||||||
/** The icon used for the different options */
|
|
||||||
IconOptions?: ComponentType<React.SVGAttributes<SVGElement>>;
|
|
||||||
/**
|
/**
|
||||||
* The available toggles (including there current state)
|
* The available toggles (including there current state)
|
||||||
* The toggle state is not stored by this component.
|
* The toggle state is not stored by this component.
|
||||||
@@ -65,16 +80,67 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
|
|||||||
title,
|
title,
|
||||||
enabled,
|
enabled,
|
||||||
onMuteClick,
|
onMuteClick,
|
||||||
IconEnabled,
|
iconsAndLabels: iconsAndLabelsWithDefaultCases,
|
||||||
IconDisabled,
|
|
||||||
options,
|
options,
|
||||||
selectedOption,
|
selectedOption,
|
||||||
IconOptions,
|
|
||||||
toggles,
|
toggles,
|
||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const [plannedSelection, setPlannedSelection] = useState<string | null>(null);
|
const [plannedSelection, setPlannedSelection] = useState<string | null>(null);
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
let iconsAndLabels: IconsAndLabels | undefined;
|
||||||
|
switch (iconsAndLabelsWithDefaultCases) {
|
||||||
|
case "video":
|
||||||
|
iconsAndLabels = {
|
||||||
|
IconEnabled: VideoCallSolidIcon,
|
||||||
|
IconDisabled: VideoCallOffSolidIcon,
|
||||||
|
IconOptions: VideoCallIcon,
|
||||||
|
disabledLabel: t("stop_video_button_label"),
|
||||||
|
enabledLabel: t("start_video_button_label"),
|
||||||
|
optionsButtonLabel: t("settings.devices.microphone"),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "audio":
|
||||||
|
iconsAndLabels = {
|
||||||
|
IconEnabled: MicOnSolidIcon,
|
||||||
|
IconDisabled: MicOffSolidIcon,
|
||||||
|
IconOptions: MicOnIcon,
|
||||||
|
disabledLabel: t("mute_microphone_button_label"),
|
||||||
|
enabledLabel: t("unmute_microphone_button_label"),
|
||||||
|
optionsButtonLabel: t("settings.devices.microphone"),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
iconsAndLabels = iconsAndLabelsWithDefaultCases;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
IconEnabled,
|
||||||
|
IconDisabled,
|
||||||
|
IconOptions,
|
||||||
|
disabledLabel,
|
||||||
|
enabledLabel,
|
||||||
|
optionsButtonLabel,
|
||||||
|
} = iconsAndLabels ?? {
|
||||||
|
IconEnabled: undefined,
|
||||||
|
IconDisabled: undefined,
|
||||||
|
IconOptions: undefined,
|
||||||
|
disabledLabel: undefined,
|
||||||
|
enabledLabel: undefined,
|
||||||
|
optionsButtonLabel: undefined,
|
||||||
|
};
|
||||||
|
{
|
||||||
|
logger.info(
|
||||||
|
"RENDER WITH: selectedOption !== option.id && plannedSelection === option.id",
|
||||||
|
selectedOption,
|
||||||
|
" !==",
|
||||||
|
"option.id",
|
||||||
|
" && ",
|
||||||
|
plannedSelection,
|
||||||
|
" === ",
|
||||||
|
"option.id",
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
@@ -94,7 +160,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
|
|||||||
kind={enabled ? "secondary" : "primary"}
|
kind={enabled ? "secondary" : "primary"}
|
||||||
size="lg"
|
size="lg"
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
aria-label={t("action.edit")}
|
aria-label={enabled ? disabledLabel : enabledLabel}
|
||||||
/>
|
/>
|
||||||
<Menu
|
<Menu
|
||||||
title={title}
|
title={title}
|
||||||
@@ -109,7 +175,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
|
|||||||
Icon={ChevronUpIcon}
|
Icon={ChevronUpIcon}
|
||||||
kind={"tertiary"}
|
kind={"tertiary"}
|
||||||
size="lg"
|
size="lg"
|
||||||
aria-label={/*TODO*/ t("action.edit")}
|
aria-label={optionsButtonLabel}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -127,8 +193,8 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onSelect={(e) => {
|
onSelect={(e) => {
|
||||||
onSelect?.(option.id);
|
|
||||||
setPlannedSelection(option.id);
|
setPlannedSelection(option.id);
|
||||||
|
onSelect?.(option.id);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
key={option.id}
|
key={option.id}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`MediaMuteAndSwitchButton > renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_1nw83_8 button _icon-only_1nw83_53"
|
||||||
|
data-kind="primary"
|
||||||
|
data-size="lg"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="menu"
|
||||||
|
class="_button_1nw83_8 menuButton _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||||
|
data-kind="tertiary"
|
||||||
|
data-size="lg"
|
||||||
|
data-state="closed"
|
||||||
|
id="radix-_r_0_"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m12 10.775-3.9 3.9a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275.95.95 0 0 1-.275-.7q0-.425.275-.7l4.6-4.6q.15-.15.325-.212Q11.8 8.4 12 8.4t.375.063a.9.9 0 0 1 .325.212l4.6 4.6a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user