Successfully extracted SectionManager from monolithic editor.js using TDD approach.
Component Extraction:
- Created modular directory structure: markitect/static/js/{core,components,utils,tests}/
- Extracted SectionManager class (490 lines) to core/section-manager.js
- Included Section class and dependencies (EditState, SectionType)
- Preserved all functionality: section creation, editing, events, status
TDD Implementation:
- Built RefactorTestRunner for component extraction testing
- Created comprehensive test suite (12 tests, all passing)
- Verified behavioral compatibility with original monolithic component
- Fixed subtle bug in getDocumentStatus (isEditing vs isEditing())
Key Features Preserved:
✅ Section creation from markdown (with sophisticated ID generation)
✅ Editing state management (start, update, accept, cancel, reset)
✅ Event system (on/emit) for section lifecycle events
✅ Document status tracking and section collection management
✅ Section splitting functionality for dynamic content changes
Architecture Benefits:
- Clean separation of concerns (490 lines vs 5,188-line monolith)
- Independent testability without DOM dependencies
- Reusable component for different UI frameworks
- Clear API surface with comprehensive test coverage
Next: Extract DOMRenderer and other UI components using same TDD approach.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
226 lines
9.1 KiB
JavaScript
226 lines
9.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* TDD Test for Extracted SectionManager Component
|
|
*
|
|
* Tests the extracted SectionManager component independently from the monolith.
|
|
* Verifies that all functionality is preserved after extraction.
|
|
*/
|
|
|
|
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')];
|
|
|
|
try {
|
|
const module = require('../core/section-manager.js');
|
|
runner.expect(module.SectionManager).toBeTruthy();
|
|
runner.expect(module.Section).toBeTruthy();
|
|
runner.expect(module.EditState).toBeTruthy();
|
|
runner.expect(module.SectionType).toBeTruthy();
|
|
|
|
// Set globals for other tests
|
|
global.ExtractedSectionManager = module.SectionManager;
|
|
global.ExtractedSection = module.Section;
|
|
global.ExtractedEditState = module.EditState;
|
|
global.ExtractedSectionType = module.SectionType;
|
|
} catch (error) {
|
|
throw new Error(`Failed to load extracted SectionManager: ${error.message}`);
|
|
}
|
|
});
|
|
|
|
runner.it('should preserve constructor functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
|
|
const manager = new SectionManager();
|
|
runner.expect(manager).toBeInstanceOf(SectionManager);
|
|
runner.expect(manager.sections).toBeInstanceOf(Map);
|
|
runner.expect(manager.listeners).toBeInstanceOf(Map);
|
|
});
|
|
|
|
runner.it('should preserve section creation functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
const manager = new SectionManager();
|
|
|
|
const testMarkdown = `# Heading 1\nContent 1\n\n## Heading 2\nContent 2`;
|
|
const sections = manager.createSectionsFromMarkdown(testMarkdown);
|
|
|
|
runner.expect(Array.isArray(sections)).toBeTruthy();
|
|
runner.expect(sections.length).toBe(2);
|
|
runner.expect(sections[0].currentMarkdown).toContain('Heading 1');
|
|
runner.expect(sections[1].currentMarkdown).toContain('Heading 2');
|
|
});
|
|
|
|
runner.it('should preserve section editing functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
const manager = new SectionManager();
|
|
|
|
const sections = manager.createSectionsFromMarkdown('# Test\nContent');
|
|
const sectionId = sections[0].id;
|
|
|
|
// Test start editing
|
|
const content = manager.startEditing(sectionId);
|
|
runner.expect(content).toContain('Test');
|
|
|
|
const section = manager.sections.get(sectionId);
|
|
runner.expect(section.isEditing()).toBeTruthy();
|
|
|
|
// Test stop editing
|
|
section.stopEditing();
|
|
runner.expect(section.isEditing()).toBeFalsy();
|
|
});
|
|
|
|
runner.it('should preserve event system functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
const manager = new SectionManager();
|
|
|
|
let eventFired = false;
|
|
let eventData = null;
|
|
|
|
manager.on('test-event', (data) => {
|
|
eventFired = true;
|
|
eventData = data;
|
|
});
|
|
|
|
manager.emit('test-event', { test: 'data' });
|
|
|
|
runner.expect(eventFired).toBeTruthy();
|
|
runner.expect(eventData).toEqual({ test: 'data' });
|
|
});
|
|
|
|
runner.it('should preserve document status functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
const manager = new SectionManager();
|
|
|
|
manager.createSectionsFromMarkdown('# Test\nContent');
|
|
const status = manager.getDocumentStatus();
|
|
|
|
runner.expect(status).toHaveProperty('totalSections');
|
|
runner.expect(status).toHaveProperty('editingSections');
|
|
runner.expect(status.totalSections).toBe(1);
|
|
});
|
|
|
|
runner.it('should preserve getAllSections functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
const manager = new SectionManager();
|
|
|
|
const testMarkdown = '# One\nContent\n\n# Two\nMore content';
|
|
manager.createSectionsFromMarkdown(testMarkdown);
|
|
|
|
const allSections = manager.getAllSections();
|
|
runner.expect(Array.isArray(allSections)).toBeTruthy();
|
|
runner.expect(allSections.length).toBe(2);
|
|
});
|
|
|
|
runner.it('should preserve section splitting functionality', () => {
|
|
const SectionManager = global.ExtractedSectionManager;
|
|
const manager = new SectionManager();
|
|
|
|
const sections = manager.createSectionsFromMarkdown('# Original\nContent');
|
|
const sectionId = sections[0].id;
|
|
|
|
const newContent = '# Split 1\nContent 1\n\n# Split 2\nContent 2';
|
|
const newSections = manager.handleSectionSplit(sectionId, newContent);
|
|
|
|
runner.expect(Array.isArray(newSections)).toBeTruthy();
|
|
runner.expect(newSections.length).toBe(2);
|
|
runner.expect(manager.sections.has(sectionId)).toBeFalsy(); // Original removed
|
|
});
|
|
|
|
runner.it('should preserve Section class functionality', () => {
|
|
const Section = global.ExtractedSection;
|
|
const EditState = global.ExtractedEditState;
|
|
|
|
const section = new Section('test-id', '# Test Content', 'heading');
|
|
|
|
runner.expect(section.id).toBe('test-id');
|
|
runner.expect(section.currentMarkdown).toBe('# Test Content');
|
|
runner.expect(section.type).toBe('heading');
|
|
runner.expect(section.state).toBe(EditState.ORIGINAL);
|
|
});
|
|
|
|
runner.it('should preserve Section ID generation', () => {
|
|
const Section = global.ExtractedSection;
|
|
|
|
const id1 = Section.generateId('# Test Heading', 0);
|
|
const id2 = Section.generateId('# Different Heading', 1);
|
|
|
|
runner.expect(typeof id1 === 'string').toBeTruthy();
|
|
runner.expect(typeof id2 === 'string').toBeTruthy();
|
|
runner.expect(id1).toContain('section-');
|
|
runner.expect(id2).toContain('section-');
|
|
runner.expect(id1 !== id2).toBeTruthy(); // Should be unique
|
|
});
|
|
|
|
runner.it('should preserve Section type detection', () => {
|
|
const Section = global.ExtractedSection;
|
|
const SectionType = global.ExtractedSectionType;
|
|
|
|
runner.expect(Section.detectType('# Heading')).toBe(SectionType.HEADING);
|
|
runner.expect(Section.detectType('')).toBe(SectionType.IMAGE);
|
|
runner.expect(Section.detectType('```code```')).toBe(SectionType.CODE);
|
|
runner.expect(Section.detectType('Regular paragraph')).toBe(SectionType.PARAGRAPH);
|
|
});
|
|
|
|
// 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 extractedModule = require('../core/section-manager.js');
|
|
|
|
const originalManager = new originalModule.SectionManager();
|
|
const extractedManager = new extractedModule.SectionManager();
|
|
|
|
const testMarkdown = '# Test\nContent\n\n## Subheading\nMore content';
|
|
|
|
// Debug: Check what each component produces
|
|
console.log('Creating sections with original component...');
|
|
const originalSections = originalManager.createSectionsFromMarkdown(testMarkdown);
|
|
console.log(`Original produced ${originalSections.length} sections`);
|
|
|
|
console.log('Creating sections with extracted component...');
|
|
const extractedSections = extractedManager.createSectionsFromMarkdown(testMarkdown);
|
|
console.log(`Extracted produced ${extractedSections.length} sections`);
|
|
|
|
if (originalSections.length > 0) {
|
|
console.log('Original first section:', originalSections[0].currentMarkdown);
|
|
}
|
|
if (extractedSections.length > 0) {
|
|
console.log('Extracted first section:', extractedSections[0].currentMarkdown);
|
|
}
|
|
|
|
// Should have same number of sections
|
|
runner.expect(extractedSections.length).toBe(originalSections.length);
|
|
|
|
// Should have same content
|
|
for (let i = 0; i < originalSections.length; i++) {
|
|
runner.expect(extractedSections[i].currentMarkdown).toBe(originalSections[i].currentMarkdown);
|
|
runner.expect(extractedSections[i].type).toBe(originalSections[i].type);
|
|
}
|
|
|
|
// Should have same document status structure
|
|
const originalStatus = originalManager.getDocumentStatus();
|
|
const extractedStatus = extractedManager.getDocumentStatus();
|
|
|
|
console.log('Original status:', originalStatus);
|
|
console.log('Extracted status:', extractedStatus);
|
|
|
|
runner.expect(extractedStatus.totalSections).toBe(originalStatus.totalSections);
|
|
runner.expect(extractedStatus.editingSections).toBe(originalStatus.editingSections);
|
|
});
|
|
});
|
|
|
|
module.exports = runner;
|
|
|
|
// Run tests if called directly
|
|
if (require.main === module) {
|
|
console.log('🧪 Testing Extracted SectionManager Component');
|
|
runner.run().then(() => {
|
|
console.log('✅ Extracted SectionManager tests completed');
|
|
});
|
|
} |