#!/usr/bin/env node /** * Test Click Propagation Fix * * Tests that button clicks don't propagate through to trigger editing * of sections below the overlay */ const { TestRunner } = require('./test_runner.js'); const runner = new TestRunner(); runner.describe('Click Propagation Fix Tests', () => { runner.it('should prevent cancel button clicks from propagating', 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); // Create multiple sections const markdown = '# Section 1\n\nFirst section.\n\n# Section 2\n\nSecond section.'; const sections = manager.createSectionsFromMarkdown(markdown); const section1 = sections[0]; const section2 = sections[1]; // Mock elements const mockElement1 = document.createElement('div'); mockElement1.setAttribute('data-section-id', section1.id); mockElement1.innerHTML = '

Section 1

First section.

'; const mockElement2 = document.createElement('div'); mockElement2.setAttribute('data-section-id', section2.id); mockElement2.innerHTML = '

Section 2

Second section.

'; // Track which element is returned for findSectionElement let currentEditingSection = null; renderer.findSectionElement = (sectionId) => { if (sectionId === section1.id) return mockElement1; if (sectionId === section2.id) return mockElement2; return null; }; // Track showEditor calls to detect unwanted propagation let showEditorCalls = []; const originalShowEditor = renderer.showEditor; renderer.showEditor = function(sectionId, content) { showEditorCalls.push(sectionId); return originalShowEditor.call(this, sectionId, content); }; // Start editing section 1 manager.startEditing(section1.id); currentEditingSection = section1.id; renderer.showEditor(section1.id, section1.currentMarkdown); // Clear the tracking array after initial call showEditorCalls = []; // Get cancel button from section 1 editor const cancelBtn = mockElement1.querySelector('.ui-edit-button-cancel'); runner.expect(cancelBtn).toBeTruthy(); // Create a mock event with stopPropagation and preventDefault tracking let preventDefaultCalled = false; let stopPropagationCalled = false; const mockEvent = { target: cancelBtn, preventDefault: () => { preventDefaultCalled = true; }, stopPropagation: () => { stopPropagationCalled = true; } }; // Simulate cancel button click renderer.handleCancel(mockEvent); // Verify event prevention methods were called runner.expect(preventDefaultCalled).toBeTruthy(); runner.expect(stopPropagationCalled).toBeTruthy(); // Verify no additional editor was shown (no propagation to section 2) runner.expect(showEditorCalls.length).toBe(0); // Cleanup document.body.removeChild(container); } }); runner.it('should prevent overlay clicks from bubbling through', 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 Section'; const sections = manager.createSectionsFromMarkdown(textMarkdown); const textSection = sections[0]; const mockElement = document.createElement('div'); mockElement.setAttribute('data-section-id', textSection.id); renderer.findSectionElement = () => mockElement; // Show editor renderer.showEditor(textSection.id, textSection.currentMarkdown); // Get overlay container const overlayContainer = mockElement.querySelector('.ui-edit-overlay-container'); runner.expect(overlayContainer).toBeTruthy(); // Verify click event listener was added to overlay // We can't directly test the event listener, but we can verify the overlay responds to click events let clickEventHandled = false; // Add a test listener to see if events bubble document.addEventListener('click', () => { clickEventHandled = true; }); // Create and dispatch a click event on the overlay const clickEvent = new Event('click', { bubbles: true }); let stopPropagationCalled = false; const originalStopPropagation = clickEvent.stopPropagation; clickEvent.stopPropagation = function() { stopPropagationCalled = true; originalStopPropagation.call(this); }; overlayContainer.dispatchEvent(clickEvent); // Verify stopPropagation was called (meaning our handler is working) runner.expect(stopPropagationCalled).toBeTruthy(); // Cleanup document.body.removeChild(container); } }); runner.it('should handle all button types with event prevention', 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); renderer.findSectionElement = () => mockElement; renderer.showEditor(textSection.id, textSection.currentMarkdown); // Test all three button types const buttons = { accept: mockElement.querySelector('.ui-edit-button-accept'), cancel: mockElement.querySelector('.ui-edit-button-cancel'), reset: mockElement.querySelector('.ui-edit-button-reset') }; const handlers = { accept: renderer.handleAccept.bind(renderer), cancel: renderer.handleCancel.bind(renderer), reset: renderer.handleReset.bind(renderer) }; Object.keys(buttons).forEach(buttonType => { const button = buttons[buttonType]; const handler = handlers[buttonType]; runner.expect(button).toBeTruthy(); // Create mock event let preventDefaultCalled = false; let stopPropagationCalled = false; const mockEvent = { target: button, preventDefault: () => { preventDefaultCalled = true; }, stopPropagation: () => { stopPropagationCalled = true; } }; // Call handler handler(mockEvent); // Verify event prevention runner.expect(preventDefaultCalled).toBeTruthy(); runner.expect(stopPropagationCalled).toBeTruthy(); }); // Cleanup document.body.removeChild(container); } }); }); // Run the tests if (require.main === module) { console.log('🛡️ Running Click Propagation Fix 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 - click propagation issues remain`); } else { console.log('✅ All click propagation fix tests passed!'); } }); } module.exports = runner;