style: apply eslint --fix (semi, indent 4); no behavior change, Jest 68/68
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 299063@bnt-lap001
Assistant-Session: 53eb1e1c-c13a-4277-9ce2-3702960d93f7
This commit is contained in:
tegwick 2026-10-02 22:59:33 +02:00
parent de47bf1d2b
commit 652cb4e255
43 changed files with 1081 additions and 1115 deletions

View file

@ -116,11 +116,11 @@ class DebugPanel {
const messagesHtml = recentMessages.map(msg => {
const categoryColor = {
'INFO': '#17a2b8',
'WARNING': '#ffc107',
'ERROR': '#dc3545',
'SUCCESS': '#28a745',
'DEBUG': '#6f42c1'
INFO: '#17a2b8',
WARNING: '#ffc107',
ERROR: '#dc3545',
SUCCESS: '#28a745',
DEBUG: '#6f42c1'
}[msg.category] || '#6c757d';
return `
@ -188,4 +188,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Export for browser use
if (typeof window !== 'undefined') {
window.DebugPanel = DebugPanel;
}
}

View file

@ -276,4 +276,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Export for browser use
if (typeof window !== 'undefined') {
window.DocumentControls = DocumentControls;
}
}

View file

@ -942,7 +942,6 @@ class DOMRenderer {
controls.appendChild(cancelBtn);
controls.appendChild(resetBtn);
// Event handlers
acceptBtn.addEventListener('click', () => {
// Apply staged changes only when accept is clicked
@ -1064,7 +1063,7 @@ class DOMRenderer {
trackEvent(eventType, data) {
const eventRecord = {
type: eventType,
data: data,
data,
timestamp: new Date().toISOString()
};
@ -1125,4 +1124,4 @@ if (typeof module !== 'undefined' && module.exports) {
if (typeof window !== 'undefined') {
window.DOMRenderer = DOMRenderer;
window.FloatingMenu = FloatingMenu;
}
}

View file

@ -27,7 +27,6 @@ class MarkitectConfig {
// Validate required fields
this.validateConfig();
} catch (error) {
console.error('❌ Failed to load Markitect configuration:', error);
this.config = this.getDefaultConfig();
@ -165,4 +164,4 @@ window.markitectBase64References = window.markitectConfig.base64References;
// Export for module use
if (typeof module !== 'undefined' && module.exports) {
module.exports = MarkitectConfig;
}
}

View file

@ -114,7 +114,6 @@ class ContentsControl extends ControlBase {
${contentsHTML}
${refreshButtonHTML}
`;
}, 'Error generating contents', 'generateContent');
}
@ -164,7 +163,6 @@ class ContentsControl extends ControlBase {
this.headings = extractedHeadings;
this.lastScanTime = Date.now();
return extractedHeadings;
}, [], 'extractHeadings');
}
@ -286,4 +284,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Also expose globally: bundlers shim `module`, so the else-branch never ran in dist builds
if (typeof window !== 'undefined') {
window.ContentsControl = ContentsControl;
}
}

View file

@ -48,14 +48,14 @@ class ControlBase {
className: 'base-control',
defaultContent: 'Control content',
ariaLabel: 'Base Control',
position: 'w', // Compass position: west (middle-left)
footer: null // Custom footer text
position: 'w', // Compass position: west (middle-left)
footer: null // Custom footer text
};
// Internal state
this.element = null;
this.isExpanded = false;
this.isHeaderOnly = false; // New state for header-only visibility
this.isHeaderOnly = false; // New state for header-only visibility
this.isDragging = false;
this.isResizing = false;
this.position = { x: 0, y: 0 };
@ -63,7 +63,7 @@ class ControlBase {
width: 300,
height: Math.floor(window.innerHeight / 3)
};
this.originalPosition = null; // Store original position for collapse
this.originalPosition = null; // Store original position for collapse
// Event handlers storage
this.eventHandlers = new Map();
@ -120,7 +120,6 @@ class ControlBase {
this.setupStyles();
this.setupEventListeners();
return control;
}, null, 'createElement');
}
@ -235,17 +234,17 @@ class ControlBase {
*/
getCompassPosition() {
const positions = {
'n': { top: '20px', left: '50%', transform: 'translateX(-50%)' },
'ne': { top: '20px', right: '20px' },
'e': { right: '20px', top: '50%', transform: 'translateY(-50%)' },
'se': { bottom: '20px', right: '20px' },
's': { bottom: '20px', left: '50%', transform: 'translateX(-50%)' },
'sw': { bottom: '20px', left: '20px' },
'w': { left: '20px', top: '50%', transform: 'translateY(-50%)' },
'nw': { top: '20px', left: '20px' }
n: { top: '20px', left: '50%', transform: 'translateX(-50%)' },
ne: { top: '20px', right: '20px' },
e: { right: '20px', top: '50%', transform: 'translateY(-50%)' },
se: { bottom: '20px', right: '20px' },
s: { bottom: '20px', left: '50%', transform: 'translateX(-50%)' },
sw: { bottom: '20px', left: '20px' },
w: { left: '20px', top: '50%', transform: 'translateY(-50%)' },
nw: { top: '20px', left: '20px' }
};
return positions[this.config.position] || positions['w'];
return positions[this.config.position] || positions.w;
}
/**
@ -733,7 +732,6 @@ class ControlBase {
// Update internal size tracking
this.size.width = idealWidth;
this.size.height = idealHeight;
}, null, 'autoResizeToContent');
}
@ -792,7 +790,7 @@ class ControlBase {
* @returns {string} HTML content for the panel body
*/
generateContent() {
return this.config.defaultContent || `<p>Panel content goes here...</p>`;
return this.config.defaultContent || '<p>Panel content goes here...</p>';
}
/**
@ -851,4 +849,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Also expose globally: bundlers shim `module`, so the else-branch never ran in dist builds
if (typeof window !== 'undefined') {
window.ControlBase = ControlBase;
}
}

View file

@ -101,7 +101,6 @@ class DebugControl extends ControlBase {
window.addEventListener('unhandledrejection', (event) => {
this.addDebugMessage('PROMISE_REJECT', `Unhandled promise rejection: ${event.reason}`, 'error');
});
}, null, 'initializeDebugCapture');
}
@ -133,7 +132,6 @@ class DebugControl extends ControlBase {
if (this.element && this.isExpanded) {
this.updateMessageDisplay();
}
}, null, 'addDebugMessage');
}
@ -162,9 +160,9 @@ class DebugControl extends ControlBase {
cookieEnabled: navigator.cookieEnabled,
onlineStatus: navigator.onLine ? 'Online' : 'Offline',
protocol: window.location.protocol,
memory: performance.memory ?
`Used: ${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)}MB` :
'Not available'
memory: performance.memory
? `Used: ${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)}MB`
: 'Not available'
};
// Get Markitect version from config or default
@ -183,7 +181,6 @@ class DebugControl extends ControlBase {
</div>
</div>
`;
}, '', 'generateSystemInfoHTML');
}
@ -215,7 +212,6 @@ class DebugControl extends ControlBase {
</div>
</div>
`;
}, '', 'generatePerformanceHTML');
}
@ -267,7 +263,6 @@ class DebugControl extends ControlBase {
${messagesHTML}
</div>
`;
}, '<p>Error displaying messages</p>', 'generateMessagesHTML');
}
@ -389,7 +384,6 @@ class DebugControl extends ControlBase {
URL.revokeObjectURL(url);
this.addDebugMessage('EXPORT', 'Debug log exported successfully', 'info');
}, null, 'exportMessages');
}
@ -482,4 +476,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Also expose globally: bundlers shim `module`, so the else-branch never ran in dist builds
if (typeof window !== 'undefined') {
window.DebugControl = DebugControl;
}
}

View file

@ -178,7 +178,6 @@ class EditControl extends ControlBase {
</div>
</div>
`;
}, '<p>Error generating edit tools</p>', 'generateEditToolsHTML');
}
@ -264,7 +263,7 @@ class EditControl extends ControlBase {
exportMenu.exportAsMarkdown = () => {
// Simple HTML to Markdown conversion (basic)
let markdown = htmlContent
const markdown = htmlContent
.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n')
.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n')
.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n')
@ -278,7 +277,6 @@ class EditControl extends ControlBase {
};
document.body.appendChild(exportMenu);
}, null, 'exportDocument');
}
@ -356,7 +354,6 @@ class EditControl extends ControlBase {
// Show feedback
this.showActionFeedback('🔄 All changes reset', '#ffc107', '#212529');
}, null, 'resetAll');
}
@ -572,4 +569,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Also expose globally: bundlers shim `module`, so the else-branch never ran in dist builds
if (typeof window !== 'undefined') {
window.EditControl = EditControl;
}
}

View file

@ -90,7 +90,6 @@ class StatusControl extends ControlBase {
this.lastUpdateTime = Date.now();
return this.stats;
}, this.stats, 'analyzeDocument');
}
@ -198,13 +197,14 @@ class StatusControl extends ControlBase {
</button>
</div>
${this.lastUpdateTime ? `
${this.lastUpdateTime
? `
<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #eee; font-size: 0.7rem; color: #666; text-align: center;">
Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
</div>
` : ''}
`
: ''}
`;
}, '<p>Error displaying statistics</p>', 'formatStatistics');
}
@ -232,7 +232,6 @@ class StatusControl extends ControlBase {
refreshBtn.innerHTML = originalText;
}, 1000);
}
}, null, 'refreshStats');
}
@ -282,7 +281,6 @@ class StatusControl extends ControlBase {
exportBtn.style.background = '#28a745';
}, 2000);
}
}, null, 'exportStats');
}
@ -370,4 +368,4 @@ if (typeof module !== 'undefined' && module.exports) {
// Also expose globally: bundlers shim `module`, so the else-branch never ran in dist builds
if (typeof window !== 'undefined') {
window.StatusControl = StatusControl;
}
}

View file

@ -83,8 +83,9 @@ class MarkitectDebugSystem {
this.notifySubscribers(messageObj);
// Console output for development
const consoleMethod = category.toLowerCase() === 'error' ? 'error' :
category.toLowerCase() === 'warning' ? 'warn' : 'log';
const consoleMethod = category.toLowerCase() === 'error'
? 'error'
: category.toLowerCase() === 'warning' ? 'warn' : 'log';
console[consoleMethod](`[${source}] ${message}`, context);
return messageObj;
@ -290,4 +291,4 @@ class MarkitectDebugSystem {
window.MarkitectDebugSystem = new MarkitectDebugSystem();
// ES6 export for bundler
export { MarkitectDebugSystem };
export { MarkitectDebugSystem };

View file

@ -493,7 +493,7 @@ class SectionManager {
return {
totalSections: sections.length,
editingSections: editingSections
editingSections
};
}
@ -518,7 +518,7 @@ class SectionManager {
// Emit section-split event
this.emit('section-split', {
originalSectionId: sectionId,
newSections: newSections,
newSections,
count: newSections.length
});
@ -541,4 +541,4 @@ if (typeof window !== 'undefined') {
window.Section = Section;
window.EditState = EditState;
window.SectionType = SectionType;
}
}

View file

@ -40,7 +40,6 @@ const MarkitectMain = {
this.initialized = true;
console.log('✅ MarkitectMain initialization complete');
} catch (error) {
console.error('❌ MarkitectMain initialization failed:', error);
this.fallbackMode();
@ -153,7 +152,6 @@ const MarkitectMain = {
this.debugPanel.addMessage(`Document saved as: ${filename}`, 'SUCCESS');
}
console.log(`✅ Document saved as: ${filename}`);
} catch (error) {
if (this.debugPanel) {
this.debugPanel.addMessage(`Save failed: ${error.message}`, 'ERROR');
@ -284,4 +282,4 @@ if (document.readyState === 'loading') {
} else {
// DOM already ready
setTimeout(() => MarkitectMain.initialize(), 100);
}
}

View file

@ -36,7 +36,7 @@ const MarkitectMain = {
// In strict mode, throw on errors for immediate development feedback
if (MARKITECT_STRICT_MODE && level === 'ERROR') {
console.error(`🚨 STRICT MODE: Throwing error for immediate diagnosis`);
console.error('🚨 STRICT MODE: Throwing error for immediate diagnosis');
throw new Error(`${component}: ${message}`);
}
@ -84,7 +84,6 @@ const MarkitectMain = {
clearTimeout(timeout);
this.safeLog(`${controlName} initialized successfully`, 'SUCCESS');
return controlInstance;
} catch (error) {
clearTimeout(timeout);
this.safeLog(`${controlName} initialization failed: ${error.message}`, 'ERROR');
@ -103,7 +102,7 @@ const MarkitectMain = {
try {
const fallback = Object.create(window.Control);
fallback.config = {
icon: icon,
icon,
title: `${name} (Fallback)`,
className: `${name.toLowerCase()}-fallback`,
defaultContent: `${name} is running in fallback mode due to initialization issues.`,
@ -198,4 +197,4 @@ if (document.readyState === 'loading') {
} else {
// DOM already loaded
setTimeout(() => MarkitectMain.initialize(), 100);
}
}

View file

@ -25,26 +25,26 @@ export default {
// Default configuration
defaultOptions: {
position: 'left', // 'left' or 'right' side
collapsed: true, // Start in collapsed state
autoHide: true, // Hide on mobile devices
maxHeadingLevel: 3, // Include H1, H2, H3
enableScrollSpy: true, // Highlight current section
smoothScroll: true, // Smooth scroll to headings
animationDuration: 300, // Animation timing in ms
minHeadings: 2, // Minimum headings to show widget
theme: 'default', // Theme variant
position: 'left', // 'left' or 'right' side
collapsed: true, // Start in collapsed state
autoHide: true, // Hide on mobile devices
maxHeadingLevel: 3, // Include H1, H2, H3
enableScrollSpy: true, // Highlight current section
smoothScroll: true, // Smooth scroll to headings
animationDuration: 300, // Animation timing in ms
minHeadings: 2, // Minimum headings to show widget
theme: 'default', // Theme variant
// Layout options
width: '280px', // Expanded width
collapsedWidth: '40px', // Collapsed width
offset: { // Position offset
width: '280px', // Expanded width
collapsedWidth: '40px', // Collapsed width
offset: { // Position offset
top: '80px',
side: '20px'
},
// Accessibility
enableKeyboard: true, // Keyboard navigation support
enableKeyboard: true, // Keyboard navigation support
ariaLabel: 'Document Navigation'
},
@ -69,42 +69,42 @@ export default {
// Feature flags and capabilities
capabilities: {
draggable: false, // Not draggable (fixed position)
resizable: false, // Not resizable (fixed width)
themeable: true, // Supports themes
persistent: false, // Rebuilds on page changes
responsive: true, // Responsive behavior
keyboard: true, // Keyboard accessible
scrollSpy: true, // Scroll spy functionality
smoothScroll: true // Smooth scroll navigation
draggable: false, // Not draggable (fixed position)
resizable: false, // Not resizable (fixed width)
themeable: true, // Supports themes
persistent: false, // Rebuilds on page changes
responsive: true, // Responsive behavior
keyboard: true, // Keyboard accessible
scrollSpy: true, // Scroll spy functionality
smoothScroll: true // Smooth scroll navigation
},
// Integration requirements
requirements: {
container: true, // Requires container element
headings: true, // Requires document headings
scrollable: true // Requires scrollable content
container: true, // Requires container element
headings: true, // Requires document headings
scrollable: true // Requires scrollable content
},
// Event types emitted by this widget
events: [
'rendered', // Widget rendered to DOM
'navigate', // User navigated to heading
'toggle', // Widget expanded/collapsed
'theme-changed', // Theme was changed
'destroyed' // Widget was destroyed
'rendered', // Widget rendered to DOM
'navigate', // User navigated to heading
'toggle', // Widget expanded/collapsed
'theme-changed', // Theme was changed
'destroyed' // Widget was destroyed
],
// CSS classes used by this widget
cssClasses: [
'document-navigator', // Main widget class
'navigator-toggle', // Toggle button
'navigator-list', // Navigation list
'navigator-item', // Navigation items
'navigator-link', // Navigation links
'navigator-header', // List header
'navigator-close', // Close button
'navigator-empty' // Empty state
'document-navigator', // Main widget class
'navigator-toggle', // Toggle button
'navigator-list', // Navigation list
'navigator-item', // Navigation items
'navigator-link', // Navigation links
'navigator-header', // List header
'navigator-close', // Close button
'navigator-empty' // Empty state
],
// Theme variants
@ -204,4 +204,4 @@ export default {
return { navigator, container };
}
}
};
};

View file

@ -34,19 +34,19 @@ class TestDriveJSUI {
this.config = {
container: options.container,
markdown: options.markdown || '# Welcome\n\nStart editing...',
mode: options.mode || 'edit', // 'edit' or 'view'
mode: options.mode || 'edit', // 'edit' or 'view'
theme: options.theme || 'github',
autosave: options.autosave || false,
shortcuts: options.shortcuts !== false, // default true
sections: options.sections !== false, // default true
shortcuts: options.shortcuts !== false, // default true
sections: options.sections !== false, // default true
debug: options.debug || false,
// Advanced control panels (compass-positioned)
controls: {
editControl: options.controls?.editControl !== false, // default true
editControl: options.controls?.editControl !== false, // default true
statusControl: options.controls?.statusControl !== false, // default true
contentsControl: options.controls?.contentsControl !== false, // default true
debugControl: options.controls?.debugControl || false, // default false
debugControl: options.controls?.debugControl || false, // default false
// Legacy simple control panel (top-right box)
simpleControls: options.controls?.simpleControls || false // default false

View file

@ -6,12 +6,12 @@
*/
describe('Button Functionality and DOM Events', () => {
let mockSection;
let documentControls;
let mockSection;
let documentControls;
beforeEach(() => {
beforeEach(() => {
// Setup DOM with various buttons and controls
document.body.innerHTML = `
document.body.innerHTML = `
<div id="content">
<div class="section" data-section-id="test-section">
<div class="section-content">
@ -31,319 +31,319 @@ describe('Button Functionality and DOM Events', () => {
</div>
`;
mockSection = document.querySelector('.section');
mockSection = document.querySelector('.section');
// Load components
require('../components/document-controls.js');
if (global.DocumentControls) {
documentControls = new global.DocumentControls(document.getElementById('content'));
}
});
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
describe('Section edit buttons', () => {
test('should show accept/cancel buttons when edit is clicked', () => {
const editBtn = document.querySelector('.edit-btn');
const acceptBtn = document.querySelector('.accept-btn');
const cancelBtn = document.querySelector('.cancel-btn');
expect(editBtn).toBeTruthy();
// Simulate edit button click
editBtn.click();
// In real implementation, accept/cancel should become visible
expect(acceptBtn.style.display).toBe('none'); // Initially hidden
expect(cancelBtn.style.display).toBe('none'); // Initially hidden
// Test that buttons exist for functionality
expect(acceptBtn).toBeTruthy();
expect(cancelBtn).toBeTruthy();
// Load components
require('../components/document-controls.js');
if (global.DocumentControls) {
documentControls = new global.DocumentControls(document.getElementById('content'));
}
});
test('should hide edit button when in edit mode', () => {
const editBtn = document.querySelector('.edit-btn');
editBtn.click();
// In real implementation, edit button should be hidden
expect(editBtn.style.display).not.toBe('block');
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
test('should restore edit button when edit is cancelled', () => {
const editBtn = document.querySelector('.edit-btn');
const cancelBtn = document.querySelector('.cancel-btn');
describe('Section edit buttons', () => {
test('should show accept/cancel buttons when edit is clicked', () => {
const editBtn = document.querySelector('.edit-btn');
const acceptBtn = document.querySelector('.accept-btn');
const cancelBtn = document.querySelector('.cancel-btn');
// Simulate edit mode
editBtn.style.display = 'none';
cancelBtn.style.display = 'inline-block';
expect(editBtn).toBeTruthy();
cancelBtn.click();
// Simulate edit button click
editBtn.click();
// In real implementation, should restore edit button
expect(cancelBtn).toBeTruthy();
expect(editBtn).toBeTruthy();
// In real implementation, accept/cancel should become visible
expect(acceptBtn.style.display).toBe('none'); // Initially hidden
expect(cancelBtn.style.display).toBe('none'); // Initially hidden
// Test that buttons exist for functionality
expect(acceptBtn).toBeTruthy();
expect(cancelBtn).toBeTruthy();
});
test('should hide edit button when in edit mode', () => {
const editBtn = document.querySelector('.edit-btn');
editBtn.click();
// In real implementation, edit button should be hidden
expect(editBtn.style.display).not.toBe('block');
});
test('should restore edit button when edit is cancelled', () => {
const editBtn = document.querySelector('.edit-btn');
const cancelBtn = document.querySelector('.cancel-btn');
// Simulate edit mode
editBtn.style.display = 'none';
cancelBtn.style.display = 'inline-block';
cancelBtn.click();
// In real implementation, should restore edit button
expect(cancelBtn).toBeTruthy();
expect(editBtn).toBeTruthy();
});
});
});
describe('Button event propagation', () => {
test('should prevent event bubbling for section buttons', () => {
const editBtn = document.querySelector('.edit-btn');
let sectionClicked = false;
describe('Button event propagation', () => {
test('should prevent event bubbling for section buttons', () => {
const editBtn = document.querySelector('.edit-btn');
let sectionClicked = false;
mockSection.addEventListener('click', () => {
sectionClicked = true;
});
mockSection.addEventListener('click', () => {
sectionClicked = true;
});
// Create event with stopPropagation mock
const clickEvent = new Event('click', { bubbles: true });
clickEvent.stopPropagation = jest.fn();
// Create event with stopPropagation mock
const clickEvent = new Event('click', { bubbles: true });
clickEvent.stopPropagation = jest.fn();
editBtn.dispatchEvent(clickEvent);
editBtn.dispatchEvent(clickEvent);
// In real implementation, should call stopPropagation
expect(clickEvent.stopPropagation).toHaveBeenCalledWith ||
// In real implementation, should call stopPropagation
expect(clickEvent.stopPropagation).toHaveBeenCalledWith ||
expect(sectionClicked).toBe(false);
});
test('should handle rapid button clicks gracefully', () => {
const editBtn = document.querySelector('.edit-btn');
// Simulate rapid clicks
for (let i = 0; i < 5; i++) {
editBtn.click();
}
// Should not cause errors
expect(editBtn).toBeTruthy();
});
test('should debounce button actions', () => {
const saveBtn = document.querySelector('.save-all-btn');
let clickCount = 0;
const debouncedHandler = jest.fn(() => {
clickCount++;
});
saveBtn.addEventListener('click', debouncedHandler);
// Simulate multiple quick clicks
saveBtn.click();
saveBtn.click();
saveBtn.click();
expect(debouncedHandler).toHaveBeenCalledTimes(3);
});
});
test('should handle rapid button clicks gracefully', () => {
const editBtn = document.querySelector('.edit-btn');
describe('Button state management', () => {
test('should disable buttons during processing', () => {
const acceptBtn = document.querySelector('.accept-btn');
// Simulate rapid clicks
for (let i = 0; i < 5; i++) {
editBtn.click();
}
// Simulate processing state
acceptBtn.disabled = true;
// Should not cause errors
expect(editBtn).toBeTruthy();
expect(acceptBtn.disabled).toBe(true);
});
test('should show loading state for async operations', () => {
const saveBtn = document.querySelector('.save-all-btn');
// Simulate loading state
const originalText = saveBtn.textContent;
saveBtn.textContent = 'Saving...';
saveBtn.disabled = true;
expect(saveBtn.textContent).toBe('Saving...');
expect(saveBtn.disabled).toBe(true);
// Restore state
saveBtn.textContent = originalText;
saveBtn.disabled = false;
expect(saveBtn.textContent).toBe('Save All');
expect(saveBtn.disabled).toBe(false);
});
test('should maintain button visibility states', () => {
const buttons = {
edit: document.querySelector('.edit-btn'),
accept: document.querySelector('.accept-btn'),
cancel: document.querySelector('.cancel-btn')
};
// Default state: edit visible, accept/cancel hidden
expect(buttons.edit.style.display).not.toBe('none');
expect(buttons.accept.style.display).toBe('none');
expect(buttons.cancel.style.display).toBe('none');
});
});
test('should debounce button actions', () => {
const saveBtn = document.querySelector('.save-all-btn');
let clickCount = 0;
describe('DOM event handling', () => {
test('should handle click events correctly', () => {
const addSectionBtn = document.querySelector('.add-section-btn');
let clicked = false;
const debouncedHandler = jest.fn(() => {
clickCount++;
});
addSectionBtn.addEventListener('click', () => {
clicked = true;
});
saveBtn.addEventListener('click', debouncedHandler);
addSectionBtn.click();
// Simulate multiple quick clicks
saveBtn.click();
saveBtn.click();
saveBtn.click();
expect(clicked).toBe(true);
});
expect(debouncedHandler).toHaveBeenCalledTimes(3);
});
});
test('should handle keyboard events for accessibility', () => {
const editBtn = document.querySelector('.edit-btn');
let keyPressed = false;
describe('Button state management', () => {
test('should disable buttons during processing', () => {
const acceptBtn = document.querySelector('.accept-btn');
editBtn.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
keyPressed = true;
}
});
// Simulate processing state
acceptBtn.disabled = true;
// Simulate Enter key press
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true
});
expect(acceptBtn.disabled).toBe(true);
editBtn.dispatchEvent(enterEvent);
expect(keyPressed).toBe(true);
});
test('should handle focus and blur events', () => {
const editBtn = document.querySelector('.edit-btn');
let focused = false;
let blurred = false;
editBtn.addEventListener('focus', () => {
focused = true;
});
editBtn.addEventListener('blur', () => {
blurred = true;
});
editBtn.focus();
expect(focused).toBe(true);
editBtn.blur();
expect(blurred).toBe(true);
});
});
test('should show loading state for async operations', () => {
const saveBtn = document.querySelector('.save-all-btn');
describe('Button positioning and layout', () => {
test('should position floating controls correctly', () => {
const floatingControls = document.querySelector('.floating-controls');
// Simulate loading state
const originalText = saveBtn.textContent;
saveBtn.textContent = 'Saving...';
saveBtn.disabled = true;
// Test positioning properties
floatingControls.style.position = 'fixed';
floatingControls.style.top = '20px';
floatingControls.style.right = '20px';
expect(saveBtn.textContent).toBe('Saving...');
expect(saveBtn.disabled).toBe(true);
expect(floatingControls.style.position).toBe('fixed');
expect(floatingControls.style.top).toBe('20px');
expect(floatingControls.style.right).toBe('20px');
});
// Restore state
saveBtn.textContent = originalText;
saveBtn.disabled = false;
test('should handle responsive button layouts', () => {
const sectionControls = document.querySelector('.section-controls');
expect(saveBtn.textContent).toBe('Save All');
expect(saveBtn.disabled).toBe(false);
// Test responsive classes
sectionControls.classList.add('responsive-controls');
expect(sectionControls.classList.contains('responsive-controls')).toBe(true);
});
test('should maintain button alignment in sections', () => {
const controls = document.querySelector('.section-controls');
const buttons = controls.querySelectorAll('button');
expect(buttons.length).toBeGreaterThan(0);
// All buttons should be in the same container
buttons.forEach(button => {
expect(button.parentElement).toBe(controls);
});
});
});
test('should maintain button visibility states', () => {
const buttons = {
edit: document.querySelector('.edit-btn'),
accept: document.querySelector('.accept-btn'),
cancel: document.querySelector('.cancel-btn')
};
describe('Button confirmation dialogs', () => {
test('should show confirmation for destructive actions', () => {
const deleteBtn = document.querySelector('.delete-btn');
// Default state: edit visible, accept/cancel hidden
expect(buttons.edit.style.display).not.toBe('none');
expect(buttons.accept.style.display).toBe('none');
expect(buttons.cancel.style.display).toBe('none');
});
});
// Mock confirm dialog
window.confirm = jest.fn(() => false);
describe('DOM event handling', () => {
test('should handle click events correctly', () => {
const addSectionBtn = document.querySelector('.add-section-btn');
let clicked = false;
deleteBtn.addEventListener('click', () => {
if (window.confirm('Are you sure you want to delete this section?')) {
// Perform deletion
}
});
addSectionBtn.addEventListener('click', () => {
clicked = true;
});
deleteBtn.click();
addSectionBtn.click();
// Should show confirmation
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete this section?');
});
expect(clicked).toBe(true);
test('should cancel action when confirmation is denied', () => {
const deleteBtn = document.querySelector('.delete-btn');
let deleted = false;
window.confirm = jest.fn(() => false);
deleteBtn.addEventListener('click', () => {
if (window.confirm('Are you sure?')) {
deleted = true;
}
});
deleteBtn.click();
expect(deleted).toBe(false);
});
});
test('should handle keyboard events for accessibility', () => {
const editBtn = document.querySelector('.edit-btn');
let keyPressed = false;
describe('DocumentControls integration', () => {
test('should integrate with DocumentControls class', () => {
if (documentControls) {
expect(typeof documentControls.create).toBe('function');
expect(typeof documentControls.addButton).toBe('function');
expect(typeof documentControls.setEventHandlers).toBe('function');
}
});
editBtn.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
keyPressed = true;
}
});
test('should handle button events through DocumentControls', () => {
if (!documentControls) return;
// Simulate Enter key press
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true
});
// Test that DocumentControls can manage event handlers
expect(documentControls.eventHandlers).toBeDefined();
expect(documentControls.eventHandlers instanceof Map).toBe(true);
});
editBtn.dispatchEvent(enterEvent);
test('should handle button actions through event delegation', () => {
const content = document.getElementById('content');
let actionTriggered = '';
expect(keyPressed).toBe(true);
content.addEventListener('click', (event) => {
if (event.target.matches('button[data-action]')) {
actionTriggered = event.target.getAttribute('data-action');
}
});
const editBtn = document.querySelector('.edit-btn');
editBtn.click();
expect(actionTriggered).toBe('edit');
});
});
test('should handle focus and blur events', () => {
const editBtn = document.querySelector('.edit-btn');
let focused = false;
let blurred = false;
editBtn.addEventListener('focus', () => {
focused = true;
});
editBtn.addEventListener('blur', () => {
blurred = true;
});
editBtn.focus();
expect(focused).toBe(true);
editBtn.blur();
expect(blurred).toBe(true);
});
});
describe('Button positioning and layout', () => {
test('should position floating controls correctly', () => {
const floatingControls = document.querySelector('.floating-controls');
// Test positioning properties
floatingControls.style.position = 'fixed';
floatingControls.style.top = '20px';
floatingControls.style.right = '20px';
expect(floatingControls.style.position).toBe('fixed');
expect(floatingControls.style.top).toBe('20px');
expect(floatingControls.style.right).toBe('20px');
});
test('should handle responsive button layouts', () => {
const sectionControls = document.querySelector('.section-controls');
// Test responsive classes
sectionControls.classList.add('responsive-controls');
expect(sectionControls.classList.contains('responsive-controls')).toBe(true);
});
test('should maintain button alignment in sections', () => {
const controls = document.querySelector('.section-controls');
const buttons = controls.querySelectorAll('button');
expect(buttons.length).toBeGreaterThan(0);
// All buttons should be in the same container
buttons.forEach(button => {
expect(button.parentElement).toBe(controls);
});
});
});
describe('Button confirmation dialogs', () => {
test('should show confirmation for destructive actions', () => {
const deleteBtn = document.querySelector('.delete-btn');
// Mock confirm dialog
window.confirm = jest.fn(() => false);
deleteBtn.addEventListener('click', () => {
if (window.confirm('Are you sure you want to delete this section?')) {
// Perform deletion
}
});
deleteBtn.click();
// Should show confirmation
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete this section?');
});
test('should cancel action when confirmation is denied', () => {
const deleteBtn = document.querySelector('.delete-btn');
let deleted = false;
window.confirm = jest.fn(() => false);
deleteBtn.addEventListener('click', () => {
if (window.confirm('Are you sure?')) {
deleted = true;
}
});
deleteBtn.click();
expect(deleted).toBe(false);
});
});
describe('DocumentControls integration', () => {
test('should integrate with DocumentControls class', () => {
if (documentControls) {
expect(typeof documentControls.create).toBe('function');
expect(typeof documentControls.addButton).toBe('function');
expect(typeof documentControls.setEventHandlers).toBe('function');
}
});
test('should handle button events through DocumentControls', () => {
if (!documentControls) return;
// Test that DocumentControls can manage event handlers
expect(documentControls.eventHandlers).toBeDefined();
expect(documentControls.eventHandlers instanceof Map).toBe(true);
});
test('should handle button actions through event delegation', () => {
const content = document.getElementById('content');
let actionTriggered = '';
content.addEventListener('click', (event) => {
if (event.target.matches('button[data-action]')) {
actionTriggered = event.target.getAttribute('data-action');
}
});
const editBtn = document.querySelector('.edit-btn');
editBtn.click();
expect(actionTriggered).toBe('edit');
});
});
});
});

View file

@ -83,4 +83,4 @@ This is test content.
domRenderer.showEditor(mockSection.id);
}).not.toThrow();
});
});
});

View file

@ -6,12 +6,12 @@
*/
describe('Image Editing', () => {
let mockImageSection;
let mockImageElement;
let mockImageSection;
let mockImageElement;
beforeEach(() => {
beforeEach(() => {
// Setup DOM with image section
document.body.innerHTML = `
document.body.innerHTML = `
<div id="content">
<div class="section image-section" data-section-id="image-section-1">
<div class="section-content">
@ -31,250 +31,250 @@ describe('Image Editing', () => {
</div>
`;
mockImageSection = document.querySelector('.image-section');
mockImageElement = document.querySelector('.section-image');
});
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
describe('Image editor dialog', () => {
test('should show image editor when edit button is clicked', () => {
const editButton = document.querySelector('.edit-image-btn');
const dialog = document.querySelector('.image-editor-dialog');
expect(editButton).toBeTruthy();
expect(dialog).toBeTruthy();
// Simulate edit button click
editButton.click();
// In real implementation, dialog should become visible
expect(dialog.style.display).toBe('none'); // Initially hidden
mockImageSection = document.querySelector('.image-section');
mockImageElement = document.querySelector('.section-image');
});
test('should populate current alt text and caption', () => {
const altTextInput = document.querySelector('.alt-text-input');
const captionInput = document.querySelector('.image-caption');
expect(altTextInput).toBeTruthy();
expect(captionInput).toBeTruthy();
// Simulate populating current values
const currentAlt = mockImageElement.alt;
altTextInput.value = currentAlt;
expect(altTextInput.value).toBe(currentAlt);
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
test('should handle dialog positioning correctly', () => {
const dialog = document.querySelector('.image-editor-dialog');
describe('Image editor dialog', () => {
test('should show image editor when edit button is clicked', () => {
const editButton = document.querySelector('.edit-image-btn');
const dialog = document.querySelector('.image-editor-dialog');
// Test that dialog positioning can be set
dialog.style.position = 'absolute';
dialog.style.top = '100px';
dialog.style.left = '100px';
expect(editButton).toBeTruthy();
expect(dialog).toBeTruthy();
expect(dialog.style.position).toBe('absolute');
expect(dialog.style.top).toBe('100px');
expect(dialog.style.left).toBe('100px');
});
});
// Simulate edit button click
editButton.click();
describe('Image modifications', () => {
test('should update alt text when applied', () => {
const altTextInput = document.querySelector('.alt-text-input');
const applyButton = document.querySelector('.apply-image-changes');
// In real implementation, dialog should become visible
expect(dialog.style.display).toBe('none'); // Initially hidden
});
const newAltText = 'Updated alt text for image';
altTextInput.value = newAltText;
test('should populate current alt text and caption', () => {
const altTextInput = document.querySelector('.alt-text-input');
const captionInput = document.querySelector('.image-caption');
// Simulate apply action
applyButton.click();
expect(altTextInput).toBeTruthy();
expect(captionInput).toBeTruthy();
// In real implementation, image alt text should be updated
expect(altTextInput.value).toBe(newAltText);
// Simulate populating current values
const currentAlt = mockImageElement.alt;
altTextInput.value = currentAlt;
expect(altTextInput.value).toBe(currentAlt);
});
test('should handle dialog positioning correctly', () => {
const dialog = document.querySelector('.image-editor-dialog');
// Test that dialog positioning can be set
dialog.style.position = 'absolute';
dialog.style.top = '100px';
dialog.style.left = '100px';
expect(dialog.style.position).toBe('absolute');
expect(dialog.style.top).toBe('100px');
expect(dialog.style.left).toBe('100px');
});
});
test('should update image caption when applied', () => {
const captionInput = document.querySelector('.image-caption');
const newCaption = 'Updated image caption';
describe('Image modifications', () => {
test('should update alt text when applied', () => {
const altTextInput = document.querySelector('.alt-text-input');
const applyButton = document.querySelector('.apply-image-changes');
captionInput.value = newCaption;
const newAltText = 'Updated alt text for image';
altTextInput.value = newAltText;
expect(captionInput.value).toBe(newCaption);
// Simulate apply action
applyButton.click();
// In real implementation, image alt text should be updated
expect(altTextInput.value).toBe(newAltText);
});
test('should update image caption when applied', () => {
const captionInput = document.querySelector('.image-caption');
const newCaption = 'Updated image caption';
captionInput.value = newCaption;
expect(captionInput.value).toBe(newCaption);
});
test('should validate required fields', () => {
const altTextInput = document.querySelector('.alt-text-input');
// Test empty alt text validation
altTextInput.value = '';
const isEmpty = altTextInput.value.trim() === '';
expect(isEmpty).toBe(true);
// Test filled alt text
altTextInput.value = 'Valid alt text';
const isFilled = altTextInput.value.trim() !== '';
expect(isFilled).toBe(true);
});
});
test('should validate required fields', () => {
const altTextInput = document.querySelector('.alt-text-input');
describe('Image reset functionality', () => {
test('should reset image to original state', () => {
const resetButton = document.querySelector('.reset-image-btn');
const altTextInput = document.querySelector('.alt-text-input');
// Test empty alt text validation
altTextInput.value = '';
// Store original values
const originalAlt = mockImageElement.alt;
const isEmpty = altTextInput.value.trim() === '';
expect(isEmpty).toBe(true);
// Modify values
altTextInput.value = 'Modified alt text';
mockImageElement.alt = 'Modified alt';
// Test filled alt text
altTextInput.value = 'Valid alt text';
const isFilled = altTextInput.value.trim() !== '';
expect(isFilled).toBe(true);
});
});
// Simulate reset
resetButton.click();
describe('Image reset functionality', () => {
test('should reset image to original state', () => {
const resetButton = document.querySelector('.reset-image-btn');
const altTextInput = document.querySelector('.alt-text-input');
// In real implementation, should restore original values
expect(resetButton).toBeTruthy();
});
// Store original values
const originalAlt = mockImageElement.alt;
test('should confirm before resetting changes', () => {
const resetButton = document.querySelector('.reset-image-btn');
// Modify values
altTextInput.value = 'Modified alt text';
mockImageElement.alt = 'Modified alt';
// Mock confirm dialog
window.confirm = jest.fn(() => true);
// Simulate reset
resetButton.click();
resetButton.click();
// In real implementation, should restore original values
expect(resetButton).toBeTruthy();
// In real implementation, should show confirmation
expect(resetButton).toBeTruthy();
});
test('should preserve original image data', () => {
// Test that original image data is stored
const originalData = {
src: mockImageElement.src,
alt: mockImageElement.alt,
caption: ''
};
expect(originalData.src).toBeTruthy();
expect(typeof originalData.alt).toBe('string');
expect(typeof originalData.caption).toBe('string');
});
});
test('should confirm before resetting changes', () => {
const resetButton = document.querySelector('.reset-image-btn');
describe('Image editor UI controls', () => {
test('should handle cancel button correctly', () => {
const cancelButton = document.querySelector('.cancel-image-changes');
const dialog = document.querySelector('.image-editor-dialog');
// Mock confirm dialog
window.confirm = jest.fn(() => true);
cancelButton.click();
resetButton.click();
// In real implementation, should close dialog without saving
expect(cancelButton).toBeTruthy();
expect(dialog).toBeTruthy();
});
// In real implementation, should show confirmation
expect(resetButton).toBeTruthy();
test('should close dialog after applying changes', () => {
const applyButton = document.querySelector('.apply-image-changes');
const dialog = document.querySelector('.image-editor-dialog');
applyButton.click();
// In real implementation, should close dialog after applying
expect(applyButton).toBeTruthy();
expect(dialog.style.display).toBe('none');
});
test('should handle escape key to cancel', () => {
const dialog = document.querySelector('.image-editor-dialog');
const altTextInput = document.querySelector('.alt-text-input');
// Simulate escape key press
const escapeEvent = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true
});
altTextInput.dispatchEvent(escapeEvent);
// In real implementation, should close dialog
expect(dialog).toBeTruthy();
});
});
test('should preserve original image data', () => {
// Test that original image data is stored
const originalData = {
src: mockImageElement.src,
alt: mockImageElement.alt,
caption: ''
};
describe('Advanced image editor features', () => {
test('should support image URL editing', () => {
const imageUrl = mockImageElement.src;
expect(originalData.src).toBeTruthy();
expect(typeof originalData.alt).toBe('string');
expect(typeof originalData.caption).toBe('string');
});
});
// Test URL validation
const isValidUrl = /^https?:\/\//.test(imageUrl) || imageUrl.startsWith('/') || imageUrl.startsWith('./');
describe('Image editor UI controls', () => {
test('should handle cancel button correctly', () => {
const cancelButton = document.querySelector('.cancel-image-changes');
const dialog = document.querySelector('.image-editor-dialog');
// Local files and URLs should be valid
expect(typeof imageUrl).toBe('string');
});
cancelButton.click();
test('should handle image loading errors', () => {
const errorHandler = jest.fn();
// In real implementation, should close dialog without saving
expect(cancelButton).toBeTruthy();
expect(dialog).toBeTruthy();
mockImageElement.onerror = errorHandler;
mockImageElement.src = 'invalid-image-url.jpg';
// In real implementation, should handle image load errors
expect(mockImageElement.onerror).toBe(errorHandler);
});
test('should support image alignment options', () => {
const alignmentOptions = ['left', 'center', 'right', 'full-width'];
alignmentOptions.forEach(alignment => {
mockImageElement.className = `section-image align-${alignment}`;
expect(mockImageElement.className).toContain(`align-${alignment}`);
});
});
test('should handle responsive image sizing', () => {
// Test responsive image attributes
mockImageElement.style.maxWidth = '100%';
mockImageElement.style.height = 'auto';
expect(mockImageElement.style.maxWidth).toBe('100%');
expect(mockImageElement.style.height).toBe('auto');
});
});
test('should close dialog after applying changes', () => {
const applyButton = document.querySelector('.apply-image-changes');
const dialog = document.querySelector('.image-editor-dialog');
describe('Image section integration', () => {
test('should maintain section integrity during image editing', () => {
const sectionId = mockImageSection.getAttribute('data-section-id');
applyButton.click();
expect(sectionId).toBeTruthy();
expect(mockImageSection.classList.contains('image-section')).toBe(true);
});
// In real implementation, should close dialog after applying
expect(applyButton).toBeTruthy();
expect(dialog.style.display).toBe('none');
test('should handle multiple images in one section', () => {
// Add another image to the section
const secondImage = document.createElement('img');
secondImage.src = 'second-image.jpg';
secondImage.alt = 'Second image';
secondImage.className = 'section-image';
mockImageSection.querySelector('.section-content').appendChild(secondImage);
const images = mockImageSection.querySelectorAll('.section-image');
expect(images.length).toBe(2);
});
test('should preserve section order when editing images', () => {
const sectionContent = mockImageSection.querySelector('.section-content');
const children = Array.from(sectionContent.children);
const imageIndex = children.findIndex(child => child.tagName === 'IMG');
expect(imageIndex).toBeGreaterThanOrEqual(0);
});
});
test('should handle escape key to cancel', () => {
const dialog = document.querySelector('.image-editor-dialog');
const altTextInput = document.querySelector('.alt-text-input');
// Simulate escape key press
const escapeEvent = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true
});
altTextInput.dispatchEvent(escapeEvent);
// In real implementation, should close dialog
expect(dialog).toBeTruthy();
});
});
describe('Advanced image editor features', () => {
test('should support image URL editing', () => {
const imageUrl = mockImageElement.src;
// Test URL validation
const isValidUrl = /^https?:\/\//.test(imageUrl) || imageUrl.startsWith('/') || imageUrl.startsWith('./');
// Local files and URLs should be valid
expect(typeof imageUrl).toBe('string');
});
test('should handle image loading errors', () => {
const errorHandler = jest.fn();
mockImageElement.onerror = errorHandler;
mockImageElement.src = 'invalid-image-url.jpg';
// In real implementation, should handle image load errors
expect(mockImageElement.onerror).toBe(errorHandler);
});
test('should support image alignment options', () => {
const alignmentOptions = ['left', 'center', 'right', 'full-width'];
alignmentOptions.forEach(alignment => {
mockImageElement.className = `section-image align-${alignment}`;
expect(mockImageElement.className).toContain(`align-${alignment}`);
});
});
test('should handle responsive image sizing', () => {
// Test responsive image attributes
mockImageElement.style.maxWidth = '100%';
mockImageElement.style.height = 'auto';
expect(mockImageElement.style.maxWidth).toBe('100%');
expect(mockImageElement.style.height).toBe('auto');
});
});
describe('Image section integration', () => {
test('should maintain section integrity during image editing', () => {
const sectionId = mockImageSection.getAttribute('data-section-id');
expect(sectionId).toBeTruthy();
expect(mockImageSection.classList.contains('image-section')).toBe(true);
});
test('should handle multiple images in one section', () => {
// Add another image to the section
const secondImage = document.createElement('img');
secondImage.src = 'second-image.jpg';
secondImage.alt = 'Second image';
secondImage.className = 'section-image';
mockImageSection.querySelector('.section-content').appendChild(secondImage);
const images = mockImageSection.querySelectorAll('.section-image');
expect(images.length).toBe(2);
});
test('should preserve section order when editing images', () => {
const sectionContent = mockImageSection.querySelector('.section-content');
const children = Array.from(sectionContent.children);
const imageIndex = children.findIndex(child => child.tagName === 'IMG');
expect(imageIndex).toBeGreaterThanOrEqual(0);
});
});
});
});

View file

@ -23,4 +23,4 @@ console.log = (...args) => {
beforeEach(() => {
// Reset document body for each test
document.body.innerHTML = '<div id="content"></div>';
});
});

View file

@ -6,12 +6,12 @@
*/
describe('Keyboard Shortcuts', () => {
let domRenderer;
let mockTextarea;
let domRenderer;
let mockTextarea;
beforeEach(() => {
beforeEach(() => {
// Setup DOM
document.body.innerHTML = `
document.body.innerHTML = `
<div id="content">
<div class="section" data-section-id="test-section">
<textarea class="edit-textarea">Test content</textarea>
@ -19,201 +19,201 @@ describe('Keyboard Shortcuts', () => {
</div>
`;
// Load components
require('../components/dom-renderer.js');
require('../core/section-manager.js');
// Load components
require('../components/dom-renderer.js');
require('../core/section-manager.js');
// Mock SectionManager with event system
const mockSectionManager = {
on: jest.fn(),
emit: jest.fn(),
handleSectionSplit: jest.fn(),
sections: []
};
if (global.DOMRenderer) {
// Create DOMRenderer with mocked dependencies
try {
domRenderer = new global.DOMRenderer(mockSectionManager, document.getElementById('content'));
} catch (error) {
// If constructor fails, create a mock with the methods we need
domRenderer = {
applyChanges: jest.fn(),
cancelEdit: jest.fn()
// Mock SectionManager with event system
const mockSectionManager = {
on: jest.fn(),
emit: jest.fn(),
handleSectionSplit: jest.fn(),
sections: []
};
}
}
mockTextarea = document.querySelector('.edit-textarea');
});
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
describe('Ctrl+Enter shortcut (Accept Changes)', () => {
test('should apply changes when Ctrl+Enter is pressed', () => {
if (!mockTextarea) {
console.warn('Textarea not available, skipping test');
return;
}
// Test that Ctrl+Enter event can be dispatched
const ctrlEnterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
ctrlKey: true,
bubbles: true
});
let eventFired = false;
mockTextarea.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
eventFired = true;
if (global.DOMRenderer) {
// Create DOMRenderer with mocked dependencies
try {
domRenderer = new global.DOMRenderer(mockSectionManager, document.getElementById('content'));
} catch (error) {
// If constructor fails, create a mock with the methods we need
domRenderer = {
applyChanges: jest.fn(),
cancelEdit: jest.fn()
};
}
}
});
mockTextarea.dispatchEvent(ctrlEnterEvent);
// Verify event was handled
expect(eventFired).toBe(true);
mockTextarea = document.querySelector('.edit-textarea');
});
test('should prevent default behavior on Ctrl+Enter', () => {
if (!mockTextarea) return;
const preventDefault = jest.fn();
const ctrlEnterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
ctrlKey: true,
bubbles: true
});
// Mock preventDefault
ctrlEnterEvent.preventDefault = preventDefault;
mockTextarea.dispatchEvent(ctrlEnterEvent);
// Note: In real implementation, preventDefault should be called
// This test documents the expected behavior
expect(true).toBe(true); // Placeholder for actual implementation check
});
});
describe('Escape shortcut (Cancel Changes)', () => {
test('should cancel changes when Escape is pressed', () => {
if (!mockTextarea) {
console.warn('Textarea not available, skipping test');
return;
}
// Test that Escape event can be dispatched
const escapeEvent = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true
});
let escapePressed = false;
mockTextarea.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
escapePressed = true;
}
});
mockTextarea.dispatchEvent(escapeEvent);
// Verify escape was detected
expect(escapePressed).toBe(true);
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
test('should restore original content on Escape', () => {
if (!mockTextarea) return;
describe('Ctrl+Enter shortcut (Accept Changes)', () => {
test('should apply changes when Ctrl+Enter is pressed', () => {
if (!mockTextarea) {
console.warn('Textarea not available, skipping test');
return;
}
const originalContent = 'Original content';
mockTextarea.setAttribute('data-original-content', originalContent);
mockTextarea.value = 'Modified content';
// Test that Ctrl+Enter event can be dispatched
const ctrlEnterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
ctrlKey: true,
bubbles: true
});
const escapeEvent = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true
});
let eventFired = false;
mockTextarea.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
eventFired = true;
}
});
mockTextarea.dispatchEvent(escapeEvent);
mockTextarea.dispatchEvent(ctrlEnterEvent);
// In real implementation, content should be restored
// This test documents the expected behavior
expect(mockTextarea.getAttribute('data-original-content')).toBe(originalContent);
});
});
describe('Keyboard shortcuts integration', () => {
test('should bind keyboard handlers to textareas', () => {
const textarea = document.createElement('textarea');
textarea.className = 'edit-textarea';
document.body.appendChild(textarea);
// Check if event listeners can be added (integration test)
let listenerAdded = false;
const originalAddEventListener = textarea.addEventListener;
textarea.addEventListener = jest.fn((event, handler) => {
if (event === 'keydown') {
listenerAdded = true;
}
return originalAddEventListener.call(textarea, event, handler);
});
// In real implementation, DOMRenderer should bind keydown listeners
// This test ensures the capability exists
expect(textarea.addEventListener).toBeDefined();
expect(typeof textarea.addEventListener).toBe('function');
});
test('should handle multiple keyboard events correctly', () => {
if (!mockTextarea) return;
const events = [
{ key: 'Enter', ctrlKey: true },
{ key: 'Escape', ctrlKey: false },
{ key: 'Tab', ctrlKey: false }
];
events.forEach(eventData => {
const event = new KeyboardEvent('keydown', {
...eventData,
bubbles: true
// Verify event was handled
expect(eventFired).toBe(true);
});
// Should not throw errors when handling various key events
expect(() => {
mockTextarea.dispatchEvent(event);
}).not.toThrow();
});
});
});
test('should prevent default behavior on Ctrl+Enter', () => {
if (!mockTextarea) return;
describe('Keyboard shortcuts accessibility', () => {
test('should provide keyboard alternatives to mouse actions', () => {
// This test ensures keyboard accessibility is maintained
const shortcuts = [
{ key: 'Enter', ctrlKey: true, action: 'apply' },
{ key: 'Escape', ctrlKey: false, action: 'cancel' }
];
const preventDefault = jest.fn();
const ctrlEnterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
ctrlKey: true,
bubbles: true
});
shortcuts.forEach(shortcut => {
expect(shortcut.key).toBeDefined();
expect(shortcut.action).toBeDefined();
});
// Mock preventDefault
ctrlEnterEvent.preventDefault = preventDefault;
mockTextarea.dispatchEvent(ctrlEnterEvent);
// Note: In real implementation, preventDefault should be called
// This test documents the expected behavior
expect(true).toBe(true); // Placeholder for actual implementation check
});
});
test('should work with screen readers and assistive technology', () => {
if (!mockTextarea) return;
describe('Escape shortcut (Cancel Changes)', () => {
test('should cancel changes when Escape is pressed', () => {
if (!mockTextarea) {
console.warn('Textarea not available, skipping test');
return;
}
// Test ARIA attributes and accessibility features
mockTextarea.setAttribute('aria-label', 'Edit section content');
mockTextarea.setAttribute('role', 'textbox');
// Test that Escape event can be dispatched
const escapeEvent = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true
});
expect(mockTextarea.getAttribute('aria-label')).toBeTruthy();
expect(mockTextarea.getAttribute('role')).toBe('textbox');
let escapePressed = false;
mockTextarea.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
escapePressed = true;
}
});
mockTextarea.dispatchEvent(escapeEvent);
// Verify escape was detected
expect(escapePressed).toBe(true);
});
test('should restore original content on Escape', () => {
if (!mockTextarea) return;
const originalContent = 'Original content';
mockTextarea.setAttribute('data-original-content', originalContent);
mockTextarea.value = 'Modified content';
const escapeEvent = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true
});
mockTextarea.dispatchEvent(escapeEvent);
// In real implementation, content should be restored
// This test documents the expected behavior
expect(mockTextarea.getAttribute('data-original-content')).toBe(originalContent);
});
});
});
});
describe('Keyboard shortcuts integration', () => {
test('should bind keyboard handlers to textareas', () => {
const textarea = document.createElement('textarea');
textarea.className = 'edit-textarea';
document.body.appendChild(textarea);
// Check if event listeners can be added (integration test)
let listenerAdded = false;
const originalAddEventListener = textarea.addEventListener;
textarea.addEventListener = jest.fn((event, handler) => {
if (event === 'keydown') {
listenerAdded = true;
}
return originalAddEventListener.call(textarea, event, handler);
});
// In real implementation, DOMRenderer should bind keydown listeners
// This test ensures the capability exists
expect(textarea.addEventListener).toBeDefined();
expect(typeof textarea.addEventListener).toBe('function');
});
test('should handle multiple keyboard events correctly', () => {
if (!mockTextarea) return;
const events = [
{ key: 'Enter', ctrlKey: true },
{ key: 'Escape', ctrlKey: false },
{ key: 'Tab', ctrlKey: false }
];
events.forEach(eventData => {
const event = new KeyboardEvent('keydown', {
...eventData,
bubbles: true
});
// Should not throw errors when handling various key events
expect(() => {
mockTextarea.dispatchEvent(event);
}).not.toThrow();
});
});
});
describe('Keyboard shortcuts accessibility', () => {
test('should provide keyboard alternatives to mouse actions', () => {
// This test ensures keyboard accessibility is maintained
const shortcuts = [
{ key: 'Enter', ctrlKey: true, action: 'apply' },
{ key: 'Escape', ctrlKey: false, action: 'cancel' }
];
shortcuts.forEach(shortcut => {
expect(shortcut.key).toBeDefined();
expect(shortcut.action).toBeDefined();
});
});
test('should work with screen readers and assistive technology', () => {
if (!mockTextarea) return;
// Test ARIA attributes and accessibility features
mockTextarea.setAttribute('aria-label', 'Edit section content');
mockTextarea.setAttribute('role', 'textbox');
expect(mockTextarea.getAttribute('aria-label')).toBeTruthy();
expect(mockTextarea.getAttribute('role')).toBe('textbox');
});
});
});

View file

@ -189,7 +189,7 @@ class RefactorTestRunner {
const endTime = Date.now();
const duration = endTime - startTime;
console.log(`\n📊 Test Results:`);
console.log('\n📊 Test Results:');
console.log(` ✅ Passed: ${this.passed}`);
console.log(` ❌ Failed: ${this.failed}`);
console.log(` ⏱️ Duration: ${duration}ms`);
@ -198,7 +198,7 @@ class RefactorTestRunner {
console.log(`\n❌ ${this.failed} test(s) failed. Refactoring should not proceed.`);
process.exit(1);
} else {
console.log(`\n✅ All tests passed! Refactoring is safe to continue.`);
console.log('\n✅ All tests passed! Refactoring is safe to continue.');
}
}
}
@ -213,4 +213,4 @@ if (typeof window !== 'undefined') {
window.RefactorTestRunner = RefactorTestRunner;
}
module.exports = RefactorTestRunner;
module.exports = RefactorTestRunner;

View file

@ -6,11 +6,11 @@
*/
describe('Section Splitting', () => {
let sectionManager;
let sectionManager;
beforeEach(() => {
beforeEach(() => {
// Setup DOM
document.body.innerHTML = `
document.body.innerHTML = `
<div id="content">
<div class="section" data-section-id="main-section">
<div class="section-content">
@ -20,22 +20,22 @@ describe('Section Splitting', () => {
</div>
`;
// Load components
require('../core/section-manager.js');
// Load components
require('../core/section-manager.js');
if (global.SectionManager) {
sectionManager = new global.SectionManager();
}
});
if (global.SectionManager) {
sectionManager = new global.SectionManager();
}
});
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
describe('Heading detection', () => {
test('should detect new headings in content', () => {
const textWithHeading = `
describe('Heading detection', () => {
test('should detect new headings in content', () => {
const textWithHeading = `
This is some content.
# New Heading
@ -43,49 +43,49 @@ describe('Section Splitting', () => {
This should be a new section.
`;
// Test heading detection with regex
const lines = textWithHeading.trim().split('\n');
const headingLine = lines.find(line => /^#+ /.test(line.trim()));
expect(headingLine).toBeTruthy();
expect(headingLine.trim()).toBe('# New Heading');
// Test heading detection with regex
const lines = textWithHeading.trim().split('\n');
const headingLine = lines.find(line => /^#+ /.test(line.trim()));
expect(headingLine).toBeTruthy();
expect(headingLine.trim()).toBe('# New Heading');
});
test('should identify different heading levels', () => {
const headingTests = [
{ text: '# Heading 1', level: 1 },
{ text: '## Heading 2', level: 2 },
{ text: '### Heading 3', level: 3 },
{ text: '#### Heading 4', level: 4 }
];
headingTests.forEach(({ text, level }) => {
const match = text.match(/^(#+) /);
expect(match).toBeTruthy();
if (match) {
expect(match[1].length).toBe(level);
}
});
});
test('should distinguish headings from regular text', () => {
const testCases = [
{ text: '# This is a heading', isHeading: true },
{ text: 'This is not a heading', isHeading: false },
{ text: 'Neither is this # hash in middle', isHeading: false },
{ text: '## Another heading', isHeading: true }
];
testCases.forEach(({ text, isHeading }) => {
const match = /^#+\s/.test(text.trim());
expect(match).toBe(isHeading);
});
});
});
test('should identify different heading levels', () => {
const headingTests = [
{ text: '# Heading 1', level: 1 },
{ text: '## Heading 2', level: 2 },
{ text: '### Heading 3', level: 3 },
{ text: '#### Heading 4', level: 4 }
];
headingTests.forEach(({ text, level }) => {
const match = text.match(/^(#+) /);
expect(match).toBeTruthy();
if (match) {
expect(match[1].length).toBe(level);
}
});
});
test('should distinguish headings from regular text', () => {
const testCases = [
{ text: '# This is a heading', isHeading: true },
{ text: 'This is not a heading', isHeading: false },
{ text: 'Neither is this # hash in middle', isHeading: false },
{ text: '## Another heading', isHeading: true }
];
testCases.forEach(({ text, isHeading }) => {
const match = /^#+\s/.test(text.trim());
expect(match).toBe(isHeading);
});
});
});
describe('Section splitting logic', () => {
test('should split content when heading is detected', () => {
const originalContent = 'Original content without headings';
const newContent = `
describe('Section splitting logic', () => {
test('should split content when heading is detected', () => {
const originalContent = 'Original content without headings';
const newContent = `
${originalContent}
# New Section
@ -93,26 +93,26 @@ describe('Section Splitting', () => {
New section content
`;
// Simulate section splitting logic
const parts = newContent.split(/\n(?=#)/);
// Simulate section splitting logic
const parts = newContent.split(/\n(?=#)/);
if (parts.length > 1) {
expect(parts.length).toBeGreaterThan(1);
expect(parts[0]).toContain('Original content');
expect(parts[1]).toContain('# New Section');
}
});
if (parts.length > 1) {
expect(parts.length).toBeGreaterThan(1);
expect(parts[0]).toContain('Original content');
expect(parts[1]).toContain('# New Section');
}
});
test('should preserve content when no headings are present', () => {
const content = 'Just regular content without any headings';
const parts = content.split(/\n(?=#)/);
test('should preserve content when no headings are present', () => {
const content = 'Just regular content without any headings';
const parts = content.split(/\n(?=#)/);
expect(parts.length).toBe(1);
expect(parts[0]).toBe(content);
});
expect(parts.length).toBe(1);
expect(parts[0]).toBe(content);
});
test('should handle multiple headings correctly', () => {
const contentWithMultipleHeadings = `Initial content
test('should handle multiple headings correctly', () => {
const contentWithMultipleHeadings = `Initial content
# First Heading
First section content
@ -123,48 +123,48 @@ Second section content
# Third Heading
Third section content`;
// Split on lines that start with headings
const parts = contentWithMultipleHeadings.split(/\n(?=#)/);
// Split on lines that start with headings
const parts = contentWithMultipleHeadings.split(/\n(?=#)/);
// Should split into multiple sections
expect(parts.length).toBeGreaterThanOrEqual(2);
// Should split into multiple sections
expect(parts.length).toBeGreaterThanOrEqual(2);
// Find heading lines
const headings = contentWithMultipleHeadings.match(/^#+.*$/gm);
expect(headings).toBeTruthy();
expect(headings.length).toBe(3);
});
});
describe('SectionManager integration', () => {
test('should have handleSectionSplit method', () => {
if (!sectionManager) {
console.warn('SectionManager not available, skipping test');
return;
}
expect(typeof sectionManager.handleSectionSplit).toBe('function');
// Find heading lines
const headings = contentWithMultipleHeadings.match(/^#+.*$/gm);
expect(headings).toBeTruthy();
expect(headings.length).toBe(3);
});
});
test('should maintain section state during splits', () => {
if (!sectionManager) return;
describe('SectionManager integration', () => {
test('should have handleSectionSplit method', () => {
if (!sectionManager) {
console.warn('SectionManager not available, skipping test');
return;
}
const originalSectionCount = document.querySelectorAll('.section').length;
expect(typeof sectionManager.handleSectionSplit).toBe('function');
});
// Mock section splitting
const mockNewSection = document.createElement('div');
mockNewSection.className = 'section';
mockNewSection.setAttribute('data-section-id', 'split-section');
test('should maintain section state during splits', () => {
if (!sectionManager) return;
if (originalSectionCount > 0) {
expect(originalSectionCount).toBeGreaterThan(0);
}
const originalSectionCount = document.querySelectorAll('.section').length;
// Mock section splitting
const mockNewSection = document.createElement('div');
mockNewSection.className = 'section';
mockNewSection.setAttribute('data-section-id', 'split-section');
if (originalSectionCount > 0) {
expect(originalSectionCount).toBeGreaterThan(0);
}
});
});
});
describe('Dynamic section creation', () => {
test('should create new section elements when splitting', () => {
const sectionContent = `
describe('Dynamic section creation', () => {
test('should create new section elements when splitting', () => {
const sectionContent = `
Original content
# New Section Title
@ -172,36 +172,36 @@ Third section content`;
New section content
`;
// Simulate section creation
const newSection = document.createElement('div');
newSection.className = 'section';
newSection.setAttribute('data-section-id', 'generated-section-id');
// Simulate section creation
const newSection = document.createElement('div');
newSection.className = 'section';
newSection.setAttribute('data-section-id', 'generated-section-id');
const contentDiv = document.createElement('div');
contentDiv.className = 'section-content';
contentDiv.textContent = 'New section content';
const contentDiv = document.createElement('div');
contentDiv.className = 'section-content';
contentDiv.textContent = 'New section content';
newSection.appendChild(contentDiv);
newSection.appendChild(contentDiv);
expect(newSection.className).toBe('section');
expect(newSection.getAttribute('data-section-id')).toBeTruthy();
expect(newSection.querySelector('.section-content')).toBeTruthy();
});
expect(newSection.className).toBe('section');
expect(newSection.getAttribute('data-section-id')).toBeTruthy();
expect(newSection.querySelector('.section-content')).toBeTruthy();
});
test('should generate unique section IDs', () => {
const headingText = 'My New Section';
test('should generate unique section IDs', () => {
const headingText = 'My New Section';
// Simulate ID generation from heading
const sectionId = headingText
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
// Simulate ID generation from heading
const sectionId = headingText
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
expect(sectionId).toBe('my-new-section');
});
expect(sectionId).toBe('my-new-section');
});
test('should preserve section hierarchy', () => {
const hierarchicalContent = `
test('should preserve section hierarchy', () => {
const hierarchicalContent = `
# Main Section
Main content
@ -212,20 +212,20 @@ Third section content`;
Sub-sub content
`;
const headings = hierarchicalContent.match(/^#+.*$/gm);
const headings = hierarchicalContent.match(/^#+.*$/gm);
if (headings) {
expect(headings.length).toBe(3);
expect(headings[0]).toMatch(/^# /);
expect(headings[1]).toMatch(/^## /);
expect(headings[2]).toMatch(/^### /);
}
if (headings) {
expect(headings.length).toBe(3);
expect(headings[0]).toMatch(/^# /);
expect(headings[1]).toMatch(/^## /);
expect(headings[2]).toMatch(/^### /);
}
});
});
});
describe('Section splitting edge cases', () => {
test('should handle empty headings gracefully', () => {
const contentWithEmptyHeading = `
describe('Section splitting edge cases', () => {
test('should handle empty headings gracefully', () => {
const contentWithEmptyHeading = `
Content before
#
@ -233,35 +233,35 @@ Third section content`;
Content after
`;
const parts = contentWithEmptyHeading.split(/\n(?=#)/);
expect(parts.length).toBeGreaterThanOrEqual(1);
});
const parts = contentWithEmptyHeading.split(/\n(?=#)/);
expect(parts.length).toBeGreaterThanOrEqual(1);
});
test('should handle headings at the start of content', () => {
const contentStartingWithHeading = `# First Heading
test('should handle headings at the start of content', () => {
const contentStartingWithHeading = `# First Heading
Content for first section
# Second Heading
Content for second section
`;
const parts = contentStartingWithHeading.split(/\n(?=#)/);
expect(parts[0]).toContain('# First Heading');
});
const parts = contentStartingWithHeading.split(/\n(?=#)/);
expect(parts[0]).toContain('# First Heading');
});
test('should handle malformed headings', () => {
const malformedHeadings = [
'#NoSpace',
'# ',
'########## Too many hashes',
'Not a heading # at all'
];
test('should handle malformed headings', () => {
const malformedHeadings = [
'#NoSpace',
'# ',
'########## Too many hashes',
'Not a heading # at all'
];
malformedHeadings.forEach(text => {
const isValidHeading = /^#{1,6}\s+\S/.test(text);
// Most should be invalid except properly formatted ones
expect(typeof isValidHeading).toBe('boolean');
});
malformedHeadings.forEach(text => {
const isValidHeading = /^#{1,6}\s+\S/.test(text);
// Most should be invalid except properly formatted ones
expect(typeof isValidHeading).toBe('boolean');
});
});
});
});
});
});

View file

@ -7,133 +7,133 @@
// Mock DOM globals that might be missing in JSDOM
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
}));
global.IntersectionObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
}));
// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
});
// Mock local storage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
// Mock session storage
const sessionStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn()
};
global.sessionStorage = sessionStorageMock;
// Global test utilities
global.testUtils = {
/**
/**
* Create a mock DOM element with specified tag and attributes
*/
createElement: (tag, attributes = {}) => {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => {
element.setAttribute(key, value);
});
return element;
},
createElement: (tag, attributes = {}) => {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => {
element.setAttribute(key, value);
});
return element;
},
/**
/**
* Create a test markdown content div
*/
createMarkdownContent: (content = '# Test Content') => {
const div = document.createElement('div');
div.id = 'markdown-content';
div.innerHTML = content;
return div;
},
createMarkdownContent: (content = '# Test Content') => {
const div = document.createElement('div');
div.id = 'markdown-content';
div.innerHTML = content;
return div;
},
/**
/**
* Wait for next tick (useful for async operations)
*/
nextTick: () => new Promise(resolve => setTimeout(resolve, 0)),
nextTick: () => new Promise(resolve => setTimeout(resolve, 0)),
/**
/**
* Simulate user interaction events
*/
simulateEvent: (element, eventType, eventProperties = {}) => {
const event = new Event(eventType, { bubbles: true, ...eventProperties });
Object.entries(eventProperties).forEach(([key, value]) => {
event[key] = value;
});
element.dispatchEvent(event);
return event;
},
simulateEvent: (element, eventType, eventProperties = {}) => {
const event = new Event(eventType, { bubbles: true, ...eventProperties });
Object.entries(eventProperties).forEach(([key, value]) => {
event[key] = value;
});
element.dispatchEvent(event);
return event;
},
/**
/**
* Clean up DOM after each test
*/
cleanupDOM: () => {
document.body.innerHTML = '';
document.head.innerHTML = '';
}
cleanupDOM: () => {
document.body.innerHTML = '';
document.head.innerHTML = '';
}
};
// Setup and teardown
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Reset mocks
jest.clearAllMocks();
// Reset localStorage/sessionStorage
localStorageMock.getItem.mockClear();
localStorageMock.setItem.mockClear();
localStorageMock.removeItem.mockClear();
localStorageMock.clear.mockClear();
// Reset localStorage/sessionStorage
localStorageMock.getItem.mockClear();
localStorageMock.setItem.mockClear();
localStorageMock.removeItem.mockClear();
localStorageMock.clear.mockClear();
sessionStorageMock.getItem.mockClear();
sessionStorageMock.setItem.mockClear();
sessionStorageMock.removeItem.mockClear();
sessionStorageMock.clear.mockClear();
sessionStorageMock.getItem.mockClear();
sessionStorageMock.setItem.mockClear();
sessionStorageMock.removeItem.mockClear();
sessionStorageMock.clear.mockClear();
});
afterEach(() => {
// Clean up DOM
global.testUtils.cleanupDOM();
// Clean up DOM
global.testUtils.cleanupDOM();
// Clean up any timers
jest.runOnlyPendingTimers();
jest.useRealTimers();
// Clean up any timers
jest.runOnlyPendingTimers();
jest.useRealTimers();
});
// Console helpers for test debugging
global.console = {
...console,
// Keep these methods for test debugging
log: console.log,
warn: console.warn,
error: console.error,
// Mock these to avoid noise in tests
info: jest.fn(),
debug: jest.fn(),
};
...console,
// Keep these methods for test debugging
log: console.log,
warn: console.warn,
error: console.error,
// Mock these to avoid noise in tests
info: jest.fn(),
debug: jest.fn()
};

View file

@ -12,7 +12,6 @@ const RefactorTestRunner = require('./refactor-test-runner.js');
const runner = new RefactorTestRunner();
runner.describe('Component Integration Tests', () => {
runner.it('should load all extracted components', () => {
try {
// Load extracted components
@ -30,7 +29,6 @@ runner.describe('Component Integration Tests', () => {
global.ExtractedDOMRenderer = domModule.DOMRenderer;
global.ExtractedFloatingMenu = domModule.FloatingMenu;
global.ExtractedEditState = sectionModule.EditState;
} catch (error) {
throw new Error(`Failed to load extracted components: ${error.message}`);
}
@ -62,7 +60,6 @@ Content for second subsection.`;
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
// Verify sections were created
// Expected: heading+paragraph, heading+paragraph, image, heading+paragraph = 4 sections
runner.expect(sections.length).toBe(4);
@ -297,12 +294,11 @@ console.log('test');
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
// Verify type detection - adjusted for actual parsing behavior
// Expected: heading+paragraph, image, code = 3 sections
runner.expect(sections[0].type).toBe('heading'); // Combined heading+paragraph
runner.expect(sections[1].type).toBe('image'); // Image section
runner.expect(sections[2].type).toBe('code'); // Code section
runner.expect(sections[1].type).toBe('image'); // Image section
runner.expect(sections[2].type).toBe('code'); // Code section
// Verify image detection
runner.expect(sections[1].isImage()).toBeTruthy(); // Image is now at index 1
@ -518,4 +514,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ Component integration tests completed');
});
}
}

View file

@ -25,7 +25,6 @@ const EXPECTED_DEBUGPANEL_API = [
];
runner.describe('DebugPanel Component Extraction', () => {
runner.it('should define expected API methods', () => {
const expectedMethods = EXPECTED_DEBUGPANEL_API;
runner.expect(expectedMethods.length).toBe(9);
@ -188,4 +187,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ DebugPanel extraction tests completed');
});
}
}

View file

@ -12,7 +12,6 @@ const RefactorTestRunner = require('./refactor-test-runner.js');
const runner = new RefactorTestRunner();
runner.describe('DebugPanel Integration Tests', () => {
runner.it('should load all extracted components including DebugPanel', () => {
try {
// Load extracted components
@ -28,7 +27,6 @@ runner.describe('DebugPanel Integration Tests', () => {
global.ExtractedSectionManager = sectionModule.SectionManager;
global.ExtractedDOMRenderer = domModule.DOMRenderer;
global.ExtractedDebugPanel = debugModule.DebugPanel;
} catch (error) {
throw new Error(`Failed to load extracted components: ${error.message}`);
}
@ -207,4 +205,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ DebugPanel integration tests completed');
});
}
}

View file

@ -84,13 +84,13 @@ class DocumentNavigatorTestRunner {
}
printSummary() {
console.log(`\n📊 Test Results:`);
console.log('\n📊 Test Results:');
console.log(` Passed: ${this.results.passed}`);
console.log(` Failed: ${this.results.failed}`);
console.log(` Total: ${this.results.total}`);
if (this.results.failed === 0) {
console.log(`\n🎉 All tests passed!`);
console.log('\n🎉 All tests passed!');
} else {
console.log(`\n❌ ${this.results.failed} test(s) failed.`);
}
@ -429,4 +429,4 @@ window.runDocumentNavigatorTests = () => runner.run();
console.log('📋 DocumentNavigator TDD Test Suite loaded. Run with: runDocumentNavigatorTests()');
export { runner };
export { runner };

View file

@ -26,7 +26,6 @@ const EXPECTED_DOCUMENTCONTROLS_API = [
];
runner.describe('DocumentControls Component Extraction', () => {
runner.it('should define expected API methods', () => {
const expectedMethods = EXPECTED_DOCUMENTCONTROLS_API;
runner.expect(expectedMethods.length).toBe(10);
@ -215,4 +214,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ DocumentControls extraction tests completed');
});
}
}

View file

@ -28,7 +28,6 @@ const EXPECTED_DOMRENDERER_API = [
];
runner.describe('DOMRenderer Component Extraction', () => {
runner.it('should define expected API methods', () => {
const expectedMethods = EXPECTED_DOMRENDERER_API;
runner.expect(expectedMethods.length).toBe(11);
@ -42,7 +41,7 @@ runner.describe('DOMRenderer Component Extraction', () => {
delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
try {
const editorModule = require('/home/worsch/markitect_project/markitect/static/editor.js');
const editorModule = require('../../../../../../../../home/worsch/markitect_project/markitect/static/editor.js');
runner.expect(editorModule.DOMRenderer).toBeTruthy();
// Set global for other tests
global.DOMRenderer = editorModule.DOMRenderer;
@ -209,4 +208,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ DOMRenderer extraction tests completed');
});
}
}

View file

@ -21,4 +21,4 @@ describe('Test Environment', () => {
expect(contentEl).toBeDefined();
expect(contentEl.tagName).toBe('DIV');
});
});
});

View file

@ -12,7 +12,6 @@ const RefactorTestRunner = require('./refactor-test-runner.js');
const runner = new RefactorTestRunner();
runner.describe('Extracted DOMRenderer Component', () => {
runner.it('should load extracted DOMRenderer component', () => {
// Load the extracted component
delete require.cache[require.resolve('../components/dom-renderer.js')];
@ -214,7 +213,7 @@ runner.describe('Extracted DOMRenderer Component', () => {
// Comparative test - verify extracted component behaves similarly to original
runner.it('should behave similarly to original monolithic component', () => {
// Load both components
const originalModule = require('/home/worsch/markitect_project/markitect/static/editor.js');
const originalModule = require('../../../../../../../../home/worsch/markitect_project/markitect/static/editor.js');
const extractedModule = require('../components/dom-renderer.js');
const sectionModule = require('../core/section-manager.js');
@ -268,4 +267,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ Extracted DOMRenderer tests completed');
});
}
}

View file

@ -12,7 +12,6 @@ const RefactorTestRunner = require('./refactor-test-runner.js');
const runner = new RefactorTestRunner();
runner.describe('Extracted SectionManager Component', () => {
runner.it('should load extracted SectionManager component', () => {
// Load the extracted component
delete require.cache[require.resolve('../core/section-manager.js')];
@ -47,7 +46,7 @@ runner.describe('Extracted SectionManager Component', () => {
const SectionManager = global.ExtractedSectionManager;
const manager = new SectionManager();
const testMarkdown = `# Heading 1\nContent 1\n\n## Heading 2\nContent 2`;
const testMarkdown = '# Heading 1\nContent 1\n\n## Heading 2\nContent 2';
const sections = manager.createSectionsFromMarkdown(testMarkdown);
runner.expect(Array.isArray(sections)).toBeTruthy();
@ -170,7 +169,7 @@ runner.describe('Extracted SectionManager Component', () => {
// Comparative test - verify extracted component behaves identically to original
runner.it('should behave identically to original monolithic component', () => {
// Load both components
const originalModule = require('/home/worsch/markitect_project/markitect/static/editor.js');
const originalModule = require('../../../../../../../../home/worsch/markitect_project/markitect/static/editor.js');
const extractedModule = require('../core/section-manager.js');
const originalManager = new originalModule.SectionManager();
@ -223,4 +222,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ Extracted SectionManager tests completed');
});
}
}

View file

@ -12,7 +12,6 @@ const RefactorTestRunner = require('./refactor-test-runner.js');
const runner = new RefactorTestRunner();
runner.describe('Full Component Integration Tests', () => {
runner.it('should load all extracted components', () => {
try {
// Load all extracted components
@ -31,7 +30,6 @@ runner.describe('Full Component Integration Tests', () => {
global.ExtractedDOMRenderer = domModule.DOMRenderer;
global.ExtractedDebugPanel = debugModule.DebugPanel;
global.ExtractedDocumentControls = controlsModule.DocumentControls;
} catch (error) {
throw new Error(`Failed to load extracted components: ${error.message}`);
}
@ -163,7 +161,7 @@ More detailed content here.`;
documentControls.create();
// Setup comprehensive event handling
let eventLog = [];
const eventLog = [];
sectionManager.on('sections-created', (data) => {
eventLog.push(`sections-created: ${data.count} sections`);
@ -302,4 +300,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ Full integration tests completed');
});
}
}

View file

@ -12,7 +12,6 @@ const RefactorTestRunner = require('./refactor-test-runner.js');
const runner = new RefactorTestRunner();
runner.describe('Real User Functionality Tests', () => {
runner.it('should allow users to edit content and see changes in DOM', () => {
// Load all extracted components
const sectionModule = require('../core/section-manager.js');
@ -40,7 +39,7 @@ runner.describe('Real User Functionality Tests', () => {
documentControls.create();
// Create sections from test markdown
const testMarkdown = `# Original Title\nOriginal content that should be editable.`;
const testMarkdown = '# Original Title\nOriginal content that should be editable.';
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
domRenderer.renderAllSections(sections);
@ -113,7 +112,7 @@ runner.describe('Real User Functionality Tests', () => {
documentControls.create();
// Create and modify content
const testMarkdown = `# Test Section\nOriginal content for reset test.`;
const testMarkdown = '# Test Section\nOriginal content for reset test.';
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
domRenderer.renderAllSections(sections);
@ -161,7 +160,7 @@ runner.describe('Real User Functionality Tests', () => {
const sectionManager = new SectionManager();
const domRenderer = new DOMRenderer(sectionManager, container);
const testMarkdown = `# Cancel Test\nContent that should remain unchanged.`;
const testMarkdown = '# Cancel Test\nContent that should remain unchanged.';
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
domRenderer.renderAllSections(sections);
@ -282,4 +281,4 @@ if (require.main === module) {
console.log('✅ Real user functionality tests completed');
console.log('These tests validate what users actually experience, not just internal APIs');
});
}
}

View file

@ -31,7 +31,6 @@ const EXPECTED_SECTION_MANAGER_API = [
];
runner.describe('SectionManager Component Extraction', () => {
runner.it('should define expected API methods', () => {
// This test defines what we expect from the extracted SectionManager
const expectedMethods = EXPECTED_SECTION_MANAGER_API;
@ -46,7 +45,7 @@ runner.describe('SectionManager Component Extraction', () => {
delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
try {
const editorModule = require('/home/worsch/markitect_project/markitect/static/editor.js');
const editorModule = require('../../../../../../../../home/worsch/markitect_project/markitect/static/editor.js');
runner.expect(editorModule.SectionManager).toBeTruthy();
// Set global for other tests
global.SectionManager = editorModule.SectionManager;
@ -69,7 +68,7 @@ runner.describe('SectionManager Component Extraction', () => {
const SectionManager = global.SectionManager;
const manager = new SectionManager();
const testMarkdown = `# Heading 1\nContent 1\n\n## Heading 2\nContent 2`;
const testMarkdown = '# Heading 1\nContent 1\n\n## Heading 2\nContent 2';
const sections = manager.createSectionsFromMarkdown(testMarkdown);
runner.expect(Array.isArray(sections)).toBeTruthy();
@ -193,4 +192,4 @@ if (require.main === module) {
runner.run().then(() => {
console.log('✅ SectionManager extraction tests completed');
});
}
}

View file

@ -23,9 +23,9 @@ class HTMLGenerator {
title: options.title || 'TestDrive-JSUI Editor',
description: options.description || 'Interactive markdown editor',
markdown: options.markdown || '# Welcome\n\nStart editing...',
mode: options.mode || 'edit', // 'edit' or 'view'
mode: options.mode || 'edit', // 'edit' or 'view'
theme: options.theme || 'github',
standalone: options.standalone !== false, // default true
standalone: options.standalone !== false, // default true
controls: {
editControl: options.controls?.editControl !== false,
statusControl: options.controls?.statusControl !== false,
@ -34,8 +34,8 @@ class HTMLGenerator {
},
shortcuts: options.shortcuts !== false,
autosave: options.autosave || false,
baseUrl: options.baseUrl || '_jsui', // For external mode
includeMarkedJS: options.includeMarkedJS !== false // Include marked.js CDN
baseUrl: options.baseUrl || '_jsui', // For external mode
includeMarkedJS: options.includeMarkedJS !== false // Include marked.js CDN
};
}

View file

@ -212,4 +212,4 @@ export class UIWidget extends Widget {
enableAnimations: true
};
}
}
}

View file

@ -138,4 +138,4 @@ export class Widget extends EventTarget {
return element;
}
}
}

View file

@ -32,15 +32,15 @@ export class DocumentNavigator extends UIWidget {
getDefaultConfig() {
return {
...super.getDefaultConfig(),
position: 'left', // 'left' or 'right'
collapsed: true, // Start collapsed
autoHide: true, // Hide on mobile
maxHeadingLevel: 3, // H1, H2, H3
enableScrollSpy: true, // Highlight current section
smoothScroll: true, // Smooth scroll behavior
animationDuration: 300, // Animation timing
minHeadings: 2, // Min headings to show navigator
theme: 'default', // Theme support
position: 'left', // 'left' or 'right'
collapsed: true, // Start collapsed
autoHide: true, // Hide on mobile
maxHeadingLevel: 3, // H1, H2, H3
enableScrollSpy: true, // Highlight current section
smoothScroll: true, // Smooth scroll behavior
animationDuration: 300, // Animation timing
minHeadings: 2, // Min headings to show navigator
theme: 'default', // Theme support
// Styling options
width: '280px',
@ -89,7 +89,7 @@ export class DocumentNavigator extends UIWidget {
className: 'document-navigator markitect-widget',
attributes: {
'aria-label': this.config.ariaLabel,
'role': 'navigation'
role: 'navigation'
},
style: this.getNavigatorStyle()
});
@ -130,7 +130,7 @@ export class DocumentNavigator extends UIWidget {
this.toggleButton = this.createElement('button', {
className: 'navigator-toggle',
attributes: {
'type': 'button',
type: 'button',
'aria-label': this.isCollapsed ? 'Expand navigation' : 'Collapse navigation',
'aria-expanded': !this.isCollapsed
},
@ -236,11 +236,11 @@ export class DocumentNavigator extends UIWidget {
className: 'navigator-link',
textContent: item.text,
attributes: {
'href': `#${item.id}`,
href: `#${item.id}`,
'data-target': item.id,
'data-level': item.level,
'role': 'button',
'tabindex': '0'
role: 'button',
tabindex: '0'
},
style: {
display: 'block',
@ -622,4 +622,4 @@ export class DocumentNavigator extends UIWidget {
await super.destroy();
}
}
}

View file

@ -1,7 +1,7 @@
import { LitElement, html, css } from "lit";
import { LitElement, html, css } from 'lit';
export class UiEditButton extends LitElement {
static styles = css`
static styles = css`
button {
position: fixed;
top: 50%;
@ -20,13 +20,13 @@ export class UiEditButton extends LitElement {
}
`;
render() {
return html`<button @click=${this._onClick}>✎ Edit</button>`;
}
render() {
return html`<button @click=${this._onClick}>✎ Edit</button>`;
}
_onClick() {
alert("Edit button clicked!");
}
_onClick() {
alert('Edit button clicked!');
}
}
customElements.define("ui-edit-button", UiEditButton);
customElements.define('ui-edit-button', UiEditButton);

View file

@ -1,7 +1,7 @@
import "./ui-edit-button.js";
import './ui-edit-button.js';
export default {
title: "UI/Edit Button",
title: 'UI/Edit Button'
};
export const Default = () => `<ui-edit-button></ui-edit-button>`;
export const Default = () => '<ui-edit-button></ui-edit-button>';

View file

@ -1,24 +1,24 @@
import "../ui-edit-button/ui-edit-button.js";
import '../ui-edit-button/ui-edit-button.js';
describe("<ui-edit-button>", () => {
it("renders a button element", () => {
const el = document.createElement("ui-edit-button");
document.body.appendChild(el);
const button = el.shadowRoot.querySelector("button");
expect(button).to.exist;
expect(button.textContent).to.include("Edit");
});
describe('<ui-edit-button>', () => {
it('renders a button element', () => {
const el = document.createElement('ui-edit-button');
document.body.appendChild(el);
const button = el.shadowRoot.querySelector('button');
expect(button).to.exist;
expect(button.textContent).to.include('Edit');
});
it("triggers a click handler", () => {
const el = document.createElement("ui-edit-button");
document.body.appendChild(el);
it('triggers a click handler', () => {
const el = document.createElement('ui-edit-button');
document.body.appendChild(el);
let clicked = false;
el._onClick = () => (clicked = true);
let clicked = false;
el._onClick = () => (clicked = true);
const button = el.shadowRoot.querySelector("button");
button.click();
const button = el.shadowRoot.querySelector('button');
button.click();
expect(clicked).to.be.true;
});
expect(clicked).to.be.true;
});
});

View file

@ -48,8 +48,8 @@ import '../js/testdrive-jsui.js';
// Get the TestDriveJSUI from window (set by testdrive-jsui.js)
// In UMD build, this will be available globally
const TestDriveJSUI = (typeof window !== 'undefined' && window.TestDriveJSUI)
|| (typeof global !== 'undefined' && global.TestDriveJSUI);
const TestDriveJSUI = (typeof window !== 'undefined' && window.TestDriveJSUI) ||
(typeof global !== 'undefined' && global.TestDriveJSUI);
// Export for ES modules and CommonJS
export default TestDriveJSUI;