generated from coulomb/repo-seed
feat: add refactored testdrive-jsui capability with consolidated architecture
Complete integration of refactored testdrive-jsui capability: ## Refactored Architecture - js/ - All JavaScript source (controls, components, core) - static/ - CSS, images, templates - src/testdrive_jsui/ - Python package - tests/ - Python tests ## Plugin Self-Declaration - get_plugin_source_dir() - plugin declares own location - get_asset_paths() - organized asset paths - No hardcoded discovery logic ## Merged Content - Baseline UI scaffold (tutorials, LICENSE, INTRODUCTION.md) - Refactored capability implementation - Comprehensive documentation Ready for standalone use or integration with markitect. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
267
js/tests/section-splitting.test.js
Normal file
267
js/tests/section-splitting.test.js
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Section Splitting Functionality Tests
|
||||
*
|
||||
* Tests dynamic section splitting when headings are detected
|
||||
* Based on functionality from history/javascript-dev-tests/test_section_splitting.js
|
||||
*/
|
||||
|
||||
describe('Section Splitting', () => {
|
||||
let sectionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup DOM
|
||||
document.body.innerHTML = `
|
||||
<div id="content">
|
||||
<div class="section" data-section-id="main-section">
|
||||
<div class="section-content">
|
||||
<p>Original content</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Load components
|
||||
require('../core/section-manager.js');
|
||||
|
||||
if (global.SectionManager) {
|
||||
sectionManager = new global.SectionManager();
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Heading detection', () => {
|
||||
test('should detect new headings in content', () => {
|
||||
const textWithHeading = `
|
||||
This is some content.
|
||||
|
||||
# New Heading
|
||||
|
||||
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('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 = `
|
||||
${originalContent}
|
||||
|
||||
# New Section
|
||||
|
||||
New section content
|
||||
`;
|
||||
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
test('should handle multiple headings correctly', () => {
|
||||
const contentWithMultipleHeadings = `Initial content
|
||||
|
||||
# First Heading
|
||||
First section content
|
||||
|
||||
## Second Heading
|
||||
Second section content
|
||||
|
||||
# Third Heading
|
||||
Third section content`;
|
||||
|
||||
// Split on lines that start with headings
|
||||
const parts = contentWithMultipleHeadings.split(/\n(?=#)/);
|
||||
|
||||
// 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');
|
||||
});
|
||||
|
||||
test('should maintain section state during splits', () => {
|
||||
if (!sectionManager) return;
|
||||
|
||||
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 = `
|
||||
Original content
|
||||
|
||||
# New Section Title
|
||||
|
||||
New section content
|
||||
`;
|
||||
|
||||
// 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';
|
||||
|
||||
newSection.appendChild(contentDiv);
|
||||
|
||||
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';
|
||||
|
||||
// Simulate ID generation from heading
|
||||
const sectionId = headingText
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
|
||||
expect(sectionId).toBe('my-new-section');
|
||||
});
|
||||
|
||||
test('should preserve section hierarchy', () => {
|
||||
const hierarchicalContent = `
|
||||
# Main Section
|
||||
Main content
|
||||
|
||||
## Subsection
|
||||
Sub content
|
||||
|
||||
### Sub-subsection
|
||||
Sub-sub content
|
||||
`;
|
||||
|
||||
const headings = hierarchicalContent.match(/^#+.*$/gm);
|
||||
|
||||
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 = `
|
||||
Content before
|
||||
|
||||
#
|
||||
|
||||
Content after
|
||||
`;
|
||||
|
||||
const parts = contentWithEmptyHeading.split(/\n(?=#)/);
|
||||
expect(parts.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user