Files
element-call-Github/src/components/AudioLevelMeter.test.tsx
T
fkwpandClaude Fable 5.1 29a7a50d57 Add the microphone level meter and its capture hook
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>
2026-09-10 23:53:36 +02:00

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");
});
});