// 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(`
Modal Test Page
`, {
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) => `${text}
`,
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 = `
`;
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 = `
`;
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);
});