modified: .gitignore
deleted: add_remote_url_keys.js deleted: build-all.log deleted: modal_tests.js deleted: simple_tests.js deleted: uncloseai.js.orig
This commit is contained in:
parent
4c07f47f47
commit
b41c10d2e6
6 changed files with 2 additions and 4980 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -6,6 +6,8 @@ node_modules/
|
|||
webwords/
|
||||
book/
|
||||
|
||||
*.log
|
||||
|
||||
# Generated audio files in language examples
|
||||
public/languages/**/*.mp3
|
||||
*.mp3
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Script to add missing remote URL translation keys to all language files
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const languagesDir = './src/languages';
|
||||
|
||||
// The new keys to add in English
|
||||
const newKeys = {
|
||||
remoteUrlTab: "🌐 Remote URL",
|
||||
remoteUrlInfo: "Enter a URL to fetch and translate:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Fetch & Translate"
|
||||
};
|
||||
|
||||
// Translations for each language
|
||||
const translations = {
|
||||
ar: {
|
||||
remoteUrlTab: "🌐 رابط بعيد",
|
||||
remoteUrlInfo: "أدخل رابط لجلبه وترجمته:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 جلب وترجمة"
|
||||
},
|
||||
bn: {
|
||||
remoteUrlTab: "🌐 দূরবর্তী URL",
|
||||
remoteUrlInfo: "একটি URL প্রবেশ করান যা আনতে এবং অনুবাদ করতে:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 আনুন এবং অনুবাদ করুন"
|
||||
},
|
||||
de: {
|
||||
remoteUrlTab: "🌐 Externe URL",
|
||||
remoteUrlInfo: "Geben Sie eine URL ein, um sie abzurufen und zu übersetzen:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Abrufen & Übersetzen"
|
||||
},
|
||||
es: {
|
||||
remoteUrlTab: "🌐 URL Remota",
|
||||
remoteUrlInfo: "Ingresa una URL para obtener y traducir:",
|
||||
remoteUrlPlaceholder: "https://ejemplo.com",
|
||||
fetchAndTranslate: "🌐 Obtener y Traducir"
|
||||
},
|
||||
fr: {
|
||||
remoteUrlTab: "🌐 URL Distante",
|
||||
remoteUrlInfo: "Entrez une URL à récupérer et traduire:",
|
||||
remoteUrlPlaceholder: "https://exemple.com",
|
||||
fetchAndTranslate: "🌐 Récupérer et Traduire"
|
||||
},
|
||||
hi: {
|
||||
remoteUrlTab: "🌐 दूरस्थ URL",
|
||||
remoteUrlInfo: "लाने और अनुवाद करने के लिए एक URL दर्ज करें:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 लाएं और अनुवाद करें"
|
||||
},
|
||||
id: {
|
||||
remoteUrlTab: "🌐 URL Jarak Jauh",
|
||||
remoteUrlInfo: "Masukkan URL untuk diambil dan diterjemahkan:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Ambil & Terjemahkan"
|
||||
},
|
||||
ja: {
|
||||
remoteUrlTab: "🌐 リモートURL",
|
||||
remoteUrlInfo: "取得して翻訳するURLを入力してください:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 取得して翻訳"
|
||||
},
|
||||
ko: {
|
||||
remoteUrlTab: "🌐 원격 URL",
|
||||
remoteUrlInfo: "가져와서 번역할 URL을 입력하세요:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 가져와서 번역"
|
||||
},
|
||||
mr: {
|
||||
remoteUrlTab: "🌐 दूरस्थ URL",
|
||||
remoteUrlInfo: "आणण्यासाठी आणि भाषांतर करण्यासाठी URL प्रविष्ट करा:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 आणा आणि भाषांतर करा"
|
||||
},
|
||||
pt: {
|
||||
remoteUrlTab: "🌐 URL Remota",
|
||||
remoteUrlInfo: "Digite uma URL para buscar e traduzir:",
|
||||
remoteUrlPlaceholder: "https://exemplo.com",
|
||||
fetchAndTranslate: "🌐 Buscar e Traduzir"
|
||||
},
|
||||
ru: {
|
||||
remoteUrlTab: "🌐 Удаленный URL",
|
||||
remoteUrlInfo: "Введите URL для загрузки и перевода:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Загрузить и Перевести"
|
||||
},
|
||||
sw: {
|
||||
remoteUrlTab: "🌐 URL ya Mbali",
|
||||
remoteUrlInfo: "Ingiza URL ya kuleta na kutafsiri:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Leta na Tafsiri"
|
||||
},
|
||||
te: {
|
||||
remoteUrlTab: "🌐 రిమోట్ URL",
|
||||
remoteUrlInfo: "తీసుకురావడానికి మరియు అనువదించడానికి URL ని నమోదు చేయండి:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 తీసుకురండి & అనువదించండి"
|
||||
},
|
||||
tr: {
|
||||
remoteUrlTab: "🌐 Uzak URL",
|
||||
remoteUrlInfo: "Getirmek ve çevirmek için bir URL girin:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 Getir ve Çevir"
|
||||
},
|
||||
ur: {
|
||||
remoteUrlTab: "🌐 ریموٹ URL",
|
||||
remoteUrlInfo: "لانے اور ترجمہ کرنے کے لیے URL داخل کریں:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 لائیں اور ترجمہ کریں"
|
||||
},
|
||||
"zh-tw": {
|
||||
remoteUrlTab: "🌐 遠端網址",
|
||||
remoteUrlInfo: "輸入要擷取和翻譯的網址:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 擷取並翻譯"
|
||||
},
|
||||
zh: {
|
||||
remoteUrlTab: "🌐 远程网址",
|
||||
remoteUrlInfo: "输入要获取和翻译的网址:",
|
||||
remoteUrlPlaceholder: "https://example.com",
|
||||
fetchAndTranslate: "🌐 获取并翻译"
|
||||
}
|
||||
};
|
||||
|
||||
// Process each language file
|
||||
for (const [langCode, langTranslations] of Object.entries(translations)) {
|
||||
const filePath = path.join(languagesDir, `${langCode}.js`);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Find the position to insert new keys (before the closing })
|
||||
const insertPosition = content.lastIndexOf('};');
|
||||
|
||||
if (insertPosition === -1) {
|
||||
console.error(`Could not find export structure in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the last non-empty line before the closing }
|
||||
const beforeClosing = content.substring(0, insertPosition).trimEnd();
|
||||
|
||||
// Check if any of the new keys already exist
|
||||
const hasNewKeys = Object.keys(langTranslations).some(key =>
|
||||
content.includes(`${key}:`));
|
||||
|
||||
if (hasNewKeys) {
|
||||
console.log(`✅ ${langCode}: Keys already present, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build the new keys string
|
||||
const newKeysString = Object.entries(langTranslations)
|
||||
.map(([key, value]) => `\t${key}: "${value}",`)
|
||||
.join('\n');
|
||||
|
||||
// Insert the new keys
|
||||
const newContent = beforeClosing + ',\n' + newKeysString + '\n};';
|
||||
|
||||
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||
console.log(`✅ ${langCode}: Added remote URL translation keys`);
|
||||
} else {
|
||||
console.error(`File not found: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n🎉 Finished adding remote URL translation keys to all language files!');
|
||||
1455
build-all.log
1455
build-all.log
File diff suppressed because it is too large
Load diff
376
modal_tests.js
376
modal_tests.js
|
|
@ -1,376 +0,0 @@
|
|||
// Custom Modal Test Suite
|
||||
// Tests modal functionality using JSDOM without Jest complexity
|
||||
// Run with: node modal_tests.js
|
||||
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Test suite state
|
||||
let testCount = 0;
|
||||
let passedCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
// Simple test framework
|
||||
function test(description, testFn) {
|
||||
testCount++;
|
||||
console.log(`\n🧪 Test: ${description}`);
|
||||
try {
|
||||
testFn();
|
||||
console.log(`✅ PASS`);
|
||||
passedCount++;
|
||||
} catch (error) {
|
||||
console.log(`❌ FAIL: ${error.message}`);
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(`${message} - Expected: ${expected}, Got: ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup mock browser environment
|
||||
function setupBrowserMocks() {
|
||||
const dom = new JSDOM(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Modal Test Page</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>`, {
|
||||
url: "https://ai.unturf.com",
|
||||
pretendToBeVisual: true,
|
||||
resources: "usable"
|
||||
});
|
||||
|
||||
// Set up globals safely for Node.js v22
|
||||
Object.defineProperty(global, 'window', { value: dom.window, configurable: true });
|
||||
Object.defineProperty(global, 'document', { value: dom.window.document, configurable: true });
|
||||
Object.defineProperty(global, 'navigator', { value: dom.window.navigator, configurable: true });
|
||||
Object.defineProperty(global, 'HTMLElement', { value: dom.window.HTMLElement, configurable: true });
|
||||
Object.defineProperty(global, 'Element', { value: dom.window.Element, configurable: true });
|
||||
// Polyfill HTMLDialogElement if not available
|
||||
if (!dom.window.HTMLDialogElement) {
|
||||
dom.window.HTMLDialogElement = class HTMLDialogElement extends dom.window.HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
showModal() {
|
||||
this.open = true;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.open = true;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
Object.defineProperty(global, 'HTMLDialogElement', { value: dom.window.HTMLDialogElement, configurable: true });
|
||||
|
||||
// Add Event constructor polyfill
|
||||
if (!global.Event) {
|
||||
global.Event = dom.window.Event;
|
||||
}
|
||||
if (!global.MouseEvent) {
|
||||
global.MouseEvent = dom.window.MouseEvent;
|
||||
}
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = async (url, options) => {
|
||||
// Mock model API responses
|
||||
if (url.includes('/v1/models')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
data: [
|
||||
{ id: 'gpt-4o', object: 'model' },
|
||||
{ id: 'claude-3-sonnet', object: 'model' }
|
||||
]
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Mock chat completion responses
|
||||
if (url.includes('/v1/chat/completions')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
choices: [{ message: { content: 'Test response' } }]
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
return { ok: false, status: 404 };
|
||||
};
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = {
|
||||
getItem: (key) => localStorageMock.data[key] || null,
|
||||
setItem: (key, value) => { localStorageMock.data[key] = value; },
|
||||
removeItem: (key) => { delete localStorageMock.data[key]; },
|
||||
clear: () => { localStorageMock.data = {}; },
|
||||
data: {}
|
||||
};
|
||||
global.localStorage = localStorageMock;
|
||||
|
||||
// Mock other browser APIs
|
||||
global.Audio = class MockAudio {
|
||||
constructor(src) { this.src = src; }
|
||||
play() { return Promise.resolve(); }
|
||||
pause() {}
|
||||
};
|
||||
|
||||
global.speechSynthesis = {
|
||||
speak: () => {},
|
||||
cancel: () => {},
|
||||
getVoices: () => []
|
||||
};
|
||||
|
||||
return dom;
|
||||
}
|
||||
|
||||
// Mock external CDN dependencies
|
||||
function mockCDNDependencies() {
|
||||
// Mock marked
|
||||
global.marked = {
|
||||
parse: (text) => `<p>${text}</p>`,
|
||||
setOptions: () => {}
|
||||
};
|
||||
|
||||
// Mock highlight.js
|
||||
global.hljs = {
|
||||
highlightAuto: (code) => ({ value: code }),
|
||||
configure: () => {}
|
||||
};
|
||||
}
|
||||
|
||||
// Load source files for testing
|
||||
async function loadSourceFile(filename) {
|
||||
const filePath = join(__dirname, 'src', filename);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Replace CDN imports with our mocks
|
||||
const modifiedContent = content
|
||||
.replace(/import\s+\{[^}]+\}\s+from\s+["']https:\/\/cdn\.jsdelivr\.net\/npm\/marked\/lib\/marked\.esm\.js["']/g,
|
||||
'// Mock marked import')
|
||||
.replace(/import\s+\{[^}]+\}\s+from\s+["']https:\/\/cdnjs\.cloudflare\.com\/ajax\/libs\/highlight\.js\/[^"']+["']/g,
|
||||
'// Mock hljs import');
|
||||
|
||||
return modifiedContent;
|
||||
}
|
||||
|
||||
// Test modal creation and basic functionality
|
||||
async function testModalCreation() {
|
||||
test('Modal dialog element can be created', () => {
|
||||
// Create a regular div and enhance it with dialog-like properties
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'test-modal';
|
||||
modal.className = 'modal-dialog';
|
||||
|
||||
// Add dialog-like methods
|
||||
modal.open = false;
|
||||
modal.showModal = function() { this.open = true; this.style.display = 'block'; };
|
||||
modal.show = function() { this.open = true; this.style.display = 'block'; };
|
||||
modal.close = function() { this.open = false; this.style.display = 'none'; };
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
assert(typeof modal.showModal === 'function', 'Modal should have showModal method');
|
||||
assert(modal.id === 'test-modal', 'Modal should have correct ID');
|
||||
assert(document.getElementById('test-modal'), 'Modal should be in DOM');
|
||||
});
|
||||
|
||||
test('Modal can be opened and closed', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
|
||||
modal.showModal();
|
||||
assert(modal.open === true, 'Modal should be open after showModal()');
|
||||
|
||||
modal.close();
|
||||
assert(modal.open === false, 'Modal should be closed after close()');
|
||||
});
|
||||
}
|
||||
|
||||
// Test modal content injection
|
||||
async function testModalContent() {
|
||||
test('Modal content can be injected', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header">
|
||||
<h2>Test Modal</h2>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<p>Test content</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
assert(modal.querySelector('.modal-header'), 'Modal should have header');
|
||||
assert(modal.querySelector('.modal-content'), 'Modal should have content');
|
||||
assert(modal.querySelector('.close-btn'), 'Modal should have close button');
|
||||
});
|
||||
|
||||
test('Modal close button functionality', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
const closeBtn = modal.querySelector('.close-btn');
|
||||
|
||||
modal.showModal();
|
||||
assert(modal.open === true, 'Modal should be open');
|
||||
|
||||
// Simulate click on close button
|
||||
closeBtn.click();
|
||||
// Note: In real implementation, this would trigger close event
|
||||
modal.close(); // Manually close for test
|
||||
|
||||
assert(modal.open === false, 'Modal should close when close button clicked');
|
||||
});
|
||||
}
|
||||
|
||||
// Test modal form handling
|
||||
async function testModalForms() {
|
||||
test('Modal can contain form elements', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
|
||||
modal.innerHTML = `
|
||||
<form class="modal-form">
|
||||
<input type="text" id="test-input" placeholder="Enter text">
|
||||
<select id="test-select">
|
||||
<option value="option1">Option 1</option>
|
||||
<option value="option2">Option 2</option>
|
||||
</select>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
`;
|
||||
|
||||
const form = modal.querySelector('.modal-form');
|
||||
const input = modal.querySelector('#test-input');
|
||||
const select = modal.querySelector('#test-select');
|
||||
|
||||
assert(form, 'Modal should contain form');
|
||||
assert(input, 'Modal should contain input field');
|
||||
assert(select, 'Modal should contain select field');
|
||||
});
|
||||
|
||||
test('Modal form inputs can be manipulated', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
const input = modal.querySelector('#test-input');
|
||||
const select = modal.querySelector('#test-select');
|
||||
|
||||
input.value = 'Test value';
|
||||
select.value = 'option2';
|
||||
|
||||
assertEqual(input.value, 'Test value', 'Input value should be set');
|
||||
assertEqual(select.value, 'option2', 'Select value should be set');
|
||||
});
|
||||
}
|
||||
|
||||
// Test modal event handling
|
||||
async function testModalEvents() {
|
||||
test('Modal can handle custom events', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
let eventFired = false;
|
||||
|
||||
modal.addEventListener('custom-event', () => {
|
||||
eventFired = true;
|
||||
});
|
||||
|
||||
const customEvent = new window.Event('custom-event');
|
||||
modal.dispatchEvent(customEvent);
|
||||
|
||||
assert(eventFired, 'Custom event should fire');
|
||||
});
|
||||
|
||||
test('Modal click outside should work', () => {
|
||||
const modal = document.getElementById('test-modal');
|
||||
modal.showModal();
|
||||
|
||||
// Simulate click on backdrop (this would need proper implementation)
|
||||
const clickEvent = new MouseEvent('click', {
|
||||
clientX: 0,
|
||||
clientY: 0
|
||||
});
|
||||
|
||||
modal.dispatchEvent(clickEvent);
|
||||
// In real implementation, this would check if click was outside content
|
||||
assert(true, 'Click outside event handling works');
|
||||
});
|
||||
}
|
||||
|
||||
// Test localStorage integration
|
||||
async function testLocalStorageIntegration() {
|
||||
test('Modal can save state to localStorage', () => {
|
||||
const testData = { modalState: 'open', userPrefs: { theme: 'dark' } };
|
||||
|
||||
localStorage.setItem('modal-test', JSON.stringify(testData));
|
||||
const retrieved = JSON.parse(localStorage.getItem('modal-test'));
|
||||
|
||||
assertEqual(retrieved.modalState, 'open', 'localStorage should save modal state');
|
||||
assertEqual(retrieved.userPrefs.theme, 'dark', 'localStorage should save user preferences');
|
||||
});
|
||||
|
||||
test('Modal can load state from localStorage', () => {
|
||||
const savedData = localStorage.getItem('modal-test');
|
||||
assert(savedData, 'Data should be available in localStorage');
|
||||
|
||||
const parsed = JSON.parse(savedData);
|
||||
assert(parsed.modalState === 'open', 'Modal state should be restored');
|
||||
});
|
||||
}
|
||||
|
||||
// Main test runner
|
||||
async function runModalTests() {
|
||||
console.log('🚀 Starting Custom Modal Test Suite\n');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
// Setup environment
|
||||
setupBrowserMocks();
|
||||
mockCDNDependencies();
|
||||
|
||||
// Run test suites
|
||||
await testModalCreation();
|
||||
await testModalContent();
|
||||
await testModalForms();
|
||||
await testModalEvents();
|
||||
await testLocalStorageIntegration();
|
||||
|
||||
// Print results
|
||||
console.log('\n' + '=' .repeat(50));
|
||||
console.log('📊 Modal Test Results:');
|
||||
console.log(`Total tests: ${testCount}`);
|
||||
console.log(`Passed: ${passedCount} ✅`);
|
||||
console.log(`Failed: ${failedCount} ❌`);
|
||||
|
||||
if (failedCount === 0) {
|
||||
console.log('\n🎉 All modal tests passed!');
|
||||
} else {
|
||||
console.log('\n💥 Some modal tests failed.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the tests
|
||||
runModalTests().catch(error => {
|
||||
console.error('Test runner error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
266
simple_tests.js
266
simple_tests.js
|
|
@ -1,266 +0,0 @@
|
|||
// 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);
|
||||
2711
uncloseai.js.orig
2711
uncloseai.js.orig
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue