|
@@ -0,0 +1,230 @@
|
|
|
|
|
+import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
|
|
+import { AgoraSTTComponent } from '../AgoraSTTComponent';
|
|
|
|
|
+
|
|
|
|
|
+// Mock the useAgoraSTT hook
|
|
|
|
|
+vi.mock('@/client/hooks/useAgoraSTT', () => ({
|
|
|
|
|
+ useAgoraSTT: vi.fn()
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+// Mock the utils
|
|
|
|
|
+vi.mock('@/client/utils/agora-stt', () => ({
|
|
|
|
|
+ getAgoraConfig: vi.fn(),
|
|
|
|
|
+ validateAgoraConfig: vi.fn(),
|
|
|
|
|
+ isBrowserSupported: vi.fn(() => true),
|
|
|
|
|
+ getBrowserSupportError: vi.fn(() => null)
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+// Mock UI components
|
|
|
|
|
+vi.mock('@/client/components/ui/button', () => ({
|
|
|
|
|
+ Button: ({ children, onClick, disabled, variant, className }: any) => (
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={onClick}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ data-variant={variant}
|
|
|
|
|
+ className={className}
|
|
|
|
|
+ >
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ )
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+vi.mock('@/client/components/ui/card', () => ({
|
|
|
|
|
+ Card: ({ children, className }: any) => (
|
|
|
|
|
+ <div className={className}>{children}</div>
|
|
|
|
|
+ ),
|
|
|
|
|
+ CardHeader: ({ children }: any) => <div>{children}</div>,
|
|
|
|
|
+ CardTitle: ({ children }: any) => <h3>{children}</h3>,
|
|
|
|
|
+ CardDescription: ({ children }: any) => <p>{children}</p>,
|
|
|
|
|
+ CardContent: ({ children }: any) => <div>{children}</div>
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+vi.mock('@/client/components/ui/badge', () => ({
|
|
|
|
|
+ Badge: ({ children, variant, className }: any) => (
|
|
|
|
|
+ <span data-variant={variant} className={className}>{children}</span>
|
|
|
|
|
+ )
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+vi.mock('@/client/components/ui/alert', () => ({
|
|
|
|
|
+ Alert: ({ children }: any) => <div role="alert">{children}</div>,
|
|
|
|
|
+ AlertDescription: ({ children }: any) => <div>{children}</div>
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+// Mock Lucide icons
|
|
|
|
|
+vi.mock('lucide-react', () => ({
|
|
|
|
|
+ Mic: () => <span>Mic</span>,
|
|
|
|
|
+ MicOff: () => <span>MicOff</span>,
|
|
|
|
|
+ Play: () => <span>Play</span>,
|
|
|
|
|
+ Square: () => <span>Square</span>,
|
|
|
|
|
+ Trash2: () => <span>Trash2</span>,
|
|
|
|
|
+ Wifi: () => <span>Wifi</span>,
|
|
|
|
|
+ WifiOff: () => <span>WifiOff</span>
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+const mockUseAgoraSTT = vi.mocked(require('@/client/hooks/useAgoraSTT').useAgoraSTT);
|
|
|
|
|
+
|
|
|
|
|
+describe('AgoraSTTComponent', () => {
|
|
|
|
|
+ const defaultState = {
|
|
|
|
|
+ isConnected: false,
|
|
|
|
|
+ isRecording: false,
|
|
|
|
|
+ isTranscribing: false,
|
|
|
|
|
+ error: null,
|
|
|
|
|
+ transcriptionResults: [],
|
|
|
|
|
+ currentTranscription: ''
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const mockJoinChannel = vi.fn();
|
|
|
|
|
+ const mockLeaveChannel = vi.fn();
|
|
|
|
|
+ const mockStartRecording = vi.fn();
|
|
|
|
|
+ const mockStopRecording = vi.fn();
|
|
|
|
|
+ const mockClearTranscriptions = vi.fn();
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(() => {
|
|
|
|
|
+ vi.clearAllMocks();
|
|
|
|
|
+
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('renders component with initial state', () => {
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('语音转文字')).toBeInTheDocument();
|
|
|
|
|
+ expect(screen.getByText('实时语音识别和转录功能')).toBeInTheDocument();
|
|
|
|
|
+ expect(screen.getByText('加入频道')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('shows connection status when connected', () => {
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState, isConnected: true },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('已连接')).toBeInTheDocument();
|
|
|
|
|
+ expect(screen.getByText('开始录音')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('shows recording status when recording', () => {
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState, isConnected: true, isRecording: true },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('录制中')).toBeInTheDocument();
|
|
|
|
|
+ expect(screen.getByText('停止录音')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('shows error message when there is an error', () => {
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState, error: 'Configuration error' },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('Configuration error')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('calls joinChannel when join button is clicked', async () => {
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ const joinButton = screen.getByText('加入频道');
|
|
|
|
|
+ fireEvent.click(joinButton);
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(mockJoinChannel).toHaveBeenCalledTimes(1);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('calls startRecording when start button is clicked', async () => {
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState, isConnected: true },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ const startButton = screen.getByText('开始录音');
|
|
|
|
|
+ fireEvent.click(startButton);
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(mockStartRecording).toHaveBeenCalledTimes(1);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('shows transcription results', () => {
|
|
|
|
|
+ const transcriptionResults = [
|
|
|
|
|
+ {
|
|
|
|
|
+ text: 'Hello world',
|
|
|
|
|
+ isFinal: true,
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ confidence: 0.95
|
|
|
|
|
+ }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState, isConnected: true, transcriptionResults },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
|
|
|
|
|
+ expect(screen.getByText('最终')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('shows interim transcription results', () => {
|
|
|
|
|
+ const transcriptionResults = [
|
|
|
|
|
+ {
|
|
|
|
|
+ text: 'Hello world interim',
|
|
|
|
|
+ isFinal: false,
|
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
|
+ confidence: 0.75
|
|
|
|
|
+ }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ mockUseAgoraSTT.mockReturnValue({
|
|
|
|
|
+ state: { ...defaultState, isConnected: true, transcriptionResults },
|
|
|
|
|
+ joinChannel: mockJoinChannel,
|
|
|
|
|
+ leaveChannel: mockLeaveChannel,
|
|
|
|
|
+ startRecording: mockStartRecording,
|
|
|
|
|
+ stopRecording: mockStopRecording,
|
|
|
|
|
+ clearTranscriptions: mockClearTranscriptions
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(<AgoraSTTComponent />);
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('Hello world interim')).toBeInTheDocument();
|
|
|
|
|
+ expect(screen.getByText('临时')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+});
|