mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
useMicrophoneLevel opens a capture of the given microphone for as long as it is enabled, reports a smoothed 0..1 level per animation frame, and tears the capture down on disable, device change or unmount, including while the capture is still being acquired. A denied permission and a microphone that cannot be opened are reported as distinct states. AudioLevelMeter renders that state as a row of bars, focusable and announcing its state to a screen reader only while focused, with a hint in place of the meter when permission is denied and a greyed-out meter when the microphone is unavailable. Nothing renders it yet; the audio menu picks it up next. Spec: FEATURES_SPEC/2026-09_Audio_Quick_Menu.md, slice 2 — AC8, AC11, AC13, AC15. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
59 lines
2.2 KiB
TypeScript
59 lines
2.2 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 { describe, expect, test } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
|
|
import { AudioLevelMeter } from "./AudioLevelMeter";
|
|
|
|
describe("AudioLevelMeter", () => {
|
|
test("a denied microphone renders a hint instead of the meter", () => {
|
|
render(<AudioLevelMeter state={{ type: "denied" }} />);
|
|
expect(screen.getByTestId("mic_level_denied")).toHaveTextContent(
|
|
/microphone access is blocked/i,
|
|
);
|
|
// A hint replaces the meter rather than sitting next to a dead one.
|
|
expect(screen.queryByRole("meter")).toBe(null);
|
|
});
|
|
|
|
test("level indicator greys out when the microphone is unavailable", () => {
|
|
render(<AudioLevelMeter state={{ type: "unavailable" }} />);
|
|
const meter = screen.getByRole("meter");
|
|
expect(meter).toHaveAttribute("data-unavailable", "true");
|
|
expect(meter).toHaveAttribute("aria-valuetext", "Microphone unavailable");
|
|
});
|
|
|
|
test("level indicator reports its level to assistive technology", () => {
|
|
const { rerender } = render(
|
|
<AudioLevelMeter state={{ type: "active", level: 0 }} />,
|
|
);
|
|
const meter = screen.getByRole("meter");
|
|
expect(meter).toHaveAttribute("aria-valuenow", "0");
|
|
expect(meter).toHaveAttribute("aria-valuetext", "No sound detected");
|
|
|
|
rerender(<AudioLevelMeter state={{ type: "active", level: 0.7 }} />);
|
|
expect(screen.getByRole("meter")).toHaveAttribute(
|
|
"aria-valuetext",
|
|
"Picking up sound",
|
|
);
|
|
});
|
|
|
|
test("level indicator announces its state only while focused", async () => {
|
|
const user = userEvent.setup();
|
|
render(<AudioLevelMeter state={{ type: "active", level: 0.7 }} />);
|
|
const meter = screen.getByRole("meter");
|
|
|
|
// Nothing is announced until the user puts the meter in focus, so the
|
|
// level does not talk over the rest of the menu.
|
|
expect(meter.textContent).not.toContain("Picking up sound");
|
|
await user.tab();
|
|
expect(meter).toHaveFocus();
|
|
expect(meter.textContent).toContain("Picking up sound");
|
|
});
|
|
});
|