carepath-api / scribe /frontend /src /visit /speech.test.ts
tranth3truong's picture
Deploy CP-UX-17: public demo hub, same-origin API, one design world
aacc29a
Raw
History Blame Contribute Delete
2.5 kB
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();
});
});