初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* useVoiceInput 测试 — mock MediaRecorder 与 ASR client。
|
||||
*/
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { useVoiceInput } from "./useVoiceInput";
|
||||
|
||||
vi.mock("@/lib/api/audio", () => ({
|
||||
transcribeAudio: vi.fn().mockResolvedValue({ text: "你好世界" }),
|
||||
}));
|
||||
|
||||
import { transcribeAudio } from "@/lib/api/audio";
|
||||
|
||||
/** 最小 MediaRecorder stub:start/stop 并触发 ondataavailable / onstop。 */
|
||||
class RecorderStub {
|
||||
state = "inactive";
|
||||
ondataavailable: ((e: { data: Blob }) => void) | null = null;
|
||||
onstop: (() => void) | null = null;
|
||||
start() {
|
||||
this.state = "recording";
|
||||
}
|
||||
stop() {
|
||||
this.state = "inactive";
|
||||
this.ondataavailable?.({ data: new Blob([new Uint8Array([1, 2, 3])]) });
|
||||
this.onstop?.();
|
||||
}
|
||||
}
|
||||
|
||||
describe("useVoiceInput", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("toggle 录音并转写后回调 onResult", async () => {
|
||||
const onResult = vi.fn();
|
||||
const onError = vi.fn();
|
||||
const stopTracks = vi.fn();
|
||||
const getTracks = () =>
|
||||
[{ stop: stopTracks }] as unknown as MediaStreamTrack[];
|
||||
|
||||
Object.defineProperty(globalThis.navigator, "mediaDevices", {
|
||||
value: {
|
||||
getUserMedia: vi.fn().mockResolvedValue({ getTracks }),
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
// 以 function 构造器作为 MediaRecorder,返回 stub 实例
|
||||
function MR(this: unknown) {
|
||||
return new RecorderStub();
|
||||
}
|
||||
Object.defineProperty(globalThis, "MediaRecorder", {
|
||||
value: MR,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(globalThis.MediaRecorder, "isTypeSupported", {
|
||||
value: () => true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useVoiceInput({ language: "zh", onResult, onError }),
|
||||
);
|
||||
|
||||
expect(result.current.status).toBe("idle");
|
||||
|
||||
await act(async () => {
|
||||
await result.current.toggle();
|
||||
});
|
||||
expect(result.current.status).toBe("recording");
|
||||
|
||||
await act(async () => {
|
||||
await result.current.toggle();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.status).toBe("idle");
|
||||
});
|
||||
|
||||
expect(transcribeAudio).toHaveBeenCalledOnce();
|
||||
expect(transcribeAudio).toHaveBeenCalledWith(
|
||||
expect.any(Blob),
|
||||
expect.stringMatching(/^voice-\d+\.webm$/),
|
||||
"zh",
|
||||
expect.any(AbortSignal),
|
||||
"funasr-sensevoice",
|
||||
);
|
||||
expect(onResult).toHaveBeenCalledWith("你好世界");
|
||||
expect(onError).not.toHaveBeenCalled();
|
||||
expect(stopTracks).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("不支持 MediaRecorder 时触发 onError", async () => {
|
||||
const onResult = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
Object.defineProperty(globalThis.navigator, "mediaDevices", {
|
||||
value: { getUserMedia: vi.fn() },
|
||||
configurable: true,
|
||||
});
|
||||
// 设为 undefined 模拟不支持
|
||||
Object.defineProperty(globalThis, "MediaRecorder", {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useVoiceInput({ language: "zh", onResult, onError }),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.start();
|
||||
});
|
||||
expect(onError).toHaveBeenCalledWith("当前浏览器不支持 MediaRecorder。");
|
||||
expect(onResult).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user