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:
Russell Ballestrini 2025-10-26 11:29:52 -04:00
parent 4c07f47f47
commit b41c10d2e6
6 changed files with 2 additions and 4980 deletions

2
.gitignore vendored
View file

@ -6,6 +6,8 @@ node_modules/
webwords/
book/
*.log
# Generated audio files in language examples
public/languages/**/*.mp3
*.mp3

View file

@ -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!');

File diff suppressed because it is too large Load diff

View file

@ -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);
});

View file

@ -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);

File diff suppressed because it is too large Load diff