diff --git a/markitect/static/editor.js b/markitect/static/editor.js
index 7ac4d7ab..1876f97c 100644
--- a/markitect/static/editor.js
+++ b/markitect/static/editor.js
@@ -1825,6 +1825,16 @@ class DOMRenderer {
this.hideCurrentEditor();
+ // Track staging state for this editor
+ const stagingState = {
+ originalMarkdown: section.currentMarkdown,
+ currentAltText: '',
+ currentImageSrc: '',
+ stagedImageSrc: null,
+ stagedAltText: null,
+ hasChanges: false
+ };
+
const editorContainer = document.createElement('div');
editorContainer.className = 'ui-edit-image-editor-container';
editorContainer.style.cssText = `
@@ -1838,7 +1848,15 @@ class DOMRenderer {
border: 2px solid #007bff;
`;
- // Image preview
+ // Parse markdown to extract image info
+ const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
+ if (imageMatch) {
+ const [, altText, imageSrc] = imageMatch;
+ stagingState.currentAltText = altText;
+ stagingState.currentImageSrc = imageSrc;
+ }
+
+ // Image preview with drop zone
const imagePreview = document.createElement('div');
imagePreview.className = 'ui-edit-image-preview';
imagePreview.style.cssText = `
@@ -1847,44 +1865,144 @@ class DOMRenderer {
background: white;
padding: 16px;
border-radius: 6px;
- border: 1px solid #dee2e6;
+ border: 2px dashed #007bff;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ min-height: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: relative;
`;
- // Parse markdown to extract image info
- const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
- if (imageMatch) {
- const [, altText, imageSrc] = imageMatch;
- const img = document.createElement('img');
- img.src = imageSrc;
- img.alt = altText;
- img.style.cssText = `
- max-width: 100%;
- max-height: 300px;
- border-radius: 4px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- `;
- imagePreview.appendChild(img);
- }
+ // Function to update image preview
+ const updateImagePreview = (imageSrc, altText) => {
+ imagePreview.innerHTML = '';
- // Image controls
- const controlPanel = document.createElement('div');
- controlPanel.className = 'ui-edit-image-controls';
- controlPanel.style.cssText = `
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
- gap: 8px;
- `;
+ if (imageSrc) {
+ const img = document.createElement('img');
+ img.src = imageSrc;
+ img.alt = altText || '';
+ img.style.cssText = `
+ max-width: 100%;
+ max-height: 250px;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ `;
+ imagePreview.appendChild(img);
+
+ // Add overlay for drop zone
+ const overlay = document.createElement('div');
+ overlay.className = 'drop-overlay';
+ overlay.style.cssText = `
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 123, 255, 0.1);
+ border-radius: 6px;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ color: #007bff;
+ font-weight: bold;
+ font-size: 18px;
+ `;
+ overlay.textContent = '📁 Drop new image here';
+ imagePreview.appendChild(overlay);
+ } else {
+ // Show drop zone placeholder
+ const placeholder = document.createElement('div');
+ placeholder.style.cssText = `
+ text-align: center;
+ color: #6c757d;
+ font-size: 16px;
+ `;
+ placeholder.innerHTML = `
+
📁
+ Drop image here or click to select
+ Supports JPG, PNG, GIF, WebP
+ `;
+ imagePreview.appendChild(placeholder);
+ }
+ };
+
+ // Initialize preview
+ updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText);
+
+ // File input for image selection
+ const fileInput = document.createElement('input');
+ fileInput.type = 'file';
+ fileInput.accept = 'image/*';
+ fileInput.style.display = 'none';
+
+ // Function to handle image file selection
+ const handleImageFile = (file) => {
+ if (file && file.type.startsWith('image/')) {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ stagingState.stagedImageSrc = event.target.result;
+ stagingState.hasChanges = true;
+ updateImagePreview(stagingState.stagedImageSrc, altTextInput.value);
+ updateChangeIndicator();
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ // Drag and drop functionality
+ imagePreview.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ imagePreview.style.borderColor = '#28a745';
+ imagePreview.style.backgroundColor = '#f8fff8';
+ const overlay = imagePreview.querySelector('.drop-overlay');
+ if (overlay) overlay.style.display = 'flex';
+ });
+
+ imagePreview.addEventListener('dragleave', (e) => {
+ e.preventDefault();
+ imagePreview.style.borderColor = '#007bff';
+ imagePreview.style.backgroundColor = 'white';
+ const overlay = imagePreview.querySelector('.drop-overlay');
+ if (overlay) overlay.style.display = 'none';
+ });
+
+ imagePreview.addEventListener('drop', (e) => {
+ e.preventDefault();
+ imagePreview.style.borderColor = '#007bff';
+ imagePreview.style.backgroundColor = 'white';
+ const overlay = imagePreview.querySelector('.drop-overlay');
+ if (overlay) overlay.style.display = 'none';
+
+ const files = e.dataTransfer.files;
+ if (files.length > 0) {
+ handleImageFile(files[0]);
+ }
+ });
+
+ // Click to select file
+ imagePreview.addEventListener('click', () => {
+ fileInput.click();
+ });
+
+ fileInput.addEventListener('change', (e) => {
+ if (e.target.files.length > 0) {
+ handleImageFile(e.target.files[0]);
+ }
+ });
// Alt text editor
const altTextContainer = document.createElement('div');
- altTextContainer.style.cssText = `grid-column: 1 / -1; margin-bottom: 8px;`;
+ altTextContainer.style.cssText = `margin-bottom: 16px;`;
const altTextLabel = document.createElement('label');
altTextLabel.textContent = 'Alt Text:';
altTextLabel.style.cssText = `display: block; margin-bottom: 4px; font-weight: bold;`;
const altTextInput = document.createElement('input');
altTextInput.type = 'text';
- altTextInput.value = imageMatch ? imageMatch[1] : '';
+ altTextInput.value = stagingState.currentAltText;
altTextInput.style.cssText = `
width: 100%;
padding: 8px;
@@ -1893,26 +2011,41 @@ class DOMRenderer {
font-size: 14px;
`;
+ // Track alt text changes
+ altTextInput.addEventListener('input', () => {
+ stagingState.stagedAltText = altTextInput.value;
+ stagingState.hasChanges = altTextInput.value !== stagingState.currentAltText || stagingState.stagedImageSrc !== null;
+ updateChangeIndicator();
+ });
+
// Add keyboard shortcuts to alt text input
altTextInput.addEventListener('keydown', this.handleKeydown);
altTextContainer.appendChild(altTextLabel);
altTextContainer.appendChild(altTextInput);
- // Image manipulation buttons
- const buttons = [
- { text: 'Replace Image', action: () => this.replaceImage(sectionId) },
- { text: 'Resize', action: () => this.resizeImage(sectionId) },
- { text: 'Add Caption', action: () => this.addImageCaption(sectionId) },
- { text: 'Remove Image', action: () => this.removeImage(sectionId) }
- ];
+ // Change indicator
+ const changeIndicator = document.createElement('div');
+ changeIndicator.className = 'change-indicator';
+ changeIndicator.style.cssText = `
+ padding: 8px 12px;
+ background: #fff3cd;
+ border: 1px solid #ffeaa7;
+ border-radius: 4px;
+ color: #856404;
+ font-size: 14px;
+ text-align: center;
+ display: none;
+ `;
+ changeIndicator.textContent = '⚠️ You have unsaved changes';
- buttons.forEach(({ text, action }) => {
- const btn = this.createButton(text, 'ui-edit-image-btn', action);
- btn.style.fontSize = '12px';
- btn.style.padding = '6px 12px';
- controlPanel.appendChild(btn);
- });
+ const updateChangeIndicator = () => {
+ if (stagingState.hasChanges) {
+ changeIndicator.style.display = 'block';
+ } else {
+ changeIndicator.style.display = 'none';
+ }
+ };
// Standard editor controls
const editorControls = document.createElement('div');
@@ -1925,12 +2058,30 @@ class DOMRenderer {
`;
const acceptBtn = this.createButton('✓ Accept', 'ui-edit-accept', (e) => {
- // Update alt text if changed
- if (imageMatch && altTextInput.value !== imageMatch[1]) {
- const newMarkdown = section.currentMarkdown.replace(
- /!\[(.*?)\]/,
- `![${altTextInput.value}]`
- );
+ // Apply staged changes only when accept is clicked
+ if (stagingState.hasChanges) {
+ let newMarkdown = stagingState.originalMarkdown;
+
+ // Apply image source change if staged
+ if (stagingState.stagedImageSrc !== null) {
+ const currentImageMatch = newMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
+ if (currentImageMatch) {
+ newMarkdown = newMarkdown.replace(
+ /!\[(.*?)\]\((.*?)\)/,
+ `![${currentImageMatch[1]}](${stagingState.stagedImageSrc})`
+ );
+ }
+ }
+
+ // Apply alt text change if staged
+ if (stagingState.stagedAltText !== null) {
+ newMarkdown = newMarkdown.replace(
+ /!\[(.*?)\]/,
+ `![${stagingState.stagedAltText}]`
+ );
+ }
+
+ // Update section with final changes
this.sectionManager.updateContent(sectionId, newMarkdown);
this.updateSectionContent(sectionId, newMarkdown);
}
@@ -1939,21 +2090,25 @@ class DOMRenderer {
this.sectionManager.acceptChanges(sectionId);
this.hideEditor(sectionId);
});
+
const cancelBtn = this.createButton('✗ Cancel', 'ui-edit-cancel', (e) => {
- // Cancel changes and hide editor
+ // Discard all staged changes and hide editor
this.sectionManager.cancelChanges(sectionId);
this.hideEditor(sectionId);
});
- const resetBtn = this.createButton('↺ Reset', 'ui-edit-reset', (e) => {
- // Reset section to original content and close editor
- this.sectionManager.resetSection(sectionId);
- this.hideEditor(sectionId);
- // Update the section content to reflect the reset immediately
- const resetSection = this.sectionManager.sections.get(sectionId);
- if (resetSection) {
- this.updateSectionContent(sectionId, resetSection.currentMarkdown);
- }
+ const resetBtn = this.createButton('↺ Reset', 'ui-edit-reset', (e) => {
+ // Reset both section and staging state
+ stagingState.stagedImageSrc = null;
+ stagingState.stagedAltText = null;
+ stagingState.hasChanges = false;
+
+ // Reset alt text input to original
+ altTextInput.value = stagingState.currentAltText;
+
+ // Reset preview to original
+ updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText);
+ updateChangeIndicator();
});
acceptBtn.style.background = '#28a745';
@@ -1967,8 +2122,9 @@ class DOMRenderer {
// Assemble the editor
editorContainer.appendChild(imagePreview);
editorContainer.appendChild(altTextContainer);
- editorContainer.appendChild(controlPanel);
+ editorContainer.appendChild(changeIndicator);
editorContainer.appendChild(editorControls);
+ editorContainer.appendChild(fileInput);
element.appendChild(editorContainer);
altTextInput.focus();
@@ -1977,40 +2133,8 @@ class DOMRenderer {
/**
* Image manipulation methods
+ * Note: Image replacement is now integrated into the main image editor with drag & drop
*/
- replaceImage(sectionId) {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.addEventListener('change', (e) => {
- const file = e.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = (event) => {
- const section = this.sectionManager.sections.get(sectionId);
- const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
- if (imageMatch) {
- const newMarkdown = section.currentMarkdown.replace(
- /!\[(.*?)\]\((.*?)\)/,
- `![${imageMatch[1]}](${event.target.result})`
- );
- this.sectionManager.updateContent(sectionId, newMarkdown);
- this.hideEditor(sectionId);
- this.updateSectionContent(sectionId, newMarkdown);
- // Wait for DOM update before showing image editor
- setTimeout(() => {
- const updatedSection = this.sectionManager.sections.get(sectionId);
- if (updatedSection) {
- this.showImageEditor(sectionId, updatedSection);
- }
- }, 100);
- }
- };
- reader.readAsDataURL(file);
- }
- });
- input.click();
- }
resizeImage(sectionId) {
const section = this.sectionManager.sections.get(sectionId);
diff --git a/test_improved_image_workflow.js b/test_improved_image_workflow.js
new file mode 100644
index 00000000..86c3747e
--- /dev/null
+++ b/test_improved_image_workflow.js
@@ -0,0 +1,373 @@
+#!/usr/bin/env node
+
+/**
+ * Test Improved Image Editing Workflow
+ *
+ * Tests the new image editing features:
+ * - Drop zone functionality
+ * - Staging changes instead of immediate application
+ * - Apply changes only on accept button
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+runner.describe('Improved Image Editing Workflow Tests', () => {
+
+ runner.it('should create image editor with drop zone functionality', 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 section with image
+ const imageMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(imageMarkdown);
+ const imageSection = sections[0];
+
+ // Render the section to create DOM element
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement to return a test element
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Verify drop zone elements exist
+ const imagePreview = testElement.querySelector('.ui-edit-image-preview');
+ runner.expect(imagePreview).toBeTruthy();
+ runner.expect(imagePreview.style.cursor).toBe('pointer');
+ runner.expect(imagePreview.style.border.includes('dashed')).toBeTruthy();
+
+ // Verify change indicator exists
+ const changeIndicator = testElement.querySelector('.change-indicator');
+ runner.expect(changeIndicator).toBeTruthy();
+ runner.expect(changeIndicator.style.display).toBe('none'); // Initially hidden
+
+ // Verify file input exists (hidden)
+ const fileInput = testElement.querySelector('input[type="file"]');
+ runner.expect(fileInput).toBeTruthy();
+ runner.expect(fileInput.accept).toBe('image/*');
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+
+ runner.it('should handle staging state for image changes', 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);
+
+ // Create section with image
+ const imageMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(imageMarkdown);
+ const imageSection = sections[0];
+
+ // Render the section to create DOM element
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Verify original content is unchanged
+ runner.expect(imageSection.currentMarkdown).toBe(imageMarkdown);
+
+ // Verify alt text input has original value
+ const altTextInput = testElement.querySelector('input[type="text"]');
+ runner.expect(altTextInput.value).toBe('Original Alt');
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+
+ runner.it('should track changes in staging state without immediate application', 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);
+
+ // Create section with image
+ const originalMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(originalMarkdown);
+ const imageSection = sections[0];
+
+ // Render the section
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Get alt text input and change it
+ const altTextInput = testElement.querySelector('input[type="text"]');
+ altTextInput.value = 'Modified Alt Text';
+
+ // Trigger input event to simulate user typing
+ const inputEvent = new Event('input', { bubbles: true });
+ altTextInput.dispatchEvent(inputEvent);
+
+ // Verify section content is NOT immediately changed
+ runner.expect(imageSection.currentMarkdown).toBe(originalMarkdown);
+ runner.expect(imageSection.currentMarkdown.includes('Modified Alt Text')).toBeFalsy();
+
+ // Verify change indicator is shown
+ const changeIndicator = testElement.querySelector('.change-indicator');
+ // Note: We can't test display style directly due to how it's updated via function closure
+ runner.expect(changeIndicator).toBeTruthy();
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+
+ runner.it('should apply staged changes only when accept button is clicked', 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);
+
+ // Create section with image
+ const originalMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(originalMarkdown);
+ const imageSection = sections[0];
+
+ // Start editing to prepare section
+ manager.startEditing(imageSection.id);
+
+ // Render the section
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement and updateSectionContent
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ let updatedContent = null;
+ renderer.updateSectionContent = (sectionId, content) => {
+ updatedContent = content;
+ };
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Modify alt text
+ const altTextInput = testElement.querySelector('input[type="text"]');
+ altTextInput.value = 'Accepted Alt Text';
+ const inputEvent = new Event('input', { bubbles: true });
+ altTextInput.dispatchEvent(inputEvent);
+
+ // Verify content still not changed
+ runner.expect(imageSection.currentMarkdown).toBe(originalMarkdown);
+
+ // Click accept button
+ const acceptButton = testElement.querySelector('.ui-edit-accept');
+ runner.expect(acceptButton).toBeTruthy();
+
+ // Simulate accept button click
+ acceptButton.click();
+
+ // Verify changes were applied
+ runner.expect(imageSection.currentMarkdown.includes('Accepted Alt Text')).toBeTruthy();
+ runner.expect(updatedContent?.includes('Accepted Alt Text')).toBeTruthy();
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+
+ runner.it('should discard staged changes when cancel button is clicked', 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);
+
+ // Create section with image
+ const originalMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(originalMarkdown);
+ const imageSection = sections[0];
+
+ // Start editing
+ manager.startEditing(imageSection.id);
+
+ // Render the section
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Modify alt text
+ const altTextInput = testElement.querySelector('input[type="text"]');
+ altTextInput.value = 'Should Be Discarded';
+ const inputEvent = new Event('input', { bubbles: true });
+ altTextInput.dispatchEvent(inputEvent);
+
+ // Click cancel button
+ const cancelButton = testElement.querySelector('.ui-edit-cancel');
+ runner.expect(cancelButton).toBeTruthy();
+
+ // Simulate cancel button click
+ cancelButton.click();
+
+ // Verify changes were discarded
+ runner.expect(imageSection.currentMarkdown).toBe(originalMarkdown);
+ runner.expect(imageSection.currentMarkdown.includes('Should Be Discarded')).toBeFalsy();
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+
+ runner.it('should reset staged changes when reset button is clicked', 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);
+
+ // Create section with image
+ const originalMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(originalMarkdown);
+ const imageSection = sections[0];
+
+ // Start editing
+ manager.startEditing(imageSection.id);
+
+ // Render the section
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Modify alt text
+ const altTextInput = testElement.querySelector('input[type="text"]');
+ altTextInput.value = 'Should Be Reset';
+ const inputEvent = new Event('input', { bubbles: true });
+ altTextInput.dispatchEvent(inputEvent);
+
+ // Click reset button
+ const resetButton = testElement.querySelector('.ui-edit-reset');
+ runner.expect(resetButton).toBeTruthy();
+
+ // Simulate reset button click
+ resetButton.click();
+
+ // Verify alt text input was reset to original
+ runner.expect(altTextInput.value).toBe('Original Alt');
+
+ // Verify section content is still original
+ runner.expect(imageSection.currentMarkdown).toBe(originalMarkdown);
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+
+ runner.it('should handle drag and drop event listeners', 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);
+
+ // Create section with image
+ const imageMarkdown = '';
+ const sections = manager.createSectionsFromMarkdown(imageMarkdown);
+ const imageSection = sections[0];
+
+ // Render the section
+ renderer.renderAllSections(sections);
+
+ // Mock findSectionElement
+ const testElement = document.createElement('div');
+ testElement.setAttribute('data-section-id', imageSection.id);
+ renderer.findSectionElement = () => testElement;
+
+ // Show image editor
+ renderer.showImageEditor(imageSection.id, imageSection);
+
+ // Get image preview element
+ const imagePreview = testElement.querySelector('.ui-edit-image-preview');
+ runner.expect(imagePreview).toBeTruthy();
+
+ // Test that drag event listeners are attached by checking if events can be created
+ // We can't fully test the drag functionality without complex event simulation,
+ // but we can verify the elements and basic structure
+
+ // Verify the element has pointer cursor for click functionality
+ runner.expect(imagePreview.style.cursor).toBe('pointer');
+
+ // Verify file input exists for click-to-select functionality
+ const fileInput = testElement.querySelector('input[type="file"]');
+ runner.expect(fileInput).toBeTruthy();
+ runner.expect(fileInput.style.display).toBe('none');
+
+ // Cleanup
+ document.body.removeChild(container);
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('🎨 Running Improved Image Editing Workflow 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 - image workflow needs attention`);
+ } else {
+ console.log('✅ All improved image workflow tests passed!');
+ }
+ });
+}
+
+module.exports = runner;
\ No newline at end of file