Clean up codebase: remove unused demos and test files

- Remove dan-eden-portfolio and devigo-web demo sites
- Clean up demo testing infrastructure and scripts
- Remove frontend test files (html-preservation, style-detection tests)
- Update configuration and auth improvements
- Simplify demo structure to focus on core functionality

This cleanup reduces repository size and focuses on essential demos.
This commit is contained in:
2025-10-19 22:38:17 +02:00
parent dbdd4361b7
commit 74de64c66b
67 changed files with 56 additions and 6026 deletions

View File

@@ -16,7 +16,7 @@ export class InsertrAuth {
hideGatesAfterAuth: options.hideGatesAfterAuth === true, // Keep gates visible by default
...options
};
// Set mockAuth based on authProvider if not explicitly set
if (options.authProvider && !options.hasOwnProperty('mockAuth')) {
this.options.mockAuth = options.authProvider === 'mock';
@@ -78,19 +78,19 @@ export class InsertrAuth {
}
console.log('🔐 Initializing Insertr Editing System');
// Auto-enable edit mode after OAuth
this.state.editMode = true;
this.state.isInitialized = true;
// Start the editor system
if (window.Insertr && window.Insertr.startEditor) {
window.Insertr.startEditor();
}
// Emit state change for UI to update
this.emitStateChange();
console.log('📱 Editing system active');
console.log('✏️ Edit mode enabled - Click elements to edit');
}
@@ -100,25 +100,25 @@ export class InsertrAuth {
*/
setupEditorGates() {
const gates = document.querySelectorAll('.insertr-gate');
if (gates.length === 0) {
console.log(' No .insertr-gate elements found - editor access disabled');
return;
}
console.log(`🚪 Found ${gates.length} editor gate(s)`);
gates.forEach((gate, index) => {
// Store original text for later restoration
if (!gate.hasAttribute('data-original-text')) {
gate.setAttribute('data-original-text', gate.textContent);
}
gate.addEventListener('click', (e) => {
e.preventDefault();
this.handleGateClick(gate, index);
});
// Add minimal styling to indicate it's clickable
gate.style.cursor = 'pointer';
});
@@ -136,30 +136,30 @@ export class InsertrAuth {
console.log(`🚀 Editor gate activated (gate ${gateIndex + 1})`);
this.state.isAuthenticating = true;
// Store original text and show loading state
const originalText = gateElement.textContent;
gateElement.setAttribute('data-original-text', originalText);
gateElement.textContent = '⏳ Signing in...';
gateElement.style.pointerEvents = 'none';
try {
// Perform OAuth authentication
await this.performOAuthFlow();
// Initialize full editing system
this.initializeFullSystem();
// Handle gate visibility based on options
if (this.options.hideGatesAfterAuth) {
this.hideAllGates();
} else {
this.updateGateState();
}
} catch (error) {
console.error('❌ Authentication failed:', error);
// Restore clicked gate to original state
const originalText = gateElement.getAttribute('data-original-text');
if (originalText) {
@@ -190,10 +190,10 @@ export class InsertrAuth {
*/
async performMockAuth() {
console.log('🔐 Mock OAuth: Simulating authentication...');
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 1000));
await new Promise(resolve => setTimeout(resolve, 100));
// Set authenticated state
this.state.isAuthenticated = true;
this.state.currentUser = {
@@ -202,7 +202,7 @@ export class InsertrAuth {
role: 'admin',
provider: 'mock'
};
console.log('✅ Mock OAuth: Authentication successful');
}
@@ -211,7 +211,7 @@ export class InsertrAuth {
*/
async performAuthentikAuth() {
console.log('🔐 Starting Authentik OIDC authentication...');
try {
// Step 1: Initiate OAuth flow with backend
const response = await fetch('/auth/login', {
@@ -220,28 +220,28 @@ export class InsertrAuth {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (!data.redirect_url) {
throw new Error('No redirect URL returned from server');
}
console.log('🔄 Redirecting to Authentik for authentication...');
// Step 2: Redirect to Authentik for authentication
// We'll use a popup window to avoid losing the current page state
return new Promise((resolve, reject) => {
const authWindow = window.open(
data.redirect_url,
data.redirect_url,
'authentik-auth',
'width=500,height=600,scrollbars=yes,resizable=yes'
);
// Poll for popup closure (user completed auth)
const pollTimer = setInterval(() => {
try {
@@ -255,7 +255,7 @@ export class InsertrAuth {
reject(error);
}
}, 1000);
// Timeout after 10 minutes
setTimeout(() => {
clearInterval(pollTimer);
@@ -265,7 +265,7 @@ export class InsertrAuth {
reject(new Error('Authentication timeout'));
}, 600000);
});
} catch (error) {
console.error('❌ Authentik authentication failed:', error);
throw error;
@@ -279,15 +279,15 @@ export class InsertrAuth {
// In a real implementation, this would either:
// 1. Extract token from URL if using implicit flow
// 2. Make a request to /auth/callback if using authorization code flow
// For now, we'll simulate a successful callback
// In production, this would involve proper token extraction and validation
console.log('🔄 Processing authentication callback...');
// Simulate token validation
await new Promise(resolve => setTimeout(resolve, 500));
// Set authenticated state (in production, extract user info from JWT)
this.state.isAuthenticated = true;
this.state.currentUser = {
@@ -296,7 +296,7 @@ export class InsertrAuth {
role: 'editor',
provider: 'authentik'
};
console.log('✅ Authentik OAuth: Authentication successful');
}
@@ -319,12 +319,12 @@ export class InsertrAuth {
if (originalText) {
gate.textContent = originalText;
}
// Restore interactive state
gate.style.pointerEvents = '';
gate.style.opacity = '';
});
console.log('🚪 Editor gates restored to original state');
}
@@ -333,8 +333,8 @@ export class InsertrAuth {
*/
toggleAuthentication() {
this.state.isAuthenticated = !this.state.isAuthenticated;
this.state.currentUser = this.state.isAuthenticated ? {
name: 'Demo User',
this.state.currentUser = this.state.isAuthenticated ? {
name: 'Demo User',
email: 'demo@example.com',
role: 'editor'
} : null;
@@ -346,8 +346,8 @@ export class InsertrAuth {
this.emitStateChange();
console.log(this.state.isAuthenticated
? '✅ Authenticated as Demo User'
console.log(this.state.isAuthenticated
? '✅ Authenticated as Demo User'
: '❌ Logged out');
}
@@ -370,8 +370,8 @@ export class InsertrAuth {
this.emitStateChange();
console.log(this.state.editMode
? '✏️ Edit mode ON - Click elements to edit'
console.log(this.state.editMode
? '✏️ Edit mode ON - Click elements to edit'
: '👀 Edit mode OFF - Read-only view');
}
@@ -425,4 +425,4 @@ export class InsertrAuth {
isAuthenticating: this.state.isAuthenticating
};
}
}
}

View File

@@ -20,13 +20,6 @@ export class InsertrFormRenderer {
showEditForm(meta, currentContent, onSave, onCancel) {
const { element } = meta;
// Handle insertr-group elements by getting their viable children
if (element.classList.contains('insertr-group')) {
const children = this.getGroupChildren(element);
const groupMeta = { ...meta, element: children };
return this.editor.edit(groupMeta, currentContent, onSave, onCancel);
}
// All other elements use the editor directly
return this.editor.edit(meta, currentContent, onSave, onCancel);
}
@@ -53,4 +46,4 @@ export class InsertrFormRenderer {
}
}
}

View File

@@ -1,348 +0,0 @@
/**
* Unit tests for HTMLPreservationEngine
* Tests HTML preservation, sanitization, and attribute maintenance
*/
import { HTMLPreservationEngine } from './html-preservation.js';
// Mock DOM environment for testing
const mockDocument = {
createElement: (tagName) => ({
tagName: tagName.toUpperCase(),
innerHTML: '',
textContent: '',
children: [],
childNodes: [],
attributes: [],
classList: new Set(),
// Mock methods
appendChild: function(child) { this.children.push(child); },
removeChild: function(child) {
const index = this.children.indexOf(child);
if (index > -1) this.children.splice(index, 1);
},
replaceChild: function(newChild, oldChild) {
const index = this.children.indexOf(oldChild);
if (index > -1) this.children[index] = newChild;
},
querySelector: function(selector) { return null; },
querySelectorAll: function(selector) { return []; },
cloneNode: function(deep) { return mockDocument.createElement(tagName); },
setAttribute: function(name, value) { this.attributes[name] = value; },
getAttribute: function(name) { return this.attributes[name]; },
removeAttribute: function(name) { delete this.attributes[name]; }
}),
createTextNode: (text) => ({
nodeType: 3, // TEXT_NODE
textContent: text
})
};
global.document = mockDocument;
global.Node = {
TEXT_NODE: 3,
ELEMENT_NODE: 1
};
describe('HTMLPreservationEngine', () => {
let engine;
beforeEach(() => {
engine = new HTMLPreservationEngine();
});
describe('Content Extraction', () => {
test('should extract content with preservation metadata', () => {
const mockElement = createMockElement('p', {
innerHTML: 'Hello <strong class="emph">world</strong>!',
classes: ['insertr'],
attributes: { id: 'test-element' }
});
const extracted = engine.extractForEditing(mockElement);
expect(extracted.html).toBe('Hello <strong class="emph">world</strong>!');
expect(extracted.text).toBe('Hello world!');
expect(extracted.containerAttributes.class).toBe('insertr');
expect(extracted.containerAttributes.id).toBe('test-element');
expect(extracted.elementTag).toBe('p');
expect(extracted.hasNestedElements).toBe(true);
});
test('should handle simple text content', () => {
const mockElement = createMockElement('p', {
innerHTML: 'Simple text content',
textContent: 'Simple text content'
});
const extracted = engine.extractForEditing(mockElement);
expect(extracted.html).toBe('Simple text content');
expect(extracted.text).toBe('Simple text content');
expect(extracted.hasNestedElements).toBe(false);
});
});
describe('HTML Validation and Sanitization', () => {
test('should validate safe HTML', () => {
const safeHTML = 'Hello <strong class="emph">world</strong>!';
expect(engine.isValidHTML(safeHTML)).toBe(true);
});
test('should reject dangerous HTML', () => {
const dangerousHTML = 'Hello <script>alert("xss")</script> world!';
expect(engine.isValidHTML(dangerousHTML)).toBe(false);
});
test('should sanitize HTML by removing dangerous elements', () => {
const unsafeHTML = 'Hello <script>alert("xss")</script><strong>safe</strong>';
const sanitized = engine.validateAndSanitizeHTML(unsafeHTML);
expect(sanitized).not.toContain('<script>');
expect(sanitized).toContain('<strong>safe</strong>');
});
test('should preserve allowed attributes', () => {
const htmlWithAttrs = '<a href="#test" class="fancy" data-track="click">Link</a>';
const sanitized = engine.validateAndSanitizeHTML(htmlWithAttrs);
expect(sanitized).toContain('href="#test"');
expect(sanitized).toContain('class="fancy"');
expect(sanitized).toContain('data-track="click"');
});
test('should remove dangerous href attributes', () => {
const htmlWithDangerousHref = '<a href="javascript:alert(1)">Bad Link</a>';
const sanitized = engine.validateAndSanitizeHTML(htmlWithDangerousHref);
expect(sanitized).not.toContain('javascript:');
});
});
describe('Attribute Preservation', () => {
test('should extract all element attributes', () => {
const mockElement = createMockElement('div', {
attributes: {
class: 'insertr test',
id: 'unique-id',
'data-value': '123',
'aria-label': 'Test element'
}
});
const attributes = engine.extractElementAttributes(mockElement);
expect(attributes.class).toBe('insertr test');
expect(attributes.id).toBe('unique-id');
expect(attributes['data-value']).toBe('123');
expect(attributes['aria-label']).toBe('Test element');
});
test('should restore element attributes correctly', () => {
const mockElement = createMockElement('div');
const attributes = {
class: 'restored',
id: 'restored-id',
'data-test': 'value'
};
engine.restoreElementAttributes(mockElement, attributes);
expect(mockElement.attributes.class).toBe('restored');
expect(mockElement.attributes.id).toBe('restored-id');
expect(mockElement.attributes['data-test']).toBe('value');
});
});
describe('Content Application', () => {
test('should apply HTML content while preserving container attributes', () => {
const mockElement = createMockElement('p', {
classes: ['insertr', 'special'],
attributes: { id: 'preserved-id' }
});
const newHTML = 'Updated <strong class="emph">content</strong>!';
const success = engine.applyFromEditing(mockElement, newHTML);
expect(success).toBe(true);
expect(mockElement.innerHTML).toBe('Updated <strong class="emph">content</strong>!');
// Container attributes should remain unchanged
expect(mockElement.classList.has('insertr')).toBe(true);
expect(mockElement.classList.has('special')).toBe(true);
});
test('should handle invalid HTML gracefully', () => {
const mockElement = createMockElement('p');
const invalidHTML = '<script>malicious()</script>';
const success = engine.applyFromEditing(mockElement, invalidHTML);
// Should handle gracefully (either sanitize or reject)
expect(typeof success).toBe('boolean');
});
});
describe('Plain Text Extraction', () => {
test('should extract plain text preserving structure', () => {
const mockElement = createMockElement('p', {
textContent: 'Hello world and welcome!'
});
const plainText = engine.extractPlainTextWithStructure(mockElement);
expect(plainText).toBe('Hello world and welcome!');
});
test('should handle complex nested content', () => {
const mockElement = createMockElement('p', {
children: [
{ nodeType: 3, textContent: 'Hello ' },
{ nodeType: 1, textContent: 'world' },
{ nodeType: 3, textContent: ' and welcome!' }
],
childNodes: [
{ nodeType: 3, textContent: 'Hello ' },
{ nodeType: 1, textContent: 'world' },
{ nodeType: 3, textContent: ' and welcome!' }
]
});
const plainText = engine.extractPlainTextWithStructure(mockElement);
expect(plainText).toBe('Hello world and welcome!');
});
});
describe('Editable Content Preparation', () => {
test('should prepare content for safe editing', () => {
const mockElement = createMockElement('p', {
innerHTML: 'Hello <strong class="emph">world</strong>!',
children: [{ tagName: 'STRONG' }]
});
const prepared = engine.prepareForEditing(mockElement);
expect(prepared.html).toBe('Hello <strong class="emph">world</strong>!');
expect(prepared.editableHTML).toBeDefined();
expect(prepared.isComplex).toBe(true);
expect(prepared.originalHTML).toBe('Hello <strong class="emph">world</strong>!');
});
});
describe('Content Finalization', () => {
test('should finalize string content', () => {
const mockElement = createMockElement('p');
const editedContent = 'New <strong>content</strong>';
const success = engine.finalizeEditing(mockElement, editedContent);
expect(success).toBe(true);
});
test('should finalize object content', () => {
const mockElement = createMockElement('p');
const editedContent = {
html: 'New <strong>content</strong>',
text: 'New content'
};
const success = engine.finalizeEditing(mockElement, editedContent);
expect(success).toBe(true);
});
test('should handle invalid content gracefully', () => {
const mockElement = createMockElement('p');
const invalidContent = null;
const success = engine.finalizeEditing(mockElement, invalidContent);
expect(success).toBe(false);
});
});
describe('Security and Safety', () => {
test('should allow safe tags', () => {
const safeTags = ['strong', 'em', 'a', 'span', 'p', 'div', 'h1', 'h2', 'h3'];
safeTags.forEach(tag => {
expect(engine.allowedTags.has(tag)).toBe(true);
});
});
test('should allow safe attributes', () => {
const safeAttrs = ['class', 'id', 'href', 'title', 'data-test', 'aria-label'];
safeAttrs.forEach(attr => {
expect(
engine.allowedAttributes.has(attr) ||
attr.startsWith('data-') ||
attr.startsWith('aria-')
).toBe(true);
});
});
test('should create safe editable copy', () => {
const unsafeHTML = '<p>Safe content</p><script>alert("unsafe")</script>';
const safeCopy = engine.createEditableCopy(unsafeHTML);
expect(safeCopy).toContain('<p>Safe content</p>');
expect(safeCopy).not.toContain('<script>');
});
});
describe('Merge with Preservation', () => {
test('should merge content while preserving specific elements', () => {
const originalHTML = '<p>Hello <span class="preserve">world</span>!</p>';
const editedHTML = '<p>Hi <span class="preserve">universe</span>!</p>';
const preserveSelectors = ['.preserve'];
const merged = engine.mergeWithPreservation(originalHTML, editedHTML, preserveSelectors);
// Should preserve the original .preserve element
expect(merged).toContain('world'); // Original preserved content
});
});
});
// Helper function to create mock DOM elements for testing
function createMockElement(tagName, options = {}) {
const element = {
tagName: tagName.toUpperCase(),
innerHTML: options.innerHTML || '',
textContent: options.textContent || options.innerHTML?.replace(/<[^>]*>/g, '') || '',
children: options.children || [],
childNodes: options.childNodes || options.children || [],
classList: new Set(options.classes || []),
attributes: { ...options.attributes } || {},
// Mock methods
querySelector: () => null,
querySelectorAll: () => [],
appendChild: function(child) { this.children.push(child); },
removeChild: function(child) {
const index = this.children.indexOf(child);
if (index > -1) this.children.splice(index, 1);
},
replaceWith: function(newElement) {
// Mock implementation
},
cloneNode: function(deep) {
return createMockElement(tagName, options);
},
setAttribute: function(name, value) {
this.attributes[name] = value;
},
getAttribute: function(name) {
return this.attributes[name];
},
removeAttribute: function(name) {
delete this.attributes[name];
}
};
// Add length property to children for proper iteration
Object.defineProperty(element.children, 'length', {
get: function() { return this.filter(Boolean).length; }
});
// Add classList methods
element.classList.has = (className) => element.classList.has(className);
element.classList.contains = (className) => element.classList.has(className);
element.classList.add = (className) => element.classList.add(className);
return element;
}

View File

@@ -1,327 +0,0 @@
/**
* Unit tests for StyleDetectionEngine
* Tests based on actual demo examples from simple/index.html
*/
import { StyleDetectionEngine } from './style-detection.js';
// Mock DOM environment for testing
const mockDocument = {
createElement: (tagName) => ({
tagName: tagName.toUpperCase(),
classList: new Set(),
attributes: [],
textContent: '',
outerHTML: `<${tagName}></${tagName}>`,
cloneNode: () => mockDocument.createElement(tagName)
})
};
global.document = mockDocument;
describe('StyleDetectionEngine', () => {
let engine;
beforeEach(() => {
engine = new StyleDetectionEngine();
});
describe('Demo Example 1: Styled Strong Element', () => {
test('should detect <strong class="emph"> as Emphasis style', () => {
// Simulate: <p class="insertr">Hello <strong class="emph">world</strong> and welcome!</p>
const mockElement = createMockElement('p', {
children: [
createMockElement('strong', {
classes: ['emph'],
textContent: 'world'
})
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(1);
expect(detectedStyles.has('strong_emph')).toBe(true);
const emphStyle = detectedStyles.get('strong_emph');
expect(emphStyle.name).toBe('Emphasis');
expect(emphStyle.tagName).toBe('strong');
expect(emphStyle.classes).toEqual(['emph']);
expect(emphStyle.textContent).toBe('world');
});
});
describe('Demo Example 2: Styled Link Element', () => {
test('should detect <a class="fancy"> as Fancy Link style', () => {
// Simulate: <p class="insertr">Visit our <a class="fancy" href="#about">about page</a> for more info.</p>
const mockElement = createMockElement('p', {
children: [
createMockElement('a', {
classes: ['fancy'],
attributes: { href: '#about' },
textContent: 'about page'
})
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(1);
const fancyLinkStyle = detectedStyles.get('a_fancy');
expect(fancyLinkStyle.name).toBe('Fancy Link');
expect(fancyLinkStyle.tagName).toBe('a');
expect(fancyLinkStyle.attributes.href).toBe('#about');
});
});
describe('Demo Example 3: Mixed Content with Complex Attributes', () => {
test('should detect link with multiple attributes', () => {
// Simulate: <a href="https://example.com" rel="noopener" target="_blank" class="fancy">our site</a>
const mockElement = createMockElement('p', {
children: [
createMockElement('a', {
classes: ['fancy'],
attributes: {
href: 'https://example.com',
rel: 'noopener',
target: '_blank'
},
textContent: 'our site'
}),
createMockElement('span', {
classes: ['highlight'],
textContent: 'amazing'
})
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(2);
const linkStyle = detectedStyles.get('a_fancy');
expect(linkStyle.attributes.rel).toBe('noopener');
expect(linkStyle.attributes.target).toBe('_blank');
const highlightStyle = detectedStyles.get('span_highlight');
expect(highlightStyle.name).toBe('Highlight');
});
});
describe('Demo Example 4: Multiple Styled Elements', () => {
test('should detect multiple different styled elements', () => {
// Simulate: <strong class="brand">Acme Corp</strong> <span class="highlight">innovative</span> <em class="emph">modern</em>
const mockElement = createMockElement('p', {
children: [
createMockElement('strong', {
classes: ['brand'],
textContent: 'Acme Corp'
}),
createMockElement('span', {
classes: ['highlight'],
textContent: 'innovative'
}),
createMockElement('em', {
classes: ['emph'],
textContent: 'modern'
})
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(3);
expect(detectedStyles.has('strong_brand')).toBe(true);
expect(detectedStyles.has('span_highlight')).toBe(true);
expect(detectedStyles.has('em_emph')).toBe(true);
expect(detectedStyles.get('strong_brand').name).toBe('Brand');
expect(detectedStyles.get('span_highlight').name).toBe('Highlight');
expect(detectedStyles.get('em_emph').name).toBe('Emphasis Italic');
});
});
describe('Demo Example 5: Button with Data Attributes', () => {
test('should detect button with data attributes', () => {
// Simulate: <button class="btn" data-action="signup" data-analytics="cta-main">Sign Up Now</button>
const mockElement = createMockElement('p', {
children: [
createMockElement('button', {
classes: ['btn'],
attributes: {
'data-action': 'signup',
'data-analytics': 'cta-main'
},
textContent: 'Sign Up Now'
})
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(1);
const buttonStyle = detectedStyles.get('button_btn');
expect(buttonStyle.name).toBe('Button Style');
expect(buttonStyle.attributes['data-action']).toBe('signup');
expect(buttonStyle.attributes['data-analytics']).toBe('cta-main');
});
});
describe('Style Name Generation', () => {
test('should generate correct names for common patterns', () => {
expect(engine.generateStyleName('strong', ['highlight-price'], {})).toBe('Highlight Price');
expect(engine.generateStyleName('span', ['brand-color'], {})).toBe('Brand Color');
expect(engine.generateStyleName('a', ['fancy'], {})).toBe('Fancy Link');
expect(engine.generateStyleName('button', ['btn'], {})).toBe('Button Style');
});
test('should handle elements with IDs', () => {
const name = engine.generateStyleName('span', [], { id: 'unique-element' });
expect(name).toBe('Style (unique-element)');
});
test('should fallback to tag names for unstyled elements', () => {
expect(engine.generateStyleName('blockquote', [], {})).toBe('Quote');
expect(engine.generateStyleName('code', [], {})).toBe('Code');
});
});
describe('Template Extraction', () => {
test('should create proper templates with placeholders', () => {
const mockElement = createMockElement('strong', {
classes: ['emph'],
attributes: { 'data-test': 'value' }
});
const template = engine.extractTemplate(mockElement);
expect(template).toContain('{{TEXT}}');
expect(template).toContain('class="emph"');
expect(template).toContain('data-test="value"');
});
});
describe('Element Creation from Templates', () => {
test('should create elements with correct styling', () => {
const styleInfo = {
tagName: 'strong',
classes: ['emph'],
attributes: { 'data-test': 'value' }
};
const element = engine.createElementFromTemplate(styleInfo, 'test content');
expect(element.tagName.toLowerCase()).toBe('strong');
expect(element.textContent).toBe('test content');
expect(element.classList.contains('emph')).toBe(true);
expect(element.getAttribute('data-test')).toBe('value');
});
});
describe('Complex Nesting Detection', () => {
test('should detect when elements have nested styled children', () => {
const mockElement = createMockElement('p', {
children: [
createMockElement('strong', { classes: ['emph'] })
]
});
expect(engine.hasNestedStyledElements(mockElement)).toBe(true);
const simpleElement = createMockElement('p', { children: [] });
expect(engine.hasNestedStyledElements(simpleElement)).toBe(false);
});
});
describe('Attribute Significance Detection', () => {
test('should identify significant attributes', () => {
expect(engine.hasSignificantAttributes({ 'data-action': 'test' })).toBe(true);
expect(engine.hasSignificantAttributes({ 'aria-label': 'test' })).toBe(true);
expect(engine.hasSignificantAttributes({ href: '#link' })).toBe(true);
expect(engine.hasSignificantAttributes({ id: 'unique' })).toBe(true);
expect(engine.hasSignificantAttributes({})).toBe(false);
});
});
describe('Edge Cases', () => {
test('should skip elements without styling', () => {
const mockElement = createMockElement('p', {
children: [
createMockElement('span', { classes: [], attributes: {} }),
createMockElement('strong', { classes: ['emph'] })
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(1);
expect(detectedStyles.has('strong_emph')).toBe(true);
});
test('should handle empty elements', () => {
const mockElement = createMockElement('p', { children: [] });
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(0);
});
test('should handle elements with only whitespace', () => {
const mockElement = createMockElement('p', {
children: [
createMockElement('span', {
classes: ['test'],
textContent: ' '
})
]
});
const detectedStyles = engine.detectStyles(mockElement);
expect(detectedStyles.size).toBe(1);
});
});
});
// Helper function to create mock DOM elements for testing
function createMockElement(tagName, options = {}) {
const element = {
tagName: tagName.toUpperCase(),
classList: new Set(options.classes || []),
children: options.children || [],
textContent: options.textContent || '',
attributes: [],
id: options.attributes?.id || '',
outerHTML: `<${tagName}${formatAttributes(options)}></${tagName}>`,
// Mock methods
cloneNode: (deep) => createMockElement(tagName, options),
setAttribute: (name, value) => {
element.attributes[name] = value;
},
getAttribute: (name) => element.attributes[name],
removeAttribute: (name) => {
delete element.attributes[name];
}
};
// Add attributes
if (options.attributes) {
Object.entries(options.attributes).forEach(([key, value]) => {
element.attributes.push({ name: key, value: value });
});
}
// Add classList methods
element.classList.contains = (className) => element.classList.has(className);
element.classList.add = (className) => element.classList.add(className);
return element;
}
function formatAttributes(options) {
let attrs = '';
if (options.classes && options.classes.length > 0) {
attrs += ` class="${options.classes.join(' ')}"`;
}
if (options.attributes) {
Object.entries(options.attributes).forEach(([key, value]) => {
attrs += ` ${key}="${value}"`;
});
}
return attrs;
}

View File

@@ -1,337 +0,0 @@
/**
* Simple test runner for style preservation system
* Tests our implementation with actual DOM elements
*/
import { styleDetectionEngine } from './style-detection.js';
import { htmlPreservationEngine } from './html-preservation.js';
/**
* Run all style detection tests with real DOM elements
*/
export function runStyleDetectionTests() {
console.log('🧪 Running Style Detection Tests');
console.log('================================');
const results = [];
// Test 1: Demo Example 1 - Styled Strong Element
results.push(testExample1());
// Test 2: Demo Example 2 - Styled Link Element
results.push(testExample2());
// Test 3: Demo Example 4 - Multiple Styled Elements
results.push(testExample4());
// Test 4: Demo Example 5 - Button with Data Attributes
results.push(testExample5());
// Summary
const passed = results.filter(r => r.passed).length;
const total = results.length;
console.log(`\n📊 Test Results: ${passed}/${total} passed`);
if (passed === total) {
console.log('✅ All style detection tests passed!');
} else {
console.log('❌ Some tests failed - see details above');
}
return { passed, total, results };
}
/**
* Test Example 1: <p>Hello <strong class="emph">world</strong> and welcome!</p>
*/
function testExample1() {
console.log('\n🔍 Test 1: Styled Strong Element');
try {
// Create test element
const container = document.createElement('p');
container.className = 'insertr';
container.innerHTML = 'Hello <strong class="emph">world</strong> and welcome!';
// Detect styles
const detectedStyles = styleDetectionEngine.detectStyles(container);
// Validate results
const hasEmphStyle = detectedStyles.has('strong_emph');
const emphStyle = detectedStyles.get('strong_emph');
const correctName = emphStyle?.name === 'Emphasis';
const correctTag = emphStyle?.tagName === 'strong';
const correctClasses = emphStyle?.classes?.includes('emph');
const passed = hasEmphStyle && correctName && correctTag && correctClasses;
console.log(` Detected ${detectedStyles.size} style(s)`);
console.log(` Found "Emphasis" style: ${hasEmphStyle ? '✅' : '❌'}`);
console.log(` Correct name: ${correctName ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'Example 1', passed, details: { hasEmphStyle, correctName, correctTag, correctClasses } };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'Example 1', passed: false, error: error.message };
}
}
/**
* Test Example 2: <p>Visit our <a class="fancy" href="#about">about page</a> for more info.</p>
*/
function testExample2() {
console.log('\n🔍 Test 2: Styled Link Element');
try {
const container = document.createElement('p');
container.className = 'insertr';
container.innerHTML = 'Visit our <a class="fancy" href="#about">about page</a> for more info.';
const detectedStyles = styleDetectionEngine.detectStyles(container);
const hasFancyStyle = detectedStyles.has('a_fancy');
const fancyStyle = detectedStyles.get('a_fancy');
const correctName = fancyStyle?.name === 'Fancy Link';
const hasHref = fancyStyle?.attributes?.href === '#about';
const passed = hasFancyStyle && correctName && hasHref;
console.log(` Detected ${detectedStyles.size} style(s)`);
console.log(` Found "Fancy Link" style: ${hasFancyStyle ? '✅' : '❌'}`);
console.log(` Preserved href attribute: ${hasHref ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'Example 2', passed, details: { hasFancyStyle, correctName, hasHref } };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'Example 2', passed: false, error: error.message };
}
}
/**
* Test Example 4: Multiple styled elements
*/
function testExample4() {
console.log('\n🔍 Test 4: Multiple Styled Elements');
try {
const container = document.createElement('p');
container.className = 'insertr';
container.innerHTML = 'Welcome to <strong class="brand">Acme Corp</strong> where we create <span class="highlight">innovative</span> solutions for <em class="emph">modern</em> businesses.';
const detectedStyles = styleDetectionEngine.detectStyles(container);
const hasBrand = detectedStyles.has('strong_brand');
const hasHighlight = detectedStyles.has('span_highlight');
const hasEmphItalic = detectedStyles.has('em_emph');
const correctCount = detectedStyles.size === 3;
const passed = hasBrand && hasHighlight && hasEmphItalic && correctCount;
console.log(` Detected ${detectedStyles.size} style(s) (expected 3)`);
console.log(` Found "Brand" style: ${hasBrand ? '✅' : '❌'}`);
console.log(` Found "Highlight" style: ${hasHighlight ? '✅' : '❌'}`);
console.log(` Found "Emphasis Italic" style: ${hasEmphItalic ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'Example 4', passed, details: { hasBrand, hasHighlight, hasEmphItalic, correctCount } };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'Example 4', passed: false, error: error.message };
}
}
/**
* Test Example 5: Button with data attributes
*/
function testExample5() {
console.log('\n🔍 Test 5: Button with Data Attributes');
try {
const container = document.createElement('p');
container.className = 'insertr';
container.innerHTML = 'Ready to start? <button class="btn" data-action="signup" data-analytics="cta-main">Sign Up Now</button> and begin your journey!';
const detectedStyles = styleDetectionEngine.detectStyles(container);
const hasButtonStyle = detectedStyles.has('button_btn');
const buttonStyle = detectedStyles.get('button_btn');
const correctName = buttonStyle?.name === 'Button Style';
const hasDataAction = buttonStyle?.attributes?.['data-action'] === 'signup';
const hasDataAnalytics = buttonStyle?.attributes?.['data-analytics'] === 'cta-main';
const passed = hasButtonStyle && correctName && hasDataAction && hasDataAnalytics;
console.log(` Detected ${detectedStyles.size} style(s)`);
console.log(` Found "Button Style": ${hasButtonStyle ? '✅' : '❌'}`);
console.log(` Preserved data-action: ${hasDataAction ? '✅' : '❌'}`);
console.log(` Preserved data-analytics: ${hasDataAnalytics ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'Example 5', passed, details: { hasButtonStyle, correctName, hasDataAction, hasDataAnalytics } };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'Example 5', passed: false, error: error.message };
}
}
/**
* Run HTML preservation tests
*/
export function runHTMLPreservationTests() {
console.log('\n🧪 Running HTML Preservation Tests');
console.log('==================================');
const results = [];
results.push(testHTMLExtraction());
results.push(testHTMLApplication());
results.push(testAttributePreservation());
const passed = results.filter(r => r.passed).length;
const total = results.length;
console.log(`\n📊 HTML Preservation Results: ${passed}/${total} passed`);
return { passed, total, results };
}
function testHTMLExtraction() {
console.log('\n🔍 HTML Extraction Test');
try {
const element = document.createElement('p');
element.className = 'insertr test';
element.id = 'test-element';
element.innerHTML = 'Hello <strong class="emph">world</strong>!';
const extracted = htmlPreservationEngine.extractForEditing(element);
const hasHTML = extracted.html === 'Hello <strong class="emph">world</strong>!';
const hasText = extracted.text === 'Hello world!';
const hasAttributes = extracted.containerAttributes.class === 'insertr test';
const detectsNesting = extracted.hasNestedElements === true;
const passed = hasHTML && hasText && hasAttributes && detectsNesting;
console.log(` HTML extraction: ${hasHTML ? '✅' : '❌'}`);
console.log(` Text extraction: ${hasText ? '✅' : '❌'}`);
console.log(` Attribute preservation: ${hasAttributes ? '✅' : '❌'}`);
console.log(` Nesting detection: ${detectsNesting ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'HTML Extraction', passed };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'HTML Extraction', passed: false, error: error.message };
}
}
function testHTMLApplication() {
console.log('\n🔍 HTML Application Test');
try {
const element = document.createElement('p');
element.className = 'insertr original';
const newHTML = 'Updated <strong class="emph">content</strong>!';
const success = htmlPreservationEngine.applyFromEditing(element, newHTML);
const appliedCorrectly = element.innerHTML === newHTML;
const preservedClass = element.className === 'insertr original';
const passed = success && appliedCorrectly && preservedClass;
console.log(` Application success: ${success ? '✅' : '❌'}`);
console.log(` Content applied: ${appliedCorrectly ? '✅' : '❌'}`);
console.log(` Container class preserved: ${preservedClass ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'HTML Application', passed };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'HTML Application', passed: false, error: error.message };
}
}
function testAttributePreservation() {
console.log('\n🔍 Attribute Preservation Test');
try {
const element = document.createElement('div');
const originalAttrs = {
class: 'insertr test',
id: 'unique-id',
'data-value': '123'
};
// Apply original attributes
Object.entries(originalAttrs).forEach(([key, value]) => {
element.setAttribute(key, value);
});
// Extract and restore
const extracted = htmlPreservationEngine.extractElementAttributes(element);
const newElement = document.createElement('div');
htmlPreservationEngine.restoreElementAttributes(newElement, extracted);
const classRestored = newElement.getAttribute('class') === 'insertr test';
const idRestored = newElement.getAttribute('id') === 'unique-id';
const dataRestored = newElement.getAttribute('data-value') === '123';
const passed = classRestored && idRestored && dataRestored;
console.log(` Class attribute: ${classRestored ? '✅' : '❌'}`);
console.log(` ID attribute: ${idRestored ? '✅' : '❌'}`);
console.log(` Data attribute: ${dataRestored ? '✅' : '❌'}`);
console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
return { test: 'Attribute Preservation', passed };
} catch (error) {
console.log(` Error: ${error.message}`);
console.log(` Result: ❌ FAILED`);
return { test: 'Attribute Preservation', passed: false, error: error.message };
}
}
/**
* Run all tests
*/
export function runAllTests() {
console.log('🚀 Running All Style Preservation System Tests');
console.log('==============================================');
const styleResults = runStyleDetectionTests();
const htmlResults = runHTMLPreservationTests();
const totalPassed = styleResults.passed + htmlResults.passed;
const totalTests = styleResults.total + htmlResults.total;
console.log(`\n🎯 Overall Results: ${totalPassed}/${totalTests} tests passed`);
if (totalPassed === totalTests) {
console.log('🎉 All tests passed! Style preservation system is working correctly.');
} else {
console.log('⚠️ Some tests failed. Review implementation before proceeding.');
}
return {
passed: totalPassed === totalTests,
details: { styleResults, htmlResults }
};
}