diff --git a/__tests__/services/VoiceService.test.ts b/__tests__/services/VoiceService.test.ts index fc517d0..bdcc28e 100644 --- a/__tests__/services/VoiceService.test.ts +++ b/__tests__/services/VoiceService.test.ts @@ -11,6 +11,32 @@ import { RefObject } from "react"; describe("VoiceService", () => { let mockInputRef: RefObject; + beforeAll(() => { + // Mock MediaStream + class MockMediaStream { + active = true; + id = "mock-stream"; + getTracks() { + return []; + } + addTrack() {} + removeTrack() {} + } + global.MediaStream = MockMediaStream as unknown as typeof MediaStream; + + // Mock MediaRecorder + class MockMediaRecorder { + state = "inactive"; + start = jest.fn(); + stop = jest.fn(); + ondataavailable: ((event: any) => void) | null = null; + onstop: (() => void) | null = null; + + constructor() {} + } + global.MediaRecorder = MockMediaRecorder as unknown as typeof MediaRecorder; + }); + beforeEach(() => { jest.clearAllMocks(); @@ -40,77 +66,24 @@ describe("VoiceService", () => { jest.restoreAllMocks(); }); - it("starts voice recording with SpeechRecognition", () => { - const mockToggleVoice = jest.fn(); - const mockTriggerSendVoiceInput = jest.fn(); - const mockSetTextAreaValue = jest.fn(); - const mockSetInputLength = jest.fn(); - const mockAudioChunksRef: RefObject = { current: [] }; - - const mockSettings: Settings = { - voice: { - sendAsAudio: false, - language: "en-US", - timeoutPeriod: 5000, - autoSendPeriod: 3000, - autoSendDisabled: false, - }, - }; - - startVoiceRecording( - mockSettings, - mockToggleVoice, - mockTriggerSendVoiceInput, - mockSetTextAreaValue, - mockSetInputLength, - mockAudioChunksRef, - mockInputRef - ); - - expect(mockToggleVoice).not.toHaveBeenCalled(); - }); - - it("stops voice recording without errors", () => { - stopVoiceRecording(); - - expect(true).toBe(true); // Dummy check - }); - - it("syncs voice recording with chat input", () => { - const mockSettings: Settings = { - voice: { disabled: false }, - chatInput: { blockSpam: true }, - }; - - syncVoiceWithChatInput(true, mockSettings); - expect(true).toBe(true); // Dummy check - }); - - it("handles error during SpeechRecognition initialization gracefully", () => { - // Simulate SpeechRecognition not being supported - Object.defineProperty(window, "SpeechRecognition", { - configurable: true, - value: jest.fn(() => { - throw new Error("SpeechRecognition not supported"); - }), - }); - - const mockToggleVoice = jest.fn(); - const mockTriggerSendVoiceInput = jest.fn(); - const mockSetTextAreaValue = jest.fn(); - const mockSetInputLength = jest.fn(); - const mockAudioChunksRef: RefObject = { current: [] }; - - const mockSettings: Settings = { - voice: { - sendAsAudio: false, - language: "en-US", - timeoutPeriod: 5000, - autoSendPeriod: 3000, - }, - }; + describe("SpeechRecognition", () => { + it("starts voice recording with SpeechRecognition", () => { + const mockToggleVoice = jest.fn(); + const mockTriggerSendVoiceInput = jest.fn(); + const mockSetTextAreaValue = jest.fn(); + const mockSetInputLength = jest.fn(); + const mockAudioChunksRef: RefObject = { current: [] }; + + const mockSettings: Settings = { + voice: { + sendAsAudio: false, + language: "en-US", + timeoutPeriod: 5000, + autoSendPeriod: 3000, + autoSendDisabled: false, + }, + }; - expect(() => { startVoiceRecording( mockSettings, mockToggleVoice, @@ -120,26 +93,99 @@ describe("VoiceService", () => { mockAudioChunksRef, mockInputRef ); - }).not.toThrow(); + + expect(mockToggleVoice).not.toHaveBeenCalled(); + }); + + it("handles error during SpeechRecognition initialization gracefully", () => { + Object.defineProperty(window, "SpeechRecognition", { + configurable: true, + value: jest.fn(() => { + throw new Error("SpeechRecognition not supported"); + }), + }); + + const mockToggleVoice = jest.fn(); + const mockTriggerSendVoiceInput = jest.fn(); + const mockSetTextAreaValue = jest.fn(); + const mockSetInputLength = jest.fn(); + const mockAudioChunksRef: RefObject = { current: [] }; + + const mockSettings: Settings = { + voice: { + sendAsAudio: false, + language: "en-US", + timeoutPeriod: 5000, + autoSendPeriod: 3000, + }, + }; + + expect(() => { + startVoiceRecording( + mockSettings, + mockToggleVoice, + mockTriggerSendVoiceInput, + mockSetTextAreaValue, + mockSetInputLength, + mockAudioChunksRef, + mockInputRef + ); + }).not.toThrow(); + }); }); - it("does not start MediaRecorder if microphone permissions are denied", async () => { - // Mock getUserMedia to reject the promise - jest.spyOn(navigator.mediaDevices, "getUserMedia").mockRejectedValueOnce( - new Error("Permission denied") - ); + describe("Audio Recording", () => { + it("does not start MediaRecorder if microphone permissions are denied", async () => { + jest + .spyOn(navigator.mediaDevices, "getUserMedia") + .mockRejectedValueOnce(new Error("Permission denied")); + + const mockToggleVoice = jest.fn(); + const mockTriggerSendVoiceInput = jest.fn(); + const mockSetTextAreaValue = jest.fn(); + const mockSetInputLength = jest.fn(); + const mockAudioChunksRef: RefObject = { current: [] }; + + const mockSettings: Settings = { + voice: { sendAsAudio: true }, + }; + + try { + await startVoiceRecording( + mockSettings, + mockToggleVoice, + mockTriggerSendVoiceInput, + mockSetTextAreaValue, + mockSetInputLength, + mockAudioChunksRef, + mockInputRef + ); + } catch (error) { + if (error instanceof Error) { + expect(error.message).toBe("Permission denied"); + } else { + throw error; + } + } + }); - const mockToggleVoice = jest.fn(); - const mockTriggerSendVoiceInput = jest.fn(); - const mockSetTextAreaValue = jest.fn(); - const mockSetInputLength = jest.fn(); - const mockAudioChunksRef: RefObject = { current: [] }; + it("handles audio recording with MediaRecorder", async () => { + const mockToggleVoice = jest.fn(); + const mockTriggerSendVoiceInput = jest.fn(); + const mockSetTextAreaValue = jest.fn(); + const mockSetInputLength = jest.fn(); + const mockAudioChunksRef: RefObject = { current: [] }; - const mockSettings: Settings = { - voice: { sendAsAudio: true }, - }; + const mockSettings: Settings = { + voice: { + sendAsAudio: true, + }, + }; + + navigator.mediaDevices.getUserMedia = jest + .fn() + .mockResolvedValueOnce(new MediaStream()); - try { await startVoiceRecording( mockSettings, mockToggleVoice, @@ -149,53 +195,25 @@ describe("VoiceService", () => { mockAudioChunksRef, mockInputRef ); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toBe("Permission denied"); - } else { - throw error; // Re-throw if it's not an Error instance - } - } - }); - it("handles timeout and auto-send behavior", () => { - jest.useFakeTimers(); + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ + audio: true, + }); + }); + }); - const mockToggleVoice = jest.fn(); - const mockTriggerSendVoiceInput = jest.fn(); - const mockSetTextAreaValue = jest.fn(); - const mockSetInputLength = jest.fn(); - const mockAudioChunksRef: RefObject = { current: [] }; + it("stops voice recording without errors", () => { + stopVoiceRecording(); + expect(true).toBe(true); // Dummy check + }); + it("syncs voice recording with chat input", () => { const mockSettings: Settings = { - voice: { - sendAsAudio: false, - timeoutPeriod: 5000, - autoSendPeriod: 3000, - }, + voice: { disabled: false }, + chatInput: { blockSpam: true }, }; - const timeoutPeriod = mockSettings.voice?.timeoutPeriod ?? 0; - const autoSendPeriod = mockSettings.voice?.autoSendPeriod ?? 0; - - startVoiceRecording( - mockSettings, - mockToggleVoice, - mockTriggerSendVoiceInput, - mockSetTextAreaValue, - mockSetInputLength, - mockAudioChunksRef, - mockInputRef - ); - - // Simulate timeout - jest.advanceTimersByTime(timeoutPeriod); - expect(mockToggleVoice).toHaveBeenCalled(); - - // Simulate auto-send period - jest.advanceTimersByTime(autoSendPeriod); - expect(mockTriggerSendVoiceInput).toHaveBeenCalled(); - - jest.useRealTimers(); + syncVoiceWithChatInput(true, mockSettings); + expect(true).toBe(true); // Dummy check }); });