Added comprehensive plugin system for independent JavaScript UI development: **Plugin Infrastructure:** - Extended existing MarkiTect plugin system with RenderingEnginePlugin base class - Added RENDERING plugin type to PluginType enum - Created RenderingConfig for asset management and deployment - Implemented RenderingEngineManager for plugin discovery and lifecycle **TestDrive JSUI Plugin:** - Extracted JavaScript UI components to independent testdrive-jsui plugin - Created standalone development environment (no Python required) - Implemented compass-positioned control panels (NW, NE, E, SE) - Added clean JSON configuration interface for Python↔JavaScript data transfer **Asset Management:** - Development mode: serve assets directly from plugin source directory - Production mode: deploy to _markitect/plugins/[plugin-name]/ structure - Configurable asset URLs and deployment strategies - Support for external dependencies (CDN resources) **Standalone Development:** - testdrive-jsui/test.html for browser-based development - Package.json with npm scripts for development server - Complete separation of JavaScript development from Python environment - Hot reload and standard web development workflow **Integration Demo:** - demo_plugin_integration.py showcasing all plugin capabilities - Standalone, plugin discovery, production deployment examples - Asset URL generation for different deployment modes This enables JavaScript-first development while maintaining clean integration with the MarkiTect Python ecosystem. Developers can now work on UI components independently using standard web development tools and workflows. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
285 lines
12 KiB
JavaScript
285 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Real User Functionality Tests
|
|
*
|
|
* This test file validates the actual functionality that users experience,
|
|
* not just internal API calls. It tests the complete user workflow.
|
|
*/
|
|
|
|
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');
|
|
const domModule = require('../components/dom-renderer.js');
|
|
const debugModule = require('../components/debug-panel.js');
|
|
const controlsModule = require('../components/document-controls.js');
|
|
|
|
const { SectionManager } = sectionModule;
|
|
const { DOMRenderer } = domModule;
|
|
const { DebugPanel } = debugModule;
|
|
const { DocumentControls } = controlsModule;
|
|
|
|
// Setup DOM container
|
|
const container = document.createElement('div');
|
|
container.innerHTML = '<div id="markdown-content"></div>';
|
|
document.body.appendChild(container);
|
|
|
|
// Create components
|
|
const sectionManager = new SectionManager();
|
|
const domRenderer = new DOMRenderer(sectionManager, container);
|
|
const debugPanel = new DebugPanel();
|
|
const documentControls = new DocumentControls();
|
|
|
|
// Setup document controls
|
|
documentControls.create();
|
|
|
|
// Create sections from test markdown
|
|
const testMarkdown = `# Original Title\nOriginal content that should be editable.`;
|
|
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
|
|
domRenderer.renderAllSections(sections);
|
|
|
|
const firstSection = sections[0];
|
|
const sectionElement = container.querySelector(`[data-section-id="${firstSection.id}"]`);
|
|
|
|
// Verify original content is rendered
|
|
runner.expect(sectionElement.innerHTML).toContain('Original Title');
|
|
|
|
// Simulate user clicking on section
|
|
const clickEvent = new Event('click', { bubbles: true });
|
|
sectionElement.dispatchEvent(clickEvent);
|
|
|
|
// Verify editing state is active
|
|
runner.expect(firstSection.isEditing()).toBeTruthy();
|
|
|
|
// Find the floating menu and edit controls
|
|
const floatingMenu = document.querySelector('.ui-edit-floating-menu');
|
|
runner.expect(floatingMenu).toBeTruthy();
|
|
|
|
const textarea = floatingMenu.querySelector('textarea');
|
|
const acceptButton = Array.from(floatingMenu.querySelectorAll('button')).find(btn => btn.textContent.includes('Accept'));
|
|
|
|
runner.expect(textarea).toBeTruthy();
|
|
runner.expect(acceptButton).toBeTruthy();
|
|
|
|
// Simulate user editing content
|
|
const newContent = '# Updated Title\nCompletely new content added by user.';
|
|
textarea.value = newContent;
|
|
|
|
// Simulate user clicking accept
|
|
acceptButton.click();
|
|
|
|
// Verify section is no longer editing
|
|
runner.expect(firstSection.isEditing()).toBeFalsy();
|
|
|
|
// Verify floating menu is gone
|
|
const menuAfterAccept = document.querySelector('.ui-edit-floating-menu');
|
|
runner.expect(menuAfterAccept).toBeFalsy();
|
|
|
|
// CRITICAL TEST: Verify DOM was actually updated with new content
|
|
const updatedElement = container.querySelector(`[data-section-id="${firstSection.id}"]`);
|
|
runner.expect(updatedElement.innerHTML).toContain('Updated Title');
|
|
runner.expect(updatedElement.innerHTML).toContain('Completely new content');
|
|
runner.expect(updatedElement.innerHTML).not.toContain('Original Title');
|
|
|
|
// Cleanup
|
|
document.body.removeChild(container);
|
|
documentControls.destroy();
|
|
});
|
|
|
|
runner.it('should allow users to reset all changes', () => {
|
|
// Setup similar to above
|
|
const sectionModule = require('../core/section-manager.js');
|
|
const domModule = require('../components/dom-renderer.js');
|
|
const controlsModule = require('../components/document-controls.js');
|
|
|
|
const { SectionManager } = sectionModule;
|
|
const { DOMRenderer } = domModule;
|
|
const { DocumentControls } = controlsModule;
|
|
|
|
const container = document.createElement('div');
|
|
container.innerHTML = '<div id="markdown-content"></div>';
|
|
document.body.appendChild(container);
|
|
|
|
const sectionManager = new SectionManager();
|
|
const domRenderer = new DOMRenderer(sectionManager, container);
|
|
const documentControls = new DocumentControls();
|
|
|
|
documentControls.create();
|
|
|
|
// Create and modify content
|
|
const testMarkdown = `# Test Section\nOriginal content for reset test.`;
|
|
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
|
|
domRenderer.renderAllSections(sections);
|
|
|
|
const firstSection = sections[0];
|
|
|
|
// Make changes to the section
|
|
sectionManager.startEditing(firstSection.id);
|
|
sectionManager.updateContent(firstSection.id, '# Modified Title\nModified content.');
|
|
sectionManager.acceptChanges(firstSection.id);
|
|
|
|
// Verify changes are applied
|
|
let sectionElement = container.querySelector(`[data-section-id="${firstSection.id}"]`);
|
|
runner.expect(sectionElement.innerHTML).toContain('Modified Title');
|
|
runner.expect(firstSection.hasChanges()).toBeTruthy();
|
|
|
|
// Test reset functionality
|
|
const resetButton = documentControls.getButton('reset-all');
|
|
runner.expect(resetButton).toBeTruthy();
|
|
|
|
// Click reset button
|
|
resetButton.click();
|
|
|
|
// Verify content is reset
|
|
sectionElement = container.querySelector(`[data-section-id="${firstSection.id}"]`);
|
|
runner.expect(sectionElement.innerHTML).toContain('Test Section');
|
|
runner.expect(sectionElement.innerHTML).not.toContain('Modified Title');
|
|
runner.expect(firstSection.hasChanges()).toBeFalsy();
|
|
|
|
// Cleanup
|
|
document.body.removeChild(container);
|
|
documentControls.destroy();
|
|
});
|
|
|
|
runner.it('should handle cancel operations correctly', () => {
|
|
const sectionModule = require('../core/section-manager.js');
|
|
const domModule = require('../components/dom-renderer.js');
|
|
|
|
const { SectionManager } = sectionModule;
|
|
const { DOMRenderer } = domModule;
|
|
|
|
const container = document.createElement('div');
|
|
container.innerHTML = '<div id="markdown-content"></div>';
|
|
document.body.appendChild(container);
|
|
|
|
const sectionManager = new SectionManager();
|
|
const domRenderer = new DOMRenderer(sectionManager, container);
|
|
|
|
const testMarkdown = `# Cancel Test\nContent that should remain unchanged.`;
|
|
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
|
|
domRenderer.renderAllSections(sections);
|
|
|
|
const firstSection = sections[0];
|
|
const originalContent = firstSection.currentMarkdown;
|
|
|
|
// Start editing
|
|
const sectionElement = container.querySelector(`[data-section-id="${firstSection.id}"]`);
|
|
sectionElement.click();
|
|
|
|
// Make changes but cancel them
|
|
const floatingMenu = document.querySelector('.ui-edit-floating-menu');
|
|
const textarea = floatingMenu.querySelector('textarea');
|
|
const cancelButton = Array.from(floatingMenu.querySelectorAll('button')).find(btn => btn.textContent.includes('Cancel'));
|
|
|
|
textarea.value = '# This should be cancelled\nThis content should not appear.';
|
|
cancelButton.click();
|
|
|
|
// Verify content is unchanged
|
|
const unchangedElement = container.querySelector(`[data-section-id="${firstSection.id}"]`);
|
|
runner.expect(unchangedElement.innerHTML).toContain('Cancel Test');
|
|
runner.expect(unchangedElement.innerHTML).not.toContain('This should be cancelled');
|
|
runner.expect(firstSection.currentMarkdown).toBe(originalContent);
|
|
|
|
// Cleanup
|
|
document.body.removeChild(container);
|
|
});
|
|
|
|
runner.it('should validate the complete editing workflow', () => {
|
|
// This test validates the entire user experience end-to-end
|
|
const sectionModule = require('../core/section-manager.js');
|
|
const domModule = require('../components/dom-renderer.js');
|
|
const debugModule = require('../components/debug-panel.js');
|
|
const controlsModule = require('../components/document-controls.js');
|
|
|
|
const { SectionManager } = sectionModule;
|
|
const { DOMRenderer } = domModule;
|
|
const { DebugPanel } = debugModule;
|
|
const { DocumentControls } = controlsModule;
|
|
|
|
const container = document.createElement('div');
|
|
container.innerHTML = '<div id="markdown-content"></div>';
|
|
document.body.appendChild(container);
|
|
|
|
const sectionManager = new SectionManager();
|
|
const domRenderer = new DOMRenderer(sectionManager, container);
|
|
const debugPanel = new DebugPanel();
|
|
const documentControls = new DocumentControls();
|
|
|
|
documentControls.create();
|
|
|
|
// Multi-section document
|
|
const testMarkdown = `# Document Title
|
|
Introduction paragraph.
|
|
|
|
## Section A
|
|
Content for section A.
|
|
|
|
## Section B
|
|
Content for section B.`;
|
|
|
|
const sections = sectionManager.createSectionsFromMarkdown(testMarkdown);
|
|
domRenderer.renderAllSections(sections);
|
|
|
|
// Verify all sections are rendered
|
|
const renderedSections = container.querySelectorAll('.ui-edit-section');
|
|
runner.expect(renderedSections.length).toBe(sections.length);
|
|
|
|
// Test editing multiple sections
|
|
const firstSection = sections[0];
|
|
const secondSection = sections[2]; // Section A
|
|
|
|
// Edit first section
|
|
renderedSections[0].click();
|
|
let floatingMenu = document.querySelector('.ui-edit-floating-menu');
|
|
let textarea = floatingMenu.querySelector('textarea');
|
|
let acceptButton = Array.from(floatingMenu.querySelectorAll('button')).find(btn => btn.textContent.includes('Accept'));
|
|
|
|
textarea.value = '# Updated Document Title\nUpdated introduction.';
|
|
acceptButton.click();
|
|
|
|
// Edit second section
|
|
renderedSections[2].click();
|
|
floatingMenu = document.querySelector('.ui-edit-floating-menu');
|
|
textarea = floatingMenu.querySelector('textarea');
|
|
acceptButton = Array.from(floatingMenu.querySelectorAll('button')).find(btn => btn.textContent.includes('Accept'));
|
|
|
|
textarea.value = '## Updated Section A\nCompletely new content for section A.';
|
|
acceptButton.click();
|
|
|
|
// Verify both sections were updated
|
|
const updatedSections = container.querySelectorAll('.ui-edit-section');
|
|
runner.expect(updatedSections[0].innerHTML).toContain('Updated Document Title');
|
|
runner.expect(updatedSections[2].innerHTML).toContain('Updated Section A');
|
|
|
|
// Test reset restores all sections
|
|
const resetButton = documentControls.getButton('reset-all');
|
|
resetButton.click();
|
|
|
|
const resetSections = container.querySelectorAll('.ui-edit-section');
|
|
runner.expect(resetSections[0].innerHTML).toContain('Document Title');
|
|
runner.expect(resetSections[0].innerHTML).not.toContain('Updated Document Title');
|
|
runner.expect(resetSections[2].innerHTML).toContain('Section A');
|
|
runner.expect(resetSections[2].innerHTML).not.toContain('Updated Section A');
|
|
|
|
// Cleanup
|
|
document.body.removeChild(container);
|
|
documentControls.destroy();
|
|
});
|
|
});
|
|
|
|
module.exports = runner;
|
|
|
|
// Run tests if called directly
|
|
if (require.main === module) {
|
|
console.log('🧪 Running Real User Functionality Tests');
|
|
runner.run().then(() => {
|
|
console.log('✅ Real user functionality tests completed');
|
|
console.log('These tests validate what users actually experience, not just internal APIs');
|
|
});
|
|
} |