diff --git a/integration_tests.js b/integration_tests.js index 97e24af..2fe719b 100755 --- a/integration_tests.js +++ b/integration_tests.js @@ -582,6 +582,157 @@ test('DOM manipulation is safe', () => { assert(testElement.style.position === 'relative', 'Style setting failed'); }); +// ===== SECTION 9: MODAL-SPECIFIC ERROR REGRESSION TESTS ===== +console.log('\n🎭 Section 9: Modal Error Regression Tests'); + +test('Modal import functions are properly defined', () => { + const modalContent = readFile('uncloseai-embed-modal.js'); + + // Check that all required import wrapper functions exist + const requiredFunctions = [ + 'async function sendMessageWithCustomHistory', + 'async function fetchModelsFromEndpoints', + 'async function loadConversationHistory', + 'async function clearConversationHistory', + 'async function saveConversationHistory', + 'async function getChatHistory', + 'async function updateChatHistory', + 'async function getSelectedModel' + ]; + + requiredFunctions.forEach(func => { + assert(modalContent.includes(func), `Modal missing function: ${func}`); + }); +}); + +test('Modal imports target correct modules', () => { + const modalContent = readFile('uncloseai-embed-modal.js'); + + // Test correct import paths for frequently broken functions + const correctImports = [ + 'await import("./models.js").*fetchModelsFromEndpoints', + 'await import("./storage.js").*loadConversationHistory', + 'await import("./storage.js").*clearConversationHistory', + 'await import("./storage.js").*saveConversationHistory', + 'await import("./models.js").*getSelectedModel', + 'await import("./chat.js").*sendMessageWithCustomHistory' + ]; + + correctImports.forEach(importPattern => { + const regex = new RegExp(importPattern, 'g'); + assert(regex.test(modalContent), `Modal missing correct import: ${importPattern}`); + }); +}); + +test('Modal generator function wrapper is correct', () => { + const modalContent = readFile('uncloseai-embed-modal.js'); + + // Check that sendMessageWithCustomHistory is properly wrapped as generator + assert( + modalContent.includes('async function* sendMessageWithCustomHistory'), + 'sendMessageWithCustomHistory should be async generator function' + ); + + assert( + modalContent.includes('yield* sendMessageWithCustomHistory(history)'), + 'sendMessageWithCustomHistory should use yield* to delegate to imported generator' + ); +}); + +test('Window global functions are exported from ui.js', () => { + const uiContent = readFile('ui.js'); + + // Check that window globals are properly exported to fix modal button errors + const requiredGlobals = [ + 'window.openTTSModal', + 'window.openTranslateModal', + 'window.toggleUncloseaiEmbeddedModal' + ]; + + requiredGlobals.forEach(global => { + assert(uiContent.includes(global), `ui.js missing global export: ${global}`); + }); +}); + +test('Modal chat history handling is safe', () => { + const modalContent = readFile('uncloseai-embed-modal.js'); + + // Verify that direct chatHistory access is replaced with function calls + const lines = modalContent.split('\n'); + let directChatHistoryAccess = false; + + lines.forEach((line, index) => { + // Look for direct chatHistory usage (not in comments) + if (line.includes('chatHistory') && + !line.trim().startsWith('//') && + !line.includes('getChatHistory') && + !line.includes('updateChatHistory') && + !line.includes('const') && + !line.includes('function')) { + + // Allow some specific safe patterns + if (!line.includes('chatHistory.find') || !line.includes('await getChatHistory()')) { + directChatHistoryAccess = true; + console.log(` Direct chatHistory access found at line ${index + 1}: ${line.trim()}`); + } + } + }); + + assert(!directChatHistoryAccess, 'Modal should not directly access chatHistory variable'); +}); + +test('Modal error handling patterns are implemented', () => { + const modalContent = readFile('uncloseai-embed-modal.js'); + + // Check for proper error handling in async functions + const asyncFunctionPattern = /async function[^{]*{[^}]*}/g; + const matches = modalContent.match(asyncFunctionPattern); + + if (matches) { + // At least some async functions should have try-catch or error handling + const hasTryCatch = modalContent.includes('try {') || modalContent.includes('catch'); + const hasErrorLog = modalContent.includes('console.error') || modalContent.includes('console.warn'); + + assert( + hasTryCatch || hasErrorLog, + 'Modal should have error handling in async functions' + ); + } +}); + +test('Modal function exports match their imports', () => { + const files = ['chat.js', 'storage.js', 'models.js', 'tts.js']; + const modalContent = readFile('uncloseai-embed-modal.js'); + + files.forEach(file => { + const fileContent = readFile(file); + const exports = extractExports(fileContent); + + // Check that functions imported by modal are actually exported + exports.forEach(exportedFunc => { + if (modalContent.includes(`${exportedFunc}`)) { + const importPattern = new RegExp(`await import\\("\\.\/${file}"\\)[^}]*${exportedFunc}`, 'g'); + if (importPattern.test(modalContent)) { + assert(true, `${file} exports ${exportedFunc} which modal imports`); + } + } + }); + }); +}); + +test('Modal prevents circular dependency with chat.js', () => { + const modalContent = readFile('uncloseai-embed-modal.js'); + const chatContent = readFile('chat.js'); + + // Modal should only import from chat.js, not vice versa + const chatImportsModal = chatContent.includes('./uncloseai-embed-modal.js'); + assert(!chatImportsModal, 'chat.js should not import from modal to prevent circular dependency'); + + // Modal should use dynamic imports from chat.js + const modalImportsChat = modalContent.includes('await import("./chat.js")'); + assert(modalImportsChat, 'Modal should use dynamic imports from chat.js'); +}); + // Cleanup dom.window.close(); diff --git a/src/uncloseai-embed-modal.js b/src/uncloseai-embed-modal.js index e5e24e8..1ec6def 100644 --- a/src/uncloseai-embed-modal.js +++ b/src/uncloseai-embed-modal.js @@ -56,7 +56,7 @@ async function loadConversationHistory() { } async function saveConversationHistory(history) { - const { saveConversationHistory } = await import("./chat.js"); + const { saveConversationHistory } = await import("./storage.js"); return saveConversationHistory(history); }