YC-Chen's picture
refactor: modularize frontend audio, components, and tests
2b18d49
/**
* Test setup for TASTE Voice Bot frontend tests
*/
// Mock Web APIs that are not available in Jest environment
global.AudioContext = jest.fn(() => ({
createScriptProcessor: jest.fn(),
createGain: jest.fn(),
createBuffer: jest.fn(),
createBufferSource: jest.fn(),
destination: {},
sampleRate: 44100,
close: jest.fn(),
suspend: jest.fn(),
resume: jest.fn()
}));
global.MediaStream = jest.fn(() => ({
getTracks: jest.fn(() => []),
getAudioTracks: jest.fn(() => []),
addTrack: jest.fn(),
removeTrack: jest.fn()
}));
// Mock navigator.mediaDevices
global.navigator.mediaDevices = {
getUserMedia: jest.fn()
};
// Mock HTMLCanvasElement and its context
global.HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
fillRect: jest.fn(),
clearRect: jest.fn(),
fillStyle: '',
strokeStyle: '',
lineWidth: 1,
beginPath: jest.fn(),
moveTo: jest.fn(),
lineTo: jest.fn(),
stroke: jest.fn()
}));
// Mock console methods to reduce noise in tests
global.console = {
...console,
log: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
};
// Mock grpc-web (since it's not available in test environment)
jest.mock('grpc-web', () => ({
GrpcWebClientBase: jest.fn(() => ({
rpcCall: jest.fn(),
serverStreaming: jest.fn(),
thenableCall: jest.fn()
}))
}), { virtual: true });
// Setup DOM elements that tests might need
document.body.innerHTML = `
<div id="test-container">
<button id="start-server-btn"></button>
<button id="stop-server-btn"></button>
<button id="connect-btn"></button>
<button id="disconnect-btn"></button>
<button id="create-session-btn"></button>
<button id="end-session-btn"></button>
<button id="start-streaming-btn"></button>
<button id="stop-streaming-btn"></button>
<button id="replay-audio-btn"></button>
<button id="stop-audio-btn"></button>
<button id="refresh-status-btn"></button>
<input id="user-id-input" type="text">
<select id="voice-id-select"></select>
<input id="temperature-slider" type="range">
<input id="volume-slider" type="range">
<span id="server-status"></span>
<span id="connection-status"></span>
<span id="session-status"></span>
<span id="streaming-status"></span>
<span id="playback-status"></span>
<div id="conversation-history"></div>
<div id="status-messages"></div>
<pre id="statistics-display"></pre>
<canvas id="visualizer-canvas"></canvas>
<audio id="audio-player"></audio>
<div id="loading-overlay"></div>
</div>
`;
// Global test utilities
global.testUtils = {
/**
* Create a mock audio buffer
* @param {number} length - Buffer length
* @returns {ArrayBuffer} Mock audio buffer
*/
createMockAudioBuffer(length = 1024) {
const buffer = new ArrayBuffer(length * 2); // 16-bit audio
const view = new Int16Array(buffer);
for (let i = 0; i < length; i++) {
view[i] = Math.random() * 32767 - 16384;
}
return buffer;
},
/**
* Create a mock gRPC response
* @param {Object} data - Response data
* @returns {Object} Mock response
*/
createMockGrpcResponse(data = {}) {
return {
status: 'success',
message: 'Mock response',
...data
};
},
/**
* Wait for a specified time
* @param {number} ms - Milliseconds to wait
* @returns {Promise} Promise that resolves after wait
*/
wait(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms));
},
/**
* Trigger a DOM event
* @param {HTMLElement} element - Target element
* @param {string} eventType - Event type
* @param {Object} options - Event options
*/
triggerEvent(element, eventType, options = {}) {
const event = new Event(eventType, { bubbles: true, ...options });
element.dispatchEvent(event);
}
};
// Cleanup after each test
afterEach(() => {
jest.clearAllMocks();
});
// Global setup
beforeAll(() => {
// Suppress console.error for expected test failures
jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterAll(() => {
// Restore console.error
console.error.mockRestore();
});