UNPKG

murmuraba

Version:

Real-time audio noise reduction with advanced chunked processing for web applications

280 lines (279 loc) 9.59 kB
/** * Shared Test Utilities * Consolidates all duplicated test patterns and mocks */ import { vi } from 'vitest'; // Consolidated Mock Factories export const MockFactories = { // Audio Context Mock Factory createAudioContextMock: (options = {}) => { const mockAudioContext = { createScriptProcessor: vi.fn().mockReturnValue({ connect: vi.fn(), disconnect: vi.fn(), onaudioprocess: null, }), createAnalyser: vi.fn().mockReturnValue({ connect: vi.fn(), disconnect: vi.fn(), frequencyBinCount: 1024, getByteFrequencyData: vi.fn(), getByteTimeDomainData: vi.fn(), }), createGain: vi.fn().mockReturnValue({ connect: vi.fn(), disconnect: vi.fn(), gain: { value: 1 }, }), createBiquadFilter: vi.fn().mockReturnValue({ connect: vi.fn(), disconnect: vi.fn(), frequency: { value: 440 }, Q: { value: 1 }, type: 'lowpass', }), destination: { connect: vi.fn(), disconnect: vi.fn(), }, sampleRate: 44100, currentTime: 0, state: 'running', suspend: vi.fn(), resume: vi.fn(), close: vi.fn(), ...options, }; return mockAudioContext; }, // MediaRecorder Mock Factory createMediaRecorderMock: (options = {}) => { const mockMediaRecorder = { start: vi.fn(), stop: vi.fn(), pause: vi.fn(), resume: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), state: 'inactive', mimeType: 'audio/webm', ondataavailable: null, onerror: null, onpause: null, onresume: null, onstart: null, onstop: null, ...options, }; return mockMediaRecorder; }, // MediaStream Mock Factory createMediaStreamMock: (tracks = []) => { const mockStream = { id: 'mock-stream-id', active: true, getTracks: vi.fn().mockReturnValue(tracks), getAudioTracks: vi.fn().mockReturnValue(tracks.filter(t => t.kind === 'audio')), getVideoTracks: vi.fn().mockReturnValue(tracks.filter(t => t.kind === 'video')), addTrack: vi.fn(), removeTrack: vi.fn(), clone: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), }; return mockStream; }, // AudioBuffer Mock Factory createAudioBufferMock: (channels = 2, length = 1024, sampleRate = 44100) => { const mockBuffer = { sampleRate, length, duration: length / sampleRate, numberOfChannels: channels, getChannelData: vi.fn().mockImplementation((channel) => { return new Float32Array(length).fill(0.1); }), copyFromChannel: vi.fn(), copyToChannel: vi.fn(), }; return mockBuffer; }, // WASM Module Mock Factory createWASMMock: () => ({ RNNoiseProcessor: vi.fn().mockImplementation(() => ({ process: vi.fn().mockReturnValue(new Float32Array(1024)), setGainControl: vi.fn(), destroy: vi.fn(), })), instance: { exports: { memory: new WebAssembly.Memory({ initial: 1 }), malloc: vi.fn().mockReturnValue(0), free: vi.fn(), }, }, }), }; // Console Management Utilities export class ConsoleManager { constructor() { this.logs = []; this.errors = []; this.warnings = []; this.originalConsole = { ...console }; } startCapture(options = {}) { const { silent = false, captureErrors = true } = options; console.log = silent ? vi.fn() : vi.fn().mockImplementation((...args) => { this.logs.push(args.join(' ')); this.originalConsole.log(...args); }); console.error = captureErrors ? vi.fn().mockImplementation((...args) => { this.errors.push(args.join(' ')); if (!silent) this.originalConsole.error(...args); }) : this.originalConsole.error; console.warn = vi.fn().mockImplementation((...args) => { this.warnings.push(args.join(' ')); if (!silent) this.originalConsole.warn(...args); }); } stopCapture() { Object.assign(console, this.originalConsole); } getLogs() { return [...this.logs]; } getErrors() { return [...this.errors]; } getWarnings() { return [...this.warnings]; } clear() { this.logs = []; this.errors = []; this.warnings = []; } hasLogContaining(text) { return this.logs.some(log => log.includes(text)); } hasErrorContaining(text) { return this.errors.some(error => error.includes(text)); } } // Test Environment Setup export class TestEnvironment { constructor() { this.consoleManager = new ConsoleManager(); this.mocks = {}; this.cleanupFunctions = []; } setup(config = {}) { // Setup console management if (config.console) { this.consoleManager.startCapture(config.console); this.cleanupFunctions.push(() => this.consoleManager.stopCapture()); } // Setup Audio Context if (config.audio) { const mockAudioContext = MockFactories.createAudioContextMock(); global.AudioContext = vi.fn().mockImplementation(() => mockAudioContext); global.webkitAudioContext = vi.fn().mockImplementation(() => mockAudioContext); this.mocks.audioContext = mockAudioContext; this.cleanupFunctions.push(() => { delete globalThis.AudioContext; delete globalThis.webkitAudioContext; }); } // Setup MediaRecorder if (config.mediaRecorder) { const mockMediaRecorder = MockFactories.createMediaRecorderMock(); global.MediaRecorder = vi.fn().mockImplementation(() => mockMediaRecorder); global.MediaRecorder.isTypeSupported = vi.fn().mockReturnValue(true); this.mocks.mediaRecorder = mockMediaRecorder; this.cleanupFunctions.push(() => { delete globalThis.MediaRecorder; }); } // Setup WASM if (config.wasm) { this.mocks.wasm = MockFactories.createWASMMock(); } return this; } get console() { return this.consoleManager; } get audioContext() { return this.mocks.audioContext; } get mediaRecorder() { return this.mocks.mediaRecorder; } get wasm() { return this.mocks.wasm; } cleanup() { this.cleanupFunctions.forEach(fn => fn()); this.cleanupFunctions = []; vi.clearAllMocks(); } } // Utility Functions export const TestUtils = { // Wait for async operations waitFor: (ms) => new Promise(resolve => setTimeout(resolve, ms)), // Wait for condition to be true waitForCondition: async (condition, timeout = 5000) => { const start = Date.now(); while (!condition() && Date.now() - start < timeout) { await TestUtils.waitFor(10); } if (!condition()) { throw new Error(`Condition not met within ${timeout}ms`); } }, // Create test audio data createTestAudioData: (length = 1024, frequency = 440, sampleRate = 44100) => { const data = new Float32Array(length); for (let i = 0; i < length; i++) { data[i] = Math.sin(2 * Math.PI * frequency * i / sampleRate) * 0.5; } return data; }, // Simulate file drop event createDropEvent: (files) => { const event = new Event('drop'); event.dataTransfer = { files, items: files.map(file => ({ getAsFile: () => file })), }; return event; }, // Common assertion helpers expectNoConsoleErrors: (consoleManager) => { const errors = consoleManager.getErrors(); if (errors.length > 0) { throw new Error(`Unexpected console errors: ${errors.join(', ')}`); } }, expectConsoleLogContaining: (consoleManager, text) => { if (!consoleManager.hasLogContaining(text)) { throw new Error(`Expected console log containing "${text}", but found: ${consoleManager.getLogs().join(', ')}`); } }, }; // Export commonly used combinations export const createTestEnvironment = (config) => { const env = new TestEnvironment(); return env.setup(config); }; export const createAudioTestEnvironment = () => { return createTestEnvironment({ audio: true, mediaRecorder: true, console: { silent: true }, }); }; export const createFullTestEnvironment = () => { return createTestEnvironment({ audio: true, mediaRecorder: true, console: { captureErrors: true }, wasm: true, }); };