266 lines
No EOL
11 KiB
JavaScript
266 lines
No EOL
11 KiB
JavaScript
// 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); |