// Simple tests that don't require DOM or external dependencies import { promises as fs } from 'fs'; import path from 'path'; const srcDir = './src'; async function runSimpleTests() { console.log('๐Ÿงช Running Simple Tests\n'); let tests = 0; let passed = 0; let failed = 0; function test(description, testFn) { tests++; try { testFn(); console.log(`โœ… ${description}`); passed++; } catch (error) { console.error(`โŒ ${description}`); console.error(` Error: ${error.message}`); failed++; } } function assert(condition, message) { if (!condition) { throw new Error(message); } } // Test 1: Core files exist test('Core JavaScript files exist', async () => { const coreFiles = [ 'ui.js', 'translation.js', 'tts.js', 'chat.js', 'config.js', 'widget-library.js', 'tts-modal.js', 'translate-modal.js', 'uncloseai-embed-modal.js' ]; for (const file of coreFiles) { const filePath = path.join(srcDir, file); try { await fs.access(filePath); } catch (error) { throw new Error(`File ${file} does not exist`); } } }); // Test 2: Language files exist test('Language files structure is correct', async () => { const langDir = path.join(srcDir, 'languages'); const files = await fs.readdir(langDir); const jsFiles = files.filter(f => f.endsWith('.js')); assert(jsFiles.length >= 18, `Expected at least 18 language files, found ${jsFiles.length}`); assert(jsFiles.includes('index.js'), 'Missing languages/index.js'); assert(jsFiles.includes('en.js'), 'Missing languages/en.js'); }); // Test 3: File sizes are reasonable test('UI.js size is reasonable after refactoring', async () => { const content = await fs.readFile(path.join(srcDir, 'ui.js'), 'utf-8'); const lines = content.split('\n').length; assert(lines < 600, `UI.js is too large: ${lines} lines (expected < 600)`); assert(lines > 100, `UI.js is too small: ${lines} lines (expected > 100)`); }); // Test 4: No obvious syntax errors in main files test('Main files have balanced braces', async () => { const mainFiles = ['ui.js', 'translation.js', 'tts.js', 'chat.js']; for (const file of mainFiles) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); const openBraces = (content.match(/{/g) || []).length; const closeBraces = (content.match(/}/g) || []).length; assert(openBraces === closeBraces, `${file} has mismatched braces: ${openBraces} open, ${closeBraces} close`); } }); // Test 5: Export statements exist test('Key files have export statements', async () => { const filesWithExports = ['ui.js', 'translation.js', 'tts.js', 'widget-library.js']; for (const file of filesWithExports) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); assert(content.includes('export'), `${file} has no export statements`); } }); // Test 6: Import statements are valid test('Import statements use correct syntax', async () => { const allFiles = await fs.readdir(srcDir); const jsFiles = allFiles.filter(f => f.endsWith('.js')); for (const file of jsFiles) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); // Check for balanced braces in import statements (basic check) const importMatches = content.match(/import\s*{[^}]*}/g) || []; for (const importMatch of importMatches) { const openBraces = (importMatch.match(/{/g) || []).length; const closeBraces = (importMatch.match(/}/g) || []).length; assert(openBraces === closeBraces, `${file}: Unbalanced braces in import: ${importMatch.substring(0, 50)}...`); } // Check that imports with 'from' have quotes const fromMatches = content.match(/import.*from\s+[^'"]/g) || []; if (fromMatches.length > 0) { console.warn(`โš ๏ธ ${file} may have unquoted import sources`); } } }); // Test 7: Function definitions look correct test('Function definitions have basic structure', async () => { const mainFiles = ['ui.js', 'translation.js', 'tts.js', 'chat.js']; for (const file of mainFiles) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); // Check for function definitions const functionCount = (content.match(/function\s+\w+/g) || []).length; const arrowFunctionCount = (content.match(/=\s*\([^)]*\)\s*=>/g) || []).length; const asyncFunctionCount = (content.match(/async\s+function/g) || []).length; const totalFunctions = functionCount + arrowFunctionCount + asyncFunctionCount; assert(totalFunctions > 0, `${file} has no function definitions`); } }); // Test 8: No obvious console.log statements in production code test('No debug console.log statements in main files', async () => { const mainFiles = ['ui.js', 'translation.js', 'tts.js']; for (const file of mainFiles) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); const consoleLogCount = (content.match(/console\.log\s*\(/g) || []).length; // Allow some console logs but warn if too many if (consoleLogCount > 3) { console.warn(`โš ๏ธ ${file} has ${consoleLogCount} console.log statements (consider removing for production)`); } } }); // Test 9: Language files have proper structure test('Language files export correct structure', async () => { const langDir = path.join(srcDir, 'languages'); const files = await fs.readdir(langDir); const langFiles = files.filter(f => f.endsWith('.js') && f !== 'index.js' && !f.includes('verify') && !f.includes('add-missing')); for (const file of langFiles) { const content = await fs.readFile(path.join(langDir, file), 'utf-8'); // Check for export statement assert(content.includes('export const'), `${file} missing export const statement`); // Check for object structure const objectPattern = /export\s+const\s+\w+\s*=\s*{/; assert(objectPattern.test(content), `${file} export is not a proper object`); // Check for translation keys (basic validation) const keyCount = (content.match(/\w+:\s*['"]/g) || []).length; assert(keyCount > 10, `${file} has too few translation keys: ${keyCount}`); } }); // Test 10: Config files have required sections test('Config file has required structure', async () => { const content = await fs.readFile(path.join(srcDir, 'config.js'), 'utf-8'); // Check for key config sections assert(content.includes('API_URL') || content.includes('CONFIG') || content.includes('SYSTEM_MESSAGE'), 'config.js missing main configuration exports'); assert(content.includes('url') || content.includes('URL') || content.includes('endpoint'), 'config.js missing URL/endpoint configuration'); }); // Test 11: Modal files have required functions test('Modal files export required functions', async () => { const modalFiles = ['tts-modal.js', 'translate-modal.js', 'uncloseai-embed-modal.js']; for (const file of modalFiles) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); // Check for main function export assert(content.includes('export'), `${file} has no exports`); // Check for modal-specific patterns if (file.includes('modal')) { assert(content.includes('modal') || content.includes('Modal'), `${file} doesn't contain modal-related code`); } } }); // Test 12: Check for potential security issues test('No obvious security vulnerabilities', async () => { const allFiles = await fs.readdir(srcDir); const jsFiles = allFiles.filter(f => f.endsWith('.js')); for (const file of jsFiles) { const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); // Check for innerHTML usage (potential XSS) const innerHTMLCount = (content.match(/\.innerHTML\s*=/g) || []).length; if (innerHTMLCount > 0) { console.warn(`โš ๏ธ ${file} uses innerHTML (${innerHTMLCount} times) - ensure proper sanitization`); } // Check for eval usage assert(!content.includes('eval('), `${file} contains eval() - security risk`); // Check for document.write (but allow in specific cases like new windows) const documentWriteMatches = content.match(/document\.write/g) || []; if (documentWriteMatches.length > 0 && !content.includes('previewWindow') && !content.includes('newWindow')) { assert(false, `${file} contains document.write - security/performance risk`); } } }); // Test 13: File encoding and line endings test('Files have consistent encoding', async () => { const allFiles = await fs.readdir(srcDir); const jsFiles = allFiles.filter(f => f.endsWith('.js')); for (const file of jsFiles) { const buffer = await fs.readFile(path.join(srcDir, file)); const content = buffer.toString('utf-8'); // Check for BOM (Byte Order Mark) assert(!content.startsWith('\uFEFF'), `${file} contains BOM - should be removed`); // Check for non-ASCII characters that might cause issues const hasNonASCII = /[^\x00-\x7F]/.test(content); if (hasNonASCII && !file.includes('languages/')) { console.warn(`โš ๏ธ ${file} contains non-ASCII characters (may be intentional)`); } } }); // Wait for async tests to complete await new Promise(resolve => setTimeout(resolve, 100)); console.log('\n๐Ÿ“Š Simple Test Results:'); console.log('='.repeat(30)); console.log(`Total tests: ${tests}`); console.log(`Passed: ${passed} โœ…`); console.log(`Failed: ${failed} โŒ`); if (failed === 0) { console.log('\n๐ŸŽ‰ All simple tests passed!'); process.exit(0); } else { console.log('\n๐Ÿ’ฅ Some tests failed.'); process.exit(1); } } runSimpleTests().catch(console.error);