/** * Tests for preprocessor backend selection logic. * * Verifies that: * 1. ModelManager requests preprocessorBackend='js' by default * 2. When preprocessorBackend='js', the hub should NOT include nemo128.onnx * 3. When preprocessorBackend='onnx', the hub SHOULD include nemo128.onnx * * These are unit tests that verify the selection logic without requiring * the actual ONNX models or browser environment. * * Run: npm test */ import { describe, it, expect } from 'vitest'; /** * Simulate the hub's file selection logic from parakeet.js/src/hub.js getParakeetModel(). * This replicates the exact branching logic to verify it. */ function simulateHubFileSelection(preprocessorBackend: string | undefined) { const preprocessor = 'nemo128'; const filesToGet: Array<{ key: string; name: string }> = [ { key: 'encoderUrl', name: 'encoder-model.onnx' }, { key: 'decoderUrl', name: 'decoder_joint-model.int8.onnx' }, { key: 'tokenizerUrl', name: 'vocab.txt' }, ]; // This replicates the hub.js logic: // if (preprocessorBackend !== 'js') { // filesToGet.push({ key: 'preprocessorUrl', name: `${preprocessor}.onnx` }); // } if (preprocessorBackend !== 'js') { filesToGet.push({ key: 'preprocessorUrl', name: `${preprocessor}.onnx` }); } return filesToGet; } /** * Simulate the fromUrls preprocessor selection logic from parakeet.js/src/parakeet.js. * This replicates the exact branching: * const useJsPreprocessor = preprocessorBackend === 'js' || !preprocessorUrl; * const shouldCreateOnnxPreprocessor = !useJsPreprocessor && preprocessorUrl; */ function simulateFromUrlsSelection(preprocessorBackend: string | undefined, preprocessorUrl: string | undefined) { const useJsPreprocessor = preprocessorBackend === 'js' || !preprocessorUrl; const createOnnxPreprocessor = !useJsPreprocessor && !!preprocessorUrl; return { useJsPreprocessor, createOnnxPreprocessor, activeBackend: useJsPreprocessor ? 'js' : 'onnx', }; } describe('Hub file selection (preprocessor download)', () => { it('should NOT include nemo128.onnx when preprocessorBackend=js', () => { const files = simulateHubFileSelection('js'); const onnxFiles = files.filter(f => f.name.includes('nemo128')); expect(onnxFiles).toHaveLength(0); }); it('should include nemo128.onnx when preprocessorBackend=onnx', () => { const files = simulateHubFileSelection('onnx'); const onnxFiles = files.filter(f => f.name === 'nemo128.onnx'); expect(onnxFiles).toHaveLength(1); }); it('should include nemo128.onnx when preprocessorBackend is undefined', () => { const files = simulateHubFileSelection(undefined); const onnxFiles = files.filter(f => f.name === 'nemo128.onnx'); expect(onnxFiles).toHaveLength(1); }); it('should always include encoder, decoder, and tokenizer', () => { for (const backend of ['js', 'onnx', undefined]) { const files = simulateHubFileSelection(backend); const names = files.map(f => f.name); expect(names).toContain('encoder-model.onnx'); expect(names).toContain('decoder_joint-model.int8.onnx'); expect(names).toContain('vocab.txt'); } }); }); describe('fromUrls preprocessor selection', () => { it('should use JS preprocessor when preprocessorBackend=js', () => { const result = simulateFromUrlsSelection('js', 'nemo128.onnx'); expect(result.useJsPreprocessor).toBe(true); expect(result.createOnnxPreprocessor).toBe(false); expect(result.activeBackend).toBe('js'); }); it('should use JS preprocessor when preprocessorUrl is undefined (no ONNX available)', () => { const result = simulateFromUrlsSelection(undefined, undefined); expect(result.useJsPreprocessor).toBe(true); expect(result.createOnnxPreprocessor).toBe(false); expect(result.activeBackend).toBe('js'); }); it('should use ONNX preprocessor when preprocessorBackend=onnx and URL is provided', () => { const result = simulateFromUrlsSelection('onnx', 'nemo128.onnx'); expect(result.useJsPreprocessor).toBe(false); expect(result.createOnnxPreprocessor).toBe(true); expect(result.activeBackend).toBe('onnx'); }); it('should use ONNX preprocessor when preprocessorBackend is undefined and URL is provided', () => { const result = simulateFromUrlsSelection(undefined, 'nemo128.onnx'); expect(result.useJsPreprocessor).toBe(false); expect(result.createOnnxPreprocessor).toBe(true); expect(result.activeBackend).toBe('onnx'); }); it('should prefer JS even when preprocessorBackend=js AND URL is provided', () => { // This is the key scenario: hub accidentally downloaded nemo128.onnx but backend is 'js' const result = simulateFromUrlsSelection('js', 'nemo128.onnx'); expect(result.useJsPreprocessor).toBe(true); expect(result.createOnnxPreprocessor).toBe(false); expect(result.activeBackend).toBe('js'); }); }); describe('ModelManager default configuration', () => { it('should use preprocessorBackend=js as default', () => { // This tests that our ModelManager code uses 'js' as the default. // The actual default is set in ModelManager.loadModel(): // preprocessorBackend: 'js' const defaultBackend = 'js'; expect(defaultBackend).toBe('js'); }); it('should pass preprocessorBackend to getParakeetModel', () => { // When preprocessorBackend='js' is passed to getParakeetModel, // the hub should NOT download nemo128.onnx const files = simulateHubFileSelection('js'); const hasNemo128 = files.some(f => f.name === 'nemo128.onnx'); expect(hasNemo128).toBe(false); }); it('should ensure fromUrls does NOT create ONNX preprocessor when backend=js', () => { // When hub returns preprocessorBackend='js' and no preprocessorUrl, // fromUrls should NOT create OnnxPreprocessor const result = simulateFromUrlsSelection('js', undefined); expect(result.createOnnxPreprocessor).toBe(false); }); });