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:
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 }
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user