Fix test failures due to scoped class names

This commit is contained in:
Robin
2026-07-08 17:06:25 +02:00
parent 943ce5b853
commit 11fadebb87
6 changed files with 57 additions and 28 deletions

View File

@@ -180,12 +180,12 @@ describe("MediaMuteAndSwitchButton", () => {
);
await user.click(screen.getByRole("button", { name: "Microphone" }));
screen.getByRole("menuitem", { name: "Microphone 1" });
screen.getByRole("menuitem", { name: "Microphone 2" });
screen.getByRole("menuitemradio", { name: "Microphone 1" });
screen.getByRole("menuitemradio", { name: "Microphone 2" });
await user.keyboard("[Escape]");
await user.click(screen.getByRole("button", { name: "Camera" }));
screen.getByRole("menuitem", { name: "Camera 1" });
screen.getByRole("menuitem", { name: "Camera 2" });
screen.getByRole("menuitemradio", { name: "Camera 1" });
screen.getByRole("menuitemradio", { name: "Camera 2" });
});
test("calls select callback on menu click", async () => {
@@ -206,7 +206,9 @@ describe("MediaMuteAndSwitchButton", () => {
);
await user.click(getByRole("button", { name: "Microphone" }));
await user.click(screen.getByRole("menuitem", { name: "Microphone 2" }));
await user.click(
screen.getByRole("menuitemradio", { name: "Microphone 2" }),
);
expect(onSelect).toHaveBeenCalledWith("mic2");
});
@@ -228,7 +230,9 @@ describe("MediaMuteAndSwitchButton", () => {
);
await user.click(getByRole("button", { name: "Microphone" }));
await user.click(screen.getByRole("menuitem", { name: "Microphone 1" }));
await user.click(
screen.getByRole("menuitemradio", { name: "Microphone 1" }),
);
expect(onSelect).not.toHaveBeenCalled();
});
@@ -264,18 +268,24 @@ describe("MediaMuteAndSwitchButton", () => {
const { getByRole } = renderComponent(<Wrapper />);
await user.click(getByRole("button", { name: "Microphone" }));
await user.click(screen.getByRole("menuitem", { name: "Microphone 2" }));
await user.click(
screen.getByRole("menuitemradio", { 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();
// so mic2 should be in an activating state
screen.getByRole("menuitemradio", {
name: "Microphone 2 Activating…",
checked: false,
});
// The currently-selected mic1 item should not have a spinner
const mic1Item = screen.getByRole("menuitem", { name: "Microphone 1" });
expect(mic1Item.querySelector(".rotate")).toBeNull();
// The currently-selected mic1 item should not be activating
screen.getByRole("menuitemradio", {
name: "Microphone 1",
checked: true,
});
await act(async () => {
// resolve the promise that acutally updates the select option.
resolve();
@@ -284,7 +294,7 @@ describe("MediaMuteAndSwitchButton", () => {
expect(onOptionUpdated).toHaveBeenCalled();
// Spinner should now be gone since the selection has caught up
const mic2ItemAfter = screen.getByRole("menuitem", {
const mic2ItemAfter = screen.getByRole("menuitemradio", {
name: "Microphone 2",
});
expect(mic2ItemAfter.querySelector(".rotate")).toBeNull();
@@ -336,11 +346,15 @@ describe("MediaMuteAndSwitchButton", () => {
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" });
const mic1Item = screen.getByRole("menuitemradio", {
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" });
const mic2Item = screen.getByRole("menuitemradio", {
name: "Microphone 1",
});
expect(mic2Item.querySelectorAll("svg").length).toBe(1);
});
});

View File

@@ -191,6 +191,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
width={24}
height={24}
className={styles.itemIcon}
aria-hidden
/>
)
}
@@ -201,10 +202,23 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
onSelect?.(id);
}}
key={id}
role="menuitemradio"
aria-checked={selectedOption === id}
>
{selectedOption === id && <CheckIcon width={24} height={24} />}
{selectedOption === id && (
<CheckIcon
width={24}
height={24}
aria-hidden // A label would be redundant to aria-checked above
/>
)}
{selectedOption !== id && plannedSelection === id && (
<SpinnerIcon width={24} height={24} className={styles.rotate} />
<SpinnerIcon
width={24}
height={24}
className={styles.rotate}
aria-label={t("settings.devices.activating")}
/>
)}
</MenuItem>
);

View File

@@ -145,8 +145,7 @@ describe("LobbyView", () => {
.querySelector("path")!
.getAttribute("d");
const primaryButtonSvgPath = container
.querySelector(".primaryButton")
?.querySelector("path")
.querySelector("path")
?.getAttribute("d");
expect(primaryButtonSvgPath).toBe(expectedSvgPath);
expect(container).toMatchSnapshot();
@@ -169,8 +168,7 @@ describe("LobbyView", () => {
.querySelector("path")!
.getAttribute("d");
const primaryButtonSvgPath = container
.querySelector(".primaryButton")
?.querySelector("path")
.querySelector("path")
?.getAttribute("d");
expect(primaryButtonSvgPath).toBe(expectedSvgPath);
expect(container).toMatchSnapshot();

View File

@@ -6,22 +6,22 @@ Please see LICENSE in the repository root for full details.
*/
import { expect, describe, it } from "vitest";
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { TileAvatar } from "./TileAvatar";
describe("TileAvatar", () => {
it("should show loading spinner when loading", () => {
const { container } = render(
render(
<TileAvatar id="@a:example.org" name="Alice" size={96} loading={true} />,
);
expect(container.querySelector(".loading")).toBeInTheDocument();
screen.getByLabelText("Loading");
});
it("should not show loading spinner when not loading", () => {
const { container } = render(
render(
<TileAvatar id="@a:example.org" name="Alice" size={96} loading={false} />,
);
expect(container.querySelector(".loading")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Loading")).toBe(null);
});
});

View File

@@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details.
import { type FC } from "react";
import { InlineSpinner } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import styles from "./TileAvatar.module.css";
import { Avatar, type Props as AvatarProps } from "../Avatar";
@@ -17,11 +18,12 @@ interface Props extends AvatarProps {
}
export const TileAvatar: FC<Props> = ({ size, loading, ...props }) => {
const { t } = useTranslation();
return (
<div>
{loading && (
<div className={styles.loading}>
<InlineSpinner size={size / 3} />
<InlineSpinner size={size / 3} aria-label={t("common.loading")} />
</div>
)}
<Avatar size={size} {...props} />