Skip to content

Commit

Permalink
Remember previous mute states when devices disappear and reappear (#2957
Browse files Browse the repository at this point in the history
)
  • Loading branch information
robintown authored Jan 17, 2025
1 parent 74d4556 commit c218dc2
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 2 deletions.
54 changes: 53 additions & 1 deletion src/room/MuteStates.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ Please see LICENSE in the repository root for full details.
*/

import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import { type ReactNode } from "react";
import { type FC, useCallback, useState, type ReactNode } from "react";
import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";

import { useMuteStates } from "./MuteStates";
import {
Expand All @@ -21,11 +22,16 @@ import { mockConfig } from "../utils/test";

function TestComponent(): ReactNode {
const muteStates = useMuteStates();
const onToggleAudio = useCallback(
() => muteStates.audio.setEnabled?.(!muteStates.audio.enabled),
[muteStates],
);
return (
<div>
<div data-testid="audio-enabled">
{muteStates.audio.enabled.toString()}
</div>
<button onClick={onToggleAudio}>Toggle audio</button>
<div data-testid="video-enabled">
{muteStates.video.enabled.toString()}
</div>
Expand Down Expand Up @@ -174,4 +180,50 @@ describe("useMuteStates", () => {
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
});

it("remembers previous state when devices disappear and reappear", async () => {
const user = userEvent.setup();
mockConfig();
const noDevices = mockMediaDevices({ microphone: false, camera: false });
const someDevices = mockMediaDevices();
const ReappearanceTest: FC = () => {
const [devices, setDevices] = useState(someDevices);
const onConnectDevicesClick = useCallback(
() => setDevices(someDevices),
[],
);
const onDisconnectDevicesClick = useCallback(
() => setDevices(noDevices),
[],
);

return (
<MemoryRouter>
<MediaDevicesContext.Provider value={devices}>
<TestComponent />
<button onClick={onConnectDevicesClick}>Connect devices</button>
<button onClick={onDisconnectDevicesClick}>
Disconnect devices
</button>
</MediaDevicesContext.Provider>
</MemoryRouter>
);
};

render(<ReappearanceTest />);
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
await user.click(screen.getByRole("button", { name: "Toggle audio" }));
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
await user.click(
screen.getByRole("button", { name: "Disconnect devices" }),
);
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
await user.click(screen.getByRole("button", { name: "Connect devices" }));
// Audio should remember that it was muted, while video should re-enable
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
});
});
3 changes: 2 additions & 1 deletion src/room/MuteStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ function useMuteState(
enabledByDefault: () => boolean,
): MuteState {
const [enabled, setEnabled] = useReactiveState<boolean | undefined>(
// Determine the default value once devices are actually connected
(prev) =>
device.available.size > 0 ? (prev ?? enabledByDefault()) : undefined,
prev ?? (device.available.size > 0 ? enabledByDefault() : undefined),
[device],
);
return useMemo(
Expand Down

0 comments on commit c218dc2

Please sign in to comment.