#!/usr/bin/env node /** * Test Unified Floating System * * Tests the new unified floating menu system that works consistently * for both text and image editing across all document positions */ const { TestRunner } = require('./test_runner.js'); const runner = new TestRunner(); runner.describe('Unified Floating System Tests', () => { runner.it('should have createFloatingMenu method', async () => { // Load editor delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')]; require('/home/worsch/markitect_project/markitect/static/editor.js'); if (global.DOMRenderer && global.SectionManager) { const container = document.createElement('div'); container.innerHTML = '
'; document.body.appendChild(container); const manager = new global.SectionManager(); const renderer = new global.DOMRenderer(manager, container); // Verify createFloatingMenu method exists runner.expect(typeof renderer.createFloatingMenu).toBe('function'); // Test basic floating menu creation const mockElement = document.createElement('div'); mockElement.setAttribute('data-section-id', 'test-id'); Object.defineProperties(mockElement, { getBoundingClientRect: { value: () => ({ top: 100, right: 300, bottom: 150, left: 50 }) } }); renderer.findSectionElement = () => mockElement; const testContent = document.createElement('div'); testContent.textContent = 'Test Content'; const testControls = document.createElement('div'); testControls.textContent = 'Test Controls'; const floatingMenu = renderer.createFloatingMenu('test-id', 'text', testContent, testControls); runner.expect(floatingMenu).toBeTruthy(); runner.expect(floatingMenu.className).toBe('ui-edit-floating-menu'); runner.expect(floatingMenu.dataset.sectionId).toBe('test-id'); runner.expect(floatingMenu.dataset.editType).toBe('text'); // Cleanup document.body.removeChild(container); if (floatingMenu.parentElement) { floatingMenu.remove(); } } }); runner.it('should calculate optimal positioning based on viewport space', async () => { if (global.DOMRenderer && global.SectionManager) { const container = document.createElement('div'); container.innerHTML = '
'; document.body.appendChild(container); const manager = new global.SectionManager(); const renderer = new global.DOMRenderer(manager, container); // Test positioning to the right (normal case) const mockElement1 = document.createElement('div'); mockElement1.setAttribute('data-section-id', 'test-right'); Object.defineProperties(mockElement1, { getBoundingClientRect: { value: () => ({ top: 100, right: 200, bottom: 150, left: 50 }) } }); // Mock wide viewport Object.defineProperty(window, 'innerWidth', { value: 1200, configurable: true }); renderer.findSectionElement = () => mockElement1; const testContent = document.createElement('div'); const testControls = document.createElement('div'); const floatingMenu1 = renderer.createFloatingMenu('test-right', 'text', testContent, testControls); // Should position to the right const leftPos1 = parseInt(floatingMenu1.style.left); runner.expect(leftPos1).toBeGreaterThan(200); // Right of element // Test positioning to the left (narrow viewport) const mockElement2 = document.createElement('div'); mockElement2.setAttribute('data-section-id', 'test-left'); Object.defineProperties(mockElement2, { getBoundingClientRect: { value: () => ({ top: 200, right: 700, bottom: 250, left: 600 }) } }); // Mock narrow viewport Object.defineProperty(window, 'innerWidth', { value: 800, configurable: true }); renderer.findSectionElement = () => mockElement2; const floatingMenu2 = renderer.createFloatingMenu('test-left', 'text', testContent.cloneNode(), testControls.cloneNode()); // Should position to the left or at minimum distance const leftPos2 = parseInt(floatingMenu2.style.left); runner.expect(leftPos2).toBeLessThan(600); // Left of element or minimum // Cleanup document.body.removeChild(container); floatingMenu1.remove(); floatingMenu2.remove(); } }); runner.it('should create different icons for text vs image editing', async () => { if (global.DOMRenderer && global.SectionManager) { const container = document.createElement('div'); container.innerHTML = '
'; document.body.appendChild(container); const manager = new global.SectionManager(); const renderer = new global.DOMRenderer(manager, container); const mockElement = document.createElement('div'); mockElement.setAttribute('data-section-id', 'test-id'); Object.defineProperties(mockElement, { getBoundingClientRect: { value: () => ({ top: 100, right: 300, bottom: 150, left: 50 }) } }); renderer.findSectionElement = () => mockElement; const testContent = document.createElement('div'); const testControls = document.createElement('div'); // Test text editing const textMenu = renderer.createFloatingMenu('test-id', 'text', testContent, testControls); const textHandle = textMenu.querySelector('.ui-edit-drag-handle'); runner.expect(textHandle.innerHTML).toContain('📝'); runner.expect(textHandle.innerHTML).toContain('Editing Text'); // Test image editing const imageMenu = renderer.createFloatingMenu('test-id', 'image', testContent.cloneNode(), testControls.cloneNode()); const imageHandle = imageMenu.querySelector('.ui-edit-drag-handle'); runner.expect(imageHandle.innerHTML).toContain('🖼️'); runner.expect(imageHandle.innerHTML).toContain('Editing Image'); // Cleanup document.body.removeChild(container); textMenu.remove(); imageMenu.remove(); } }); runner.it('should apply consistent element highlighting', async () => { if (global.DOMRenderer && global.SectionManager) { const container = document.createElement('div'); container.innerHTML = '
'; document.body.appendChild(container); const manager = new global.SectionManager(); const renderer = new global.DOMRenderer(manager, container); const mockElement = document.createElement('div'); mockElement.setAttribute('data-section-id', 'test-id'); Object.defineProperties(mockElement, { getBoundingClientRect: { value: () => ({ top: 100, right: 300, bottom: 150, left: 50 }) } }); renderer.findSectionElement = () => mockElement; const testContent = document.createElement('div'); const testControls = document.createElement('div'); // Create floating menu const floatingMenu = renderer.createFloatingMenu('test-id', 'text', testContent, testControls); // Verify element highlighting runner.expect(mockElement.style.outline).toBe('3px solid rgb(0, 123, 255)'); runner.expect(mockElement.style.outlineOffset).toBe('2px'); runner.expect(mockElement.style.backgroundColor).toBe('rgba(0, 123, 255, 0.08)'); runner.expect(mockElement.style.transition).toBe('all 0.2s ease'); // Cleanup document.body.removeChild(container); floatingMenu.remove(); } }); runner.it('should have consistent width and styling', async () => { if (global.DOMRenderer && global.SectionManager) { const container = document.createElement('div'); container.innerHTML = '
'; document.body.appendChild(container); const manager = new global.SectionManager(); const renderer = new global.DOMRenderer(manager, container); const mockElement = document.createElement('div'); mockElement.setAttribute('data-section-id', 'test-id'); Object.defineProperties(mockElement, { getBoundingClientRect: { value: () => ({ top: 100, right: 300, bottom: 150, left: 50 }) } }); renderer.findSectionElement = () => mockElement; const testContent = document.createElement('div'); const testControls = document.createElement('div'); const floatingMenu = renderer.createFloatingMenu('test-id', 'text', testContent, testControls); // Verify consistent styling runner.expect(floatingMenu.style.position).toBe('fixed'); runner.expect(floatingMenu.style.width).toBe('340px'); runner.expect(floatingMenu.style.zIndex).toBe('10000'); runner.expect(floatingMenu.style.borderRadius).toBe('12px'); runner.expect(floatingMenu.style.display).toBe('flex'); runner.expect(floatingMenu.style.flexDirection).toBe('column'); // Verify drag handle exists const dragHandle = floatingMenu.querySelector('.ui-edit-drag-handle'); runner.expect(dragHandle).toBeTruthy(); runner.expect(dragHandle.style.cursor).toBe('move'); // Cleanup document.body.removeChild(container); floatingMenu.remove(); } }); runner.it('should work with text editor using unified system', async () => { if (global.DOMRenderer && global.SectionManager) { const container = document.createElement('div'); container.innerHTML = '
'; document.body.appendChild(container); const manager = new global.SectionManager(); const renderer = new global.DOMRenderer(manager, container); const textMarkdown = '# Test Content'; const sections = manager.createSectionsFromMarkdown(textMarkdown); const textSection = sections[0]; const mockElement = document.createElement('div'); mockElement.setAttribute('data-section-id', textSection.id); Object.defineProperties(mockElement, { getBoundingClientRect: { value: () => ({ top: 100, right: 300, bottom: 150, left: 50 }) } }); renderer.findSectionElement = () => mockElement; // Show text editor renderer.showEditor(textSection.id, textSection.currentMarkdown); // Verify floating menu was created for text const floatingMenu = document.querySelector('.ui-edit-floating-menu'); runner.expect(floatingMenu).toBeTruthy(); runner.expect(floatingMenu.dataset.editType).toBe('text'); // Verify textarea exists const textarea = floatingMenu.querySelector('.ui-edit-textarea'); runner.expect(textarea).toBeTruthy(); runner.expect(textarea.value).toBe(textSection.currentMarkdown); // Cleanup document.body.removeChild(container); floatingMenu.remove(); } }); }); // Run the tests if (require.main === module) { console.log('🔧 Running Unified Floating System Tests'); runner.run().then(() => { const results = runner.results; const failed = results.filter(r => r.status === 'FAIL').length; if (failed > 0) { console.log(`❌ ${failed} test(s) failed - unified system needs attention`); results.forEach(result => { if (result.status === 'FAIL') { console.log(`Failed test: ${result.name}`); console.log(`Error: ${result.error}`); } }); } else { console.log('✅ All unified floating system tests passed!'); } }); } module.exports = runner;