런타임 계약과 학습자 흐름 보강
This commit is contained in:
parent
f456b8997a
commit
206018b088
56 changed files with 4306 additions and 1008 deletions
|
|
@ -39,6 +39,9 @@ interface VoiceUiProbeState {
|
|||
getUserMediaCalls: number;
|
||||
recorderStarts: number;
|
||||
recorderStops: number;
|
||||
workletModuleLoads: number;
|
||||
workletNodes: number;
|
||||
workletChunks: number;
|
||||
trackStops: number;
|
||||
audioPlays: number;
|
||||
audioContextResumes: number;
|
||||
|
|
@ -375,6 +378,9 @@ async function installSyntheticVoiceCapture(
|
|||
getUserMediaCalls: number;
|
||||
recorderStarts: number;
|
||||
recorderStops: number;
|
||||
workletModuleLoads: number;
|
||||
workletNodes: number;
|
||||
workletChunks: number;
|
||||
trackStops: number;
|
||||
audioPlays: number;
|
||||
audioContextResumes: number;
|
||||
|
|
@ -388,6 +394,9 @@ async function installSyntheticVoiceCapture(
|
|||
getUserMediaCalls: 0,
|
||||
recorderStarts: 0,
|
||||
recorderStops: 0,
|
||||
workletModuleLoads: 0,
|
||||
workletNodes: 0,
|
||||
workletChunks: 0,
|
||||
trackStops: 0,
|
||||
audioPlays: 0,
|
||||
audioContextResumes: 0,
|
||||
|
|
@ -560,9 +569,68 @@ async function installSyntheticVoiceCapture(
|
|||
}
|
||||
}
|
||||
|
||||
class FakeWorkletPort {
|
||||
onmessage: ((event: MessageEvent) => void) | null = null;
|
||||
private closed = false;
|
||||
|
||||
postMessage(message: unknown) {
|
||||
if ((message as { type?: string })?.type === "flush") {
|
||||
this.emitChunk();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
emitChunk() {
|
||||
if (this.closed) return;
|
||||
probe.workletChunks += 1;
|
||||
const pcm = new Int16Array([0, 1024, -1024, 0]);
|
||||
this.onmessage?.({ data: { type: "chunk", pcm: pcm.buffer } } as MessageEvent);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAudioWorkletNode {
|
||||
port = new FakeWorkletPort();
|
||||
|
||||
constructor(_ctx: unknown, _name: string, _options?: unknown) {
|
||||
probe.workletNodes += 1;
|
||||
}
|
||||
|
||||
connect() {
|
||||
return this;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
__start() {
|
||||
window.setTimeout(() => this.port.emitChunk(), 25);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeMediaStreamSource {
|
||||
connect(node: { __start?: () => void }) {
|
||||
node.__start?.();
|
||||
return node;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAudioContext {
|
||||
state = "running";
|
||||
destination = {};
|
||||
sampleRate = 16000;
|
||||
audioWorklet = {
|
||||
addModule: async (_url: string) => {
|
||||
probe.workletModuleLoads += 1;
|
||||
},
|
||||
};
|
||||
|
||||
async resume() {
|
||||
probe.audioContextResumes += 1;
|
||||
|
|
@ -584,8 +652,16 @@ async function installSyntheticVoiceCapture(
|
|||
createBufferSource() {
|
||||
return new FakeBufferSource();
|
||||
}
|
||||
|
||||
createMediaStreamSource(_stream: unknown) {
|
||||
return new FakeMediaStreamSource();
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "AudioWorkletNode", {
|
||||
configurable: true,
|
||||
value: FakeAudioWorkletNode,
|
||||
});
|
||||
Object.defineProperty(window, "AudioContext", {
|
||||
configurable: true,
|
||||
value: FakeAudioContext,
|
||||
|
|
@ -883,7 +959,13 @@ test.describe("voice cascade success path", () => {
|
|||
.poll(async () => (await readVoiceUiProbe(page)).getUserMediaCalls, { timeout: 10_000 })
|
||||
.toBeGreaterThan(0);
|
||||
await expect
|
||||
.poll(async () => (await readVoiceUiProbe(page)).recorderStarts, { timeout: 10_000 })
|
||||
.poll(async () => (await readVoiceUiProbe(page)).workletModuleLoads, { timeout: 10_000 })
|
||||
.toBeGreaterThan(0);
|
||||
await expect
|
||||
.poll(async () => (await readVoiceUiProbe(page)).workletNodes, { timeout: 10_000 })
|
||||
.toBeGreaterThan(0);
|
||||
await expect
|
||||
.poll(async () => (await readVoiceUiProbe(page)).workletChunks, { timeout: 10_000 })
|
||||
.toBeGreaterThan(0);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
|
|
@ -892,7 +974,9 @@ test.describe("voice cascade success path", () => {
|
|||
(message) =>
|
||||
message.direction === "sent" &&
|
||||
message.kind === "text" &&
|
||||
message.data?.includes('"audio_start"'),
|
||||
message.data?.includes('"audio_start"') &&
|
||||
message.data?.includes('"format":"pcm"') &&
|
||||
message.data?.includes('"sample_rate":16000'),
|
||||
);
|
||||
}, { timeout: 10_000 })
|
||||
.toBeTruthy();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue