All checks were successful
ci/woodpecker/push/web Pipeline was successful
Implements AudioPlayer inline component with play/pause, progress bar, speed control (0.5x-2x), download, and duration display. Adds TextToSpeechButton "Read aloud" component that synthesizes text via the speech API and integrates AudioPlayer for playback. Includes useTextToSpeech hook with API integration, audio caching, and playback state management. All 32 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { AudioVisualizer } from "./AudioVisualizer";
|
|
|
|
describe("AudioVisualizer", (): void => {
|
|
it("should render the visualizer container", (): void => {
|
|
render(<AudioVisualizer audioLevel={0} isActive={false} />);
|
|
|
|
const container = screen.getByTestId("audio-visualizer");
|
|
expect(container).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render visualization bars", (): void => {
|
|
render(<AudioVisualizer audioLevel={0.5} isActive={true} />);
|
|
|
|
const bars = screen.getAllByTestId("visualizer-bar");
|
|
expect(bars.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should show inactive state when not active", (): void => {
|
|
render(<AudioVisualizer audioLevel={0} isActive={false} />);
|
|
|
|
const container = screen.getByTestId("audio-visualizer");
|
|
expect(container).toBeInTheDocument();
|
|
// Bars should be at minimum height when inactive
|
|
const bars = screen.getAllByTestId("visualizer-bar");
|
|
bars.forEach((bar) => {
|
|
const style = bar.getAttribute("style");
|
|
expect(style).toContain("height");
|
|
});
|
|
});
|
|
|
|
it("should reflect audio level in bar heights when active", (): void => {
|
|
render(<AudioVisualizer audioLevel={0.8} isActive={true} />);
|
|
|
|
const bars = screen.getAllByTestId("visualizer-bar");
|
|
// At least one bar should have non-minimal height
|
|
const hasActiveBars = bars.some((bar) => {
|
|
const style = bar.getAttribute("style") ?? "";
|
|
const heightMatch = /height:\s*(\d+)/.exec(style);
|
|
return heightMatch?.[1] ? parseInt(heightMatch[1], 10) > 4 : false;
|
|
});
|
|
expect(hasActiveBars).toBe(true);
|
|
});
|
|
|
|
it("should use calm colors (no aggressive reds)", (): void => {
|
|
render(<AudioVisualizer audioLevel={0.5} isActive={true} />);
|
|
|
|
const container = screen.getByTestId("audio-visualizer");
|
|
const allElements = container.querySelectorAll("*");
|
|
allElements.forEach((el) => {
|
|
const className = (el as HTMLElement).className;
|
|
expect(className).not.toMatch(/bg-red-|text-red-/);
|
|
});
|
|
});
|
|
|
|
it("should accept custom className", (): void => {
|
|
render(<AudioVisualizer audioLevel={0.5} isActive={true} className="custom-class" />);
|
|
|
|
const container = screen.getByTestId("audio-visualizer");
|
|
expect(container.className).toContain("custom-class");
|
|
});
|
|
|
|
it("should render with configurable bar count", (): void => {
|
|
render(<AudioVisualizer audioLevel={0.5} isActive={true} barCount={8} />);
|
|
|
|
const bars = screen.getAllByTestId("visualizer-bar");
|
|
expect(bars).toHaveLength(8);
|
|
});
|
|
});
|