初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
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();
});
});