diff --git a/src/room/MuteStates.test.tsx b/src/room/MuteStates.test.tsx index 719315e89..99f7eaf84 100644 --- a/src/room/MuteStates.test.tsx +++ b/src/room/MuteStates.test.tsx @@ -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 { @@ -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 (
{muteStates.audio.enabled.toString()}
+
{muteStates.video.enabled.toString()}
@@ -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 ( + + + + + + + + ); + }; + + render(); + 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"); + }); }); diff --git a/src/room/MuteStates.ts b/src/room/MuteStates.ts index 4a8aa9ddc..132273787 100644 --- a/src/room/MuteStates.ts +++ b/src/room/MuteStates.ts @@ -57,8 +57,9 @@ function useMuteState( enabledByDefault: () => boolean, ): MuteState { const [enabled, setEnabled] = useReactiveState( + // 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(