mirror of
https://github.com/vector-im/element-call.git
synced 2026-05-01 09:54:37 +00:00
Add tests to make sure we always have one settings button.
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import styles from "./Button.module.css";
|
||||
import inCallViewStyles from "../components/InCallFooter.module.css";
|
||||
import inCallFooterStyles from "../components/InCallFooter.module.css";
|
||||
import { platform } from "../Platform";
|
||||
|
||||
interface MicButtonProps extends ComponentPropsWithoutRef<"button"> {
|
||||
@@ -177,9 +177,9 @@ export const SettingsButton: FC<SettingsButtonProps> = ({
|
||||
<Tooltip label={t("common.settings")}>
|
||||
<CpdButton
|
||||
className={classNames(className, {
|
||||
[inCallViewStyles.settingsOnlyShowWide]:
|
||||
[inCallFooterStyles.settingsOnlyShowWide]:
|
||||
showForScreenWidth === "wide",
|
||||
[inCallViewStyles.settingsOnlyShowNarrow]:
|
||||
[inCallFooterStyles.settingsOnlyShowNarrow]:
|
||||
showForScreenWidth === "narrow",
|
||||
})}
|
||||
iconOnly
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface InCallFooterProps {
|
||||
showControls: boolean;
|
||||
hideSettingsButton: boolean;
|
||||
hideLogo: boolean;
|
||||
/** Pip controls buttonSize and hides: settings button, layout switcher and logo */
|
||||
asPip: boolean;
|
||||
gridMode: GridMode;
|
||||
setGridMode: (mode: GridMode) => void;
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { render, type RenderResult } from "@testing-library/react";
|
||||
import { type LocalParticipant } from "livekit-client";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { BrowserRouter, MemoryRouter } from "react-router-dom";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { RoomContext, useLocalParticipant } from "@livekit/components-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
@@ -34,13 +34,17 @@ import {
|
||||
} from "../utils/test";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||
import { type CallViewModelOptions } from "../state/CallViewModel/CallViewModel";
|
||||
import { alice, local } from "../utils/test-fixtures";
|
||||
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
||||
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||
import { LivekitRoomAudioRenderer } from "../livekit/MatrixAudioRenderer";
|
||||
import { MediaDevicesContext } from "../MediaDevicesContext";
|
||||
import { type MediaDevices as ECMediaDevices } from "../state/MediaDevices";
|
||||
import { constant } from "../state/Behavior";
|
||||
import { AppBar } from "../AppBar";
|
||||
import { initializeWidget } from "../widget";
|
||||
import inCallFooterStyles from "../components/InCallFooter.module.css";
|
||||
|
||||
initializeWidget();
|
||||
vi.hoisted(
|
||||
@@ -96,6 +100,11 @@ beforeEach(() => {
|
||||
});
|
||||
interface CreateInCallViewArgs {
|
||||
mediaDevices?: ECMediaDevices;
|
||||
callViewModelOptions?: Partial<CallViewModelOptions>;
|
||||
/** If set, uses a MemoryRouter with this as the initial entry instead of BrowserRouter */
|
||||
initialRoute?: string;
|
||||
/** If true, wraps the rendered tree in an AppBar provider */
|
||||
withAppBar?: boolean;
|
||||
}
|
||||
function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
||||
rtcSession: MockRTCSession;
|
||||
@@ -112,47 +121,60 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
||||
const { vm, rtcSession } = getBasicCallViewModelEnvironment(
|
||||
[local, alice],
|
||||
undefined,
|
||||
{},
|
||||
args.callViewModelOptions ?? {},
|
||||
args.mediaDevices,
|
||||
);
|
||||
|
||||
rtcSession.joined = true;
|
||||
const room = rtcSession.room;
|
||||
const client = room.client;
|
||||
|
||||
const Router = args.initialRoute
|
||||
? ({ children }: { children: React.ReactNode }) => (
|
||||
<MemoryRouter initialEntries={[args.initialRoute!]}>
|
||||
{children}
|
||||
</MemoryRouter>
|
||||
)
|
||||
: BrowserRouter;
|
||||
|
||||
const inCallView = (
|
||||
<InCallView
|
||||
client={client}
|
||||
rtcSession={rtcSession.asMockedSession()}
|
||||
muteStates={muteState}
|
||||
vm={vm}
|
||||
matrixInfo={{
|
||||
userId: "",
|
||||
displayName: "",
|
||||
avatarUrl: "",
|
||||
roomId: "",
|
||||
roomName: "",
|
||||
roomAlias: null,
|
||||
roomAvatar: null,
|
||||
e2eeSystem: {
|
||||
kind: E2eeType.NONE,
|
||||
},
|
||||
}}
|
||||
matrixRoom={room}
|
||||
onShareClick={null}
|
||||
/>
|
||||
);
|
||||
|
||||
const content = args.withAppBar ? <AppBar>{inCallView}</AppBar> : inCallView;
|
||||
|
||||
const renderResult = render(
|
||||
<BrowserRouter>
|
||||
<Router>
|
||||
<MediaDevicesContext value={args.mediaDevices ?? mockMediaDevices({})}>
|
||||
<ReactionsSenderProvider
|
||||
vm={vm}
|
||||
rtcSession={rtcSession.asMockedSession()}
|
||||
>
|
||||
<TooltipProvider>
|
||||
<RoomContext value={livekitRoom}>
|
||||
<InCallView
|
||||
client={client}
|
||||
rtcSession={rtcSession.asMockedSession()}
|
||||
muteStates={muteState}
|
||||
vm={vm}
|
||||
matrixInfo={{
|
||||
userId: "",
|
||||
displayName: "",
|
||||
avatarUrl: "",
|
||||
roomId: "",
|
||||
roomName: "",
|
||||
roomAlias: null,
|
||||
roomAvatar: null,
|
||||
e2eeSystem: {
|
||||
kind: E2eeType.NONE,
|
||||
},
|
||||
}}
|
||||
matrixRoom={room}
|
||||
onShareClick={null}
|
||||
/>
|
||||
</RoomContext>
|
||||
<RoomContext value={livekitRoom}>{content}</RoomContext>
|
||||
</TooltipProvider>
|
||||
</ReactionsSenderProvider>
|
||||
</MediaDevicesContext>
|
||||
</BrowserRouter>,
|
||||
</Router>,
|
||||
);
|
||||
return {
|
||||
...renderResult,
|
||||
@@ -167,6 +189,53 @@ describe("InCallView", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe("settings button with AppBar header", () => {
|
||||
it("is accessible when showHeader is false", () => {
|
||||
// windowSize with height <= 600 results in "flat" windowMode,
|
||||
// which means showHeader$ emits false.
|
||||
const { getAllByRole } = createInCallView({
|
||||
initialRoute: "/?header=app_bar",
|
||||
withAppBar: true,
|
||||
callViewModelOptions: {
|
||||
// Set windowMode$ to "flat" (height <= 600)
|
||||
windowSize$: constant({ width: 1000, height: 500 }),
|
||||
},
|
||||
});
|
||||
// When showHeader is false, hideSettingsButton is false,
|
||||
// so the settings button is visible in the footer.
|
||||
const settingsBtns = getAllByRole("button", { name: "Settings" });
|
||||
expect(settingsBtns.length).toBe(2);
|
||||
const [btnA, btnB] = settingsBtns;
|
||||
// here we check for two settings buttons because there are two buttons in the bottom bar. One for the
|
||||
// the narrow layout and another one for the wide layout.
|
||||
// Their visibility uses @media css queries, which cannot be tested in JSDOM,
|
||||
// but we can at least check that both buttons are rendered and have the correct classes.
|
||||
expect(btnA).toBeInTheDocument();
|
||||
expect(btnA).toHaveClass(inCallFooterStyles.settingsOnlyShowWide);
|
||||
expect(btnB).toBeInTheDocument();
|
||||
expect(btnB).toHaveClass(inCallFooterStyles.settingsOnlyShowNarrow);
|
||||
});
|
||||
|
||||
it("is accessible when showHeader is true", () => {
|
||||
// windowSize with height > 600 and width > 600 results in "normal" windowMode,
|
||||
// which means showHeader$ emits true.
|
||||
const { getAllByRole } = createInCallView({
|
||||
initialRoute: "/?header=app_bar",
|
||||
withAppBar: true,
|
||||
callViewModelOptions: {
|
||||
// Set windowMode$ to "normal" (height >= 600)
|
||||
windowSize$: constant({ width: 1000, height: 800 }),
|
||||
},
|
||||
});
|
||||
// When showHeader is true and headerStyle is AppBar,
|
||||
// hideSettingsButton is true in the footer, but the settings
|
||||
// button is rendered in the AppBar via useAppBarSecondaryButton.
|
||||
const settingsBtns = getAllByRole("button", { name: "Settings" });
|
||||
expect(settingsBtns.length).toBe(1);
|
||||
|
||||
expect(settingsBtns[0]).toBeVisible();
|
||||
});
|
||||
});
|
||||
describe("audioOutputSwitcher", () => {
|
||||
it("is visible and can be clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -360,7 +360,12 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
let header: ReactNode = null;
|
||||
if (showHeader) {
|
||||
switch (headerStyle) {
|
||||
case "none":
|
||||
case HeaderStyle.AppBar: {
|
||||
// dont build a header here. The AppBar will take care of it.
|
||||
header = null;
|
||||
break;
|
||||
}
|
||||
case HeaderStyle.None:
|
||||
// Cosmetic header to fill out space while still affecting the bounds of
|
||||
// the grid
|
||||
header = (
|
||||
@@ -370,7 +375,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "standard":
|
||||
case HeaderStyle.Standard:
|
||||
header = (
|
||||
<Header
|
||||
className={styles.header}
|
||||
@@ -565,7 +570,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
showControls={showControls}
|
||||
// Hide the logo for both embedded solutions. mobile: HeaderStyle.AppBar and desktop: HeaderStyle.None.
|
||||
hideLogo={headerStyle !== HeaderStyle.Standard}
|
||||
hideSettingsButton={headerStyle === HeaderStyle.AppBar}
|
||||
// Only hide the settings button if we have an AppBar header and we are showing the header
|
||||
hideSettingsButton={headerStyle === HeaderStyle.AppBar && showHeader}
|
||||
asPip={layout.type === "pip"}
|
||||
gridMode={gridMode}
|
||||
setGridMode={setGridMode}
|
||||
|
||||
@@ -169,7 +169,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_8_"
|
||||
class="_button_13vu4_8 settingForBottomLeftCorner _has-icon_13vu4_60 _icon-only_13vu4_53"
|
||||
class="_button_13vu4_8 settingsOnlyShowWide _has-icon_13vu4_60 _icon-only_13vu4_53"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
@@ -298,7 +298,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
||||
>
|
||||
<button
|
||||
aria-labelledby="_r_d_"
|
||||
class="_button_13vu4_8 settingForButtonsBar _has-icon_13vu4_60 _icon-only_13vu4_53"
|
||||
class="_button_13vu4_8 settingsOnlyShowNarrow _has-icon_13vu4_60 _icon-only_13vu4_53"
|
||||
data-kind="secondary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
|
||||
Reference in New Issue
Block a user