keet-streaming / src /lib /buffer /buffer.worker.test.ts
ysdede's picture
feat(space): migrate Hugging Face Space to keet SolidJS app
b8cc2bf
/**
* Integration tests for buffer.worker.ts (v4 centralized multi-layer data store).
*
* Ensures the BufferWorker:
* - Loads and responds to INIT with the v4 layer config
* - Accepts WRITE to VAD layers and responds to HAS_SPEECH / GET_SILENCE_TAIL
* - Resets state on RESET
*
* Run: npm test
*/
import '@vitest/web-worker';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import type { BufferWorkerConfig } from './types';
function defaultConfig(): BufferWorkerConfig {
return {
sampleRate: 16000,
layers: {
audio: { hopSamples: 1, entryDimension: 1, maxDurationSec: 30 },
mel: { hopSamples: 160, entryDimension: 128, maxDurationSec: 30 },
energyVad: { hopSamples: 1280, entryDimension: 1, maxDurationSec: 30 },
inferenceVad: { hopSamples: 256, entryDimension: 1, maxDurationSec: 30 },
},
};
}
function sendRequest(
worker: Worker,
type: string,
payload: any,
id: number
): Promise<{ type: string; id?: number; payload?: any }> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`Worker ${type} timed out`)), 5000);
const handler = (e: MessageEvent) => {
const data = e.data as { type: string; id?: number; payload?: any };
if (data.type === 'ERROR' && data.id === id) {
clearTimeout(timeout);
worker.removeEventListener('message', handler);
reject(new Error(data.payload));
return;
}
if (data.id === id || (type === 'INIT' && data.type === 'INIT')) {
clearTimeout(timeout);
worker.removeEventListener('message', handler);
resolve(data);
return;
}
};
worker.addEventListener('message', handler);
worker.postMessage({ type, payload, id });
});
}
describe('buffer.worker', () => {
let worker: Worker;
let nextId: number;
beforeEach(() => {
worker = new Worker(new URL('./buffer.worker.ts', import.meta.url), {
type: 'module',
});
nextId = 1;
});
afterEach(() => {
worker.terminate();
});
it('should load without errors', async () => {
const errPromise = new Promise<ErrorEvent>((resolve) => {
worker.onerror = (e) => resolve(e as ErrorEvent);
});
const initPromise = sendRequest(worker, 'INIT', defaultConfig(), nextId++);
const result = await Promise.race([
initPromise.then(() => 'ok'),
errPromise.then((e) => {
throw new Error(`Worker load failed: ${e.message}`);
}),
]);
expect(result).toBe('ok');
});
it('should respond to INIT with success', async () => {
const response = await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
expect(response.type).toBe('INIT');
expect(response.payload?.success).toBe(true);
});
it('should return no speech and zero silence tail before any write', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
const hasSpeech = await sendRequest(
worker,
'HAS_SPEECH',
{ layer: 'energyVad', startSample: 0, endSample: 1280, threshold: 0.3 },
nextId++
);
expect(hasSpeech.type).toBe('HAS_SPEECH');
expect(hasSpeech.payload?.hasSpeech).toBe(false);
const silence = await sendRequest(
worker,
'GET_SILENCE_TAIL',
{ layer: 'energyVad', threshold: 0.3 },
nextId++
);
expect(silence.type).toBe('GET_SILENCE_TAIL');
expect(silence.payload?.durationSec).toBe(0);
});
it('should report speech after writing above-threshold values to energyVad', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
worker.postMessage({
type: 'WRITE',
payload: { layer: 'energyVad', data: [0.9] },
});
await new Promise((r) => setTimeout(r, 50));
const hasSpeech = await sendRequest(
worker,
'HAS_SPEECH',
{ layer: 'energyVad', startSample: 0, endSample: 1280, threshold: 0.3 },
nextId++
);
expect(hasSpeech.payload?.hasSpeech).toBe(true);
expect(hasSpeech.payload?.maxProb).toBeGreaterThanOrEqual(0.3);
});
it('should return silence tail duration after writing silence', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
worker.postMessage({ type: 'WRITE', payload: { layer: 'energyVad', data: [0.1] } });
worker.postMessage({ type: 'WRITE', payload: { layer: 'energyVad', data: [0.1] } });
await new Promise((r) => setTimeout(r, 50));
const silence = await sendRequest(
worker,
'GET_SILENCE_TAIL',
{ layer: 'energyVad', threshold: 0.3 },
nextId++
);
expect(silence.payload?.durationSec).toBeGreaterThan(0);
});
it('should respond to RESET with success', async () => {
await sendRequest(worker, 'INIT', defaultConfig(), nextId++);
const response = await sendRequest(worker, 'RESET', undefined, nextId++);
expect(response.type).toBe('RESET');
expect(response.payload?.success).toBe(true);
});
});