The view models reached for getUrlParams() — and so window.location — from deep inside the call path: CallViewModel, MediaDevices, Publisher, LocalMember and the footer view model. An embedded Element Call has no URL of its own, so these values have to arrive as arguments instead. Add the relevant options to CallViewModelOptions, to the MediaDevices and Publisher constructors, to createLocalMembership$ and enterRTCSession, and to createCallFooterViewModel. The remaining React consumers read the context added in the previous commit. AppViewModel now takes its audio output options too, moving that URL read out to main.tsx, where the app shell can act as the adapter. The new CallViewModelOptions fields are optional, defaulting to what the URL parameters resolve to outside widget mode; the MediaDevices and Publisher arguments are required, so that every construction site has to be explicit. useTheme.test.ts mocked the UrlParams module with a factory, so it needed updating to mock the hook rather than getUrlParams. No functional change.
116 lines
3.0 KiB
TypeScript
116 lines
3.0 KiB
TypeScript
/*
|
|
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 { fn, userEvent, within, expect } from "storybook/test";
|
|
import { type JSX } from "react";
|
|
|
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
import { MediaMuteAndSwitchButton } from "./MediaMuteAndSwitchButton";
|
|
import { MediaDevicesContext } from "../MediaDevicesContext";
|
|
import { MediaDevices } from "../state/MediaDevices";
|
|
import { globalScope } from "../state/ObservableScope";
|
|
|
|
const mediaDevices = new MediaDevices(globalScope, {
|
|
controlledAudioDevices: false,
|
|
});
|
|
|
|
const meta = {
|
|
component: MediaMuteAndSwitchButton,
|
|
decorators: [
|
|
(Story): JSX.Element => (
|
|
<MediaDevicesContext value={mediaDevices}>
|
|
<Story />
|
|
</MediaDevicesContext>
|
|
),
|
|
],
|
|
} satisfies Meta<typeof MediaMuteAndSwitchButton>;
|
|
|
|
export default meta;
|
|
type Story = StoryObj<typeof meta>;
|
|
|
|
export const Default: Story = {
|
|
args: {
|
|
title: "SomeMenu",
|
|
iconsAndLabels: "audio",
|
|
enabled: true,
|
|
options: [
|
|
{ label: { type: "name", name: "Option 1" }, id: "1" },
|
|
{ label: { type: "name", name: "Option 2" }, id: "2" },
|
|
],
|
|
selectedOption: "1",
|
|
onMuteClick: fn(),
|
|
onSelect: fn(),
|
|
},
|
|
};
|
|
|
|
export const AudioMute: Story = {
|
|
args: {
|
|
...Default.args,
|
|
title: "Microphone",
|
|
iconsAndLabels: "audio",
|
|
enabled: false,
|
|
options: [
|
|
{ label: { type: "name", name: "Microphone 1" }, id: "1" },
|
|
{ label: { type: "name", name: "Microphone 2" }, id: "2" },
|
|
],
|
|
videoBlurEnabled: true,
|
|
videoBlurToggleClick: fn(),
|
|
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.getByTestId("incall_mute");
|
|
await userEvent.click(muteButton);
|
|
await expect(args.onMuteClick).toHaveBeenCalled();
|
|
},
|
|
};
|
|
|
|
export const AudioUnmute: Story = {
|
|
args: {
|
|
title: "Microphone",
|
|
iconsAndLabels: "audio",
|
|
enabled: true,
|
|
options: [
|
|
{ label: { type: "name", name: "Microphone 1" }, id: "1" },
|
|
{ label: { type: "name", name: "Microphone 2" }, id: "2" },
|
|
],
|
|
|
|
selectedOption: "2",
|
|
},
|
|
};
|
|
|
|
export const VideoMute: Story = {
|
|
args: {
|
|
title: "Camera",
|
|
iconsAndLabels: "video",
|
|
enabled: false,
|
|
options: [
|
|
{ label: { type: "name", name: "Camera 1" }, id: "1" },
|
|
{ label: { type: "name", name: "Camera 2" }, id: "2" },
|
|
],
|
|
|
|
selectedOption: "1",
|
|
},
|
|
};
|
|
|
|
export const VideoUnmute: Story = {
|
|
args: {
|
|
title: "Camera",
|
|
iconsAndLabels: "video",
|
|
enabled: true,
|
|
options: [
|
|
{ label: { type: "name", name: "Camera 1" }, id: "1" },
|
|
{ label: { type: "name", name: "Camera 2" }, id: "2" },
|
|
],
|
|
videoBlurEnabled: true,
|
|
videoBlurToggleClick: fn(),
|
|
selectedOption: "2",
|
|
},
|
|
};
|