Spaces:
Runtime error
Runtime error
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| import { hasVoiceFor, speak } from "./speech"; | |
| const playMock = vi.fn(() => Promise.resolve()); | |
| const speakMock = vi.fn(); | |
| beforeEach(() => { | |
| playMock.mockClear(); | |
| speakMock.mockClear(); | |
| vi.stubGlobal("speechSynthesis", { | |
| speak: speakMock, | |
| cancel: vi.fn(), | |
| getVoices: () => [{ lang: "en-US", name: "English" }], | |
| }); | |
| vi.stubGlobal("SpeechSynthesisUtterance", class { | |
| lang = ""; | |
| constructor(public text: string) {} | |
| }); | |
| vi.stubGlobal("Audio", class { | |
| onended: (() => void) | null = null; | |
| constructor(public src: string) {} | |
| play = playMock; | |
| pause = vi.fn(); | |
| }); | |
| vi.stubGlobal("URL", { | |
| createObjectURL: () => "blob:fake", | |
| revokeObjectURL: vi.fn(), | |
| }); | |
| }); | |
| afterEach(() => vi.unstubAllGlobals()); | |
| describe("hasVoiceFor", () => { | |
| it("reports the missing Vietnamese voice that motivates server TTS", () => { | |
| expect(hasVoiceFor("vi")).toBe(false); | |
| expect(hasVoiceFor("en")).toBe(true); | |
| }); | |
| }); | |
| describe("speak", () => { | |
| it("uses the browser directly for English", async () => { | |
| vi.stubGlobal("fetch", vi.fn()); | |
| await speak("Take one tablet", "en"); | |
| expect(speakMock).toHaveBeenCalled(); | |
| expect(fetch).not.toHaveBeenCalled(); | |
| }); | |
| it("routes Vietnamese through the server voice", async () => { | |
| const fetchMock = vi.fn(async () => ({ ok: true, blob: async () => new Blob(["wav"]) })); | |
| vi.stubGlobal("fetch", fetchMock); | |
| await speak("T么i u峄憂g 500 mg", "vi"); | |
| expect(fetchMock).toHaveBeenCalled(); | |
| const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; | |
| expect(url).toContain("/api/v1/speech"); | |
| expect(JSON.parse(init.body as string)).toEqual({ text: "T么i u峄憂g 500 mg", lang: "vi" }); | |
| expect(playMock).toHaveBeenCalled(); | |
| expect(speakMock).not.toHaveBeenCalled(); | |
| }); | |
| it("falls back to the browser when the server voice is unavailable", async () => { | |
| vi.stubGlobal("fetch", vi.fn(async () => ({ ok: false, status: 503 }))); | |
| await speak("T么i u峄憂g 500 mg", "vi"); | |
| // Degraded audio beats a silent consultation. | |
| expect(speakMock).toHaveBeenCalled(); | |
| expect(playMock).not.toHaveBeenCalled(); | |
| }); | |
| it("falls back when the network throws", async () => { | |
| vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("offline"); })); | |
| await speak("T么i u峄憂g 500 mg", "vi"); | |
| expect(speakMock).toHaveBeenCalled(); | |
| }); | |
| }); | |