- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
713 lines
24 KiB
JavaScript
713 lines
24 KiB
JavaScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
import { LiveComponentDevTools } from '../../../../../resources/js/modules/LiveComponentDevTools.js';
|
|
import { Core } from '../../../../../resources/js/modules/core.js';
|
|
|
|
describe('LiveComponentDevTools', () => {
|
|
let devTools;
|
|
let mockLocalStorage;
|
|
|
|
beforeEach(() => {
|
|
// Reset DOM
|
|
document.body.innerHTML = '';
|
|
|
|
// Mock localStorage
|
|
mockLocalStorage = {};
|
|
global.localStorage = {
|
|
getItem: jest.fn((key) => mockLocalStorage[key] || null),
|
|
setItem: jest.fn((key, value) => { mockLocalStorage[key] = value; }),
|
|
removeItem: jest.fn((key) => { delete mockLocalStorage[key]; }),
|
|
clear: jest.fn(() => { mockLocalStorage = {}; })
|
|
};
|
|
|
|
// Set development environment
|
|
document.documentElement.dataset.env = 'development';
|
|
|
|
// Mock Core.emit
|
|
Core.emit = jest.fn();
|
|
Core.on = jest.fn();
|
|
|
|
// Mock performance.memory (Chrome-specific)
|
|
if (!window.performance.memory) {
|
|
window.performance.memory = {
|
|
usedJSHeapSize: 10000000,
|
|
totalJSHeapSize: 20000000,
|
|
jsHeapSizeLimit: 100000000
|
|
};
|
|
}
|
|
|
|
// Mock window.LiveComponent
|
|
window.LiveComponent = {
|
|
enableDevTools: jest.fn()
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Cleanup
|
|
if (devTools && devTools.overlay) {
|
|
devTools.overlay.remove();
|
|
}
|
|
devTools = null;
|
|
});
|
|
|
|
describe('Initialization', () => {
|
|
it('initializes in development mode', () => {
|
|
document.documentElement.dataset.env = 'development';
|
|
devTools = new LiveComponentDevTools();
|
|
|
|
expect(devTools.isEnabled).toBe(true);
|
|
expect(devTools.overlay).not.toBeNull();
|
|
expect(document.getElementById('livecomponent-devtools')).not.toBeNull();
|
|
});
|
|
|
|
it('does not initialize in production mode by default', () => {
|
|
document.documentElement.dataset.env = 'production';
|
|
devTools = new LiveComponentDevTools();
|
|
|
|
expect(devTools.isEnabled).toBe(false);
|
|
expect(devTools.overlay).toBeNull();
|
|
});
|
|
|
|
it('initializes in production with explicit localStorage flag', () => {
|
|
document.documentElement.dataset.env = 'production';
|
|
mockLocalStorage['livecomponent_devtools'] = 'true';
|
|
|
|
devTools = new LiveComponentDevTools();
|
|
|
|
expect(devTools.isEnabled).toBe(true);
|
|
expect(devTools.overlay).not.toBeNull();
|
|
});
|
|
|
|
it('creates all required UI elements', () => {
|
|
devTools = new LiveComponentDevTools();
|
|
|
|
const overlay = document.getElementById('livecomponent-devtools');
|
|
expect(overlay).not.toBeNull();
|
|
|
|
// Check header
|
|
expect(overlay.querySelector('.lc-devtools__header')).not.toBeNull();
|
|
expect(overlay.querySelector('.lc-devtools__title')).not.toBeNull();
|
|
|
|
// Check tabs
|
|
expect(overlay.querySelector('[data-tab="components"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-tab="actions"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-tab="events"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-tab="performance"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-tab="network"]')).not.toBeNull();
|
|
|
|
// Check panes
|
|
expect(overlay.querySelector('[data-pane="components"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-pane="actions"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-pane="events"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-pane="performance"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-pane="network"]')).not.toBeNull();
|
|
|
|
// Check action buttons
|
|
expect(overlay.querySelector('[data-action="minimize"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-action="close"]')).not.toBeNull();
|
|
expect(overlay.querySelector('[data-action="toggle-badges"]')).not.toBeNull();
|
|
});
|
|
|
|
it('connects to LiveComponent manager', (done) => {
|
|
devTools = new LiveComponentDevTools();
|
|
|
|
setTimeout(() => {
|
|
expect(window.LiveComponent.enableDevTools).toHaveBeenCalledWith(devTools);
|
|
done();
|
|
}, 150);
|
|
});
|
|
});
|
|
|
|
describe('Component Discovery', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('discovers components via data-component-id attribute', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="counter:demo" data-component-name="Counter">
|
|
<span>Count: 0</span>
|
|
</div>
|
|
<div data-component-id="user-stats:123" data-component-name="UserStats">
|
|
<p>Stats</p>
|
|
</div>
|
|
`;
|
|
|
|
devTools.refreshComponents();
|
|
|
|
expect(devTools.components.size).toBe(2);
|
|
expect(devTools.components.has('counter:demo')).toBe(true);
|
|
expect(devTools.components.has('user-stats:123')).toBe(true);
|
|
|
|
const counter = devTools.components.get('counter:demo');
|
|
expect(counter.name).toBe('Counter');
|
|
expect(counter.id).toBe('counter:demo');
|
|
});
|
|
|
|
it('extracts component props from data attributes', () => {
|
|
document.body.innerHTML = `
|
|
<div
|
|
data-component-id="product:5"
|
|
data-component-name="Product"
|
|
data-product-id="5"
|
|
data-category="electronics"
|
|
>
|
|
Product
|
|
</div>
|
|
`;
|
|
|
|
devTools.refreshComponents();
|
|
|
|
const product = devTools.components.get('product:5');
|
|
expect(product.props.productId).toBe('5');
|
|
expect(product.props.category).toBe('electronics');
|
|
expect(product.props.componentId).toBeUndefined(); // Excluded
|
|
expect(product.props.componentName).toBeUndefined(); // Excluded
|
|
});
|
|
|
|
it('handles components without data-component-name', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="anonymous:1">Content</div>
|
|
`;
|
|
|
|
devTools.refreshComponents();
|
|
|
|
const component = devTools.components.get('anonymous:1');
|
|
expect(component.name).toBe('Unknown');
|
|
});
|
|
});
|
|
|
|
describe('Action Logging', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('logs action executions', () => {
|
|
const startTime = 100;
|
|
const endTime = 150;
|
|
|
|
devTools.logAction('counter:demo', 'increment', { amount: 5 }, startTime, endTime, true);
|
|
|
|
expect(devTools.actionLog.length).toBe(1);
|
|
|
|
const logEntry = devTools.actionLog[0];
|
|
expect(logEntry.componentId).toBe('counter:demo');
|
|
expect(logEntry.actionName).toBe('increment');
|
|
expect(logEntry.params).toEqual({ amount: 5 });
|
|
expect(logEntry.duration).toBe(50);
|
|
expect(logEntry.success).toBe(true);
|
|
expect(logEntry.error).toBeNull();
|
|
});
|
|
|
|
it('logs action failures with error messages', () => {
|
|
devTools.logAction('form:contact', 'submit', {}, 100, 200, false, 'Validation failed');
|
|
|
|
const logEntry = devTools.actionLog[0];
|
|
expect(logEntry.success).toBe(false);
|
|
expect(logEntry.error).toBe('Validation failed');
|
|
});
|
|
|
|
it('maintains max 100 action log entries', () => {
|
|
// Add 110 actions
|
|
for (let i = 0; i < 110; i++) {
|
|
devTools.logAction(`component:${i}`, 'action', {}, 0, 10, true);
|
|
}
|
|
|
|
expect(devTools.actionLog.length).toBe(100);
|
|
// Most recent should be at index 0
|
|
expect(devTools.actionLog[0].componentId).toBe('component:109');
|
|
});
|
|
|
|
it('records action execution for performance profiling when recording', () => {
|
|
devTools.isRecording = true;
|
|
|
|
devTools.logAction('counter:demo', 'increment', {}, 100, 150, true);
|
|
|
|
expect(devTools.performanceRecording.length).toBe(1);
|
|
expect(devTools.performanceRecording[0].type).toBe('action');
|
|
expect(devTools.performanceRecording[0].duration).toBe(50);
|
|
});
|
|
|
|
it('does not record performance when not recording', () => {
|
|
devTools.isRecording = false;
|
|
|
|
devTools.logAction('counter:demo', 'increment', {}, 100, 150, true);
|
|
|
|
expect(devTools.performanceRecording.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Event Logging', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('logs events with source tracking', () => {
|
|
devTools.logEvent('user:updated', { userId: 123 }, 'client');
|
|
|
|
expect(devTools.eventLog.length).toBe(1);
|
|
|
|
const logEntry = devTools.eventLog[0];
|
|
expect(logEntry.eventName).toBe('user:updated');
|
|
expect(logEntry.data).toEqual({ userId: 123 });
|
|
expect(logEntry.source).toBe('client');
|
|
});
|
|
|
|
it('defaults source to client', () => {
|
|
devTools.logEvent('notification:received', { message: 'Hello' });
|
|
|
|
expect(devTools.eventLog[0].source).toBe('client');
|
|
});
|
|
|
|
it('maintains max 100 event log entries', () => {
|
|
// Add 110 events
|
|
for (let i = 0; i < 110; i++) {
|
|
devTools.logEvent(`event:${i}`, {}, 'client');
|
|
}
|
|
|
|
expect(devTools.eventLog.length).toBe(100);
|
|
// Most recent should be at index 0
|
|
expect(devTools.eventLog[0].eventName).toBe('event:109');
|
|
});
|
|
|
|
it('intercepts Core.emit for automatic event logging', () => {
|
|
const originalEmit = Core.emit;
|
|
|
|
devTools.interceptCoreEmit();
|
|
|
|
Core.emit('test:event', { data: 'test' });
|
|
|
|
expect(devTools.eventLog.length).toBe(1);
|
|
expect(devTools.eventLog[0].eventName).toBe('test:event');
|
|
expect(devTools.eventLog[0].data).toEqual({ data: 'test' });
|
|
expect(devTools.eventLog[0].source).toBe('client');
|
|
});
|
|
});
|
|
|
|
describe('Network Monitoring', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
|
|
// Mock original fetch
|
|
global.originalFetch = global.fetch;
|
|
global.fetch = jest.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Restore original fetch
|
|
global.fetch = global.originalFetch;
|
|
});
|
|
|
|
it('intercepts fetch requests', async () => {
|
|
global.fetch.mockResolvedValue({
|
|
status: 200,
|
|
ok: true
|
|
});
|
|
|
|
devTools.monitorNetworkRequests();
|
|
|
|
await window.fetch('/api/users');
|
|
|
|
expect(devTools.networkLog.length).toBe(1);
|
|
|
|
const logEntry = devTools.networkLog[0];
|
|
expect(logEntry.method).toBe('GET');
|
|
expect(logEntry.url).toBe('/api/users');
|
|
expect(logEntry.status).toBe(200);
|
|
expect(logEntry.duration).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('logs POST requests with correct method', async () => {
|
|
global.fetch.mockResolvedValue({
|
|
status: 201,
|
|
ok: true
|
|
});
|
|
|
|
devTools.monitorNetworkRequests();
|
|
|
|
await window.fetch('/api/users', { method: 'POST' });
|
|
|
|
expect(devTools.networkLog[0].method).toBe('POST');
|
|
expect(devTools.networkLog[0].status).toBe(201);
|
|
});
|
|
|
|
it('maintains max 50 network log entries', async () => {
|
|
global.fetch.mockResolvedValue({ status: 200, ok: true });
|
|
|
|
devTools.monitorNetworkRequests();
|
|
|
|
// Make 60 requests
|
|
for (let i = 0; i < 60; i++) {
|
|
await window.fetch(`/api/request/${i}`);
|
|
}
|
|
|
|
expect(devTools.networkLog.length).toBe(50);
|
|
});
|
|
|
|
it('logs failed requests', async () => {
|
|
global.fetch.mockRejectedValue(new Error('Network error'));
|
|
|
|
devTools.monitorNetworkRequests();
|
|
|
|
try {
|
|
await window.fetch('/api/fail');
|
|
} catch (error) {
|
|
// Expected
|
|
}
|
|
|
|
expect(devTools.networkLog.length).toBe(1);
|
|
expect(devTools.networkLog[0].status).toBe('Error');
|
|
});
|
|
});
|
|
|
|
describe('Tab Switching', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
devTools.open();
|
|
});
|
|
|
|
it('switches to actions tab', () => {
|
|
devTools.switchTab('actions');
|
|
|
|
expect(devTools.activeTab).toBe('actions');
|
|
|
|
const actionsTab = devTools.overlay.querySelector('[data-tab="actions"]');
|
|
const actionsPane = devTools.overlay.querySelector('[data-pane="actions"]');
|
|
|
|
expect(actionsTab.classList.contains('lc-devtools__tab--active')).toBe(true);
|
|
expect(actionsPane.classList.contains('lc-devtools__pane--active')).toBe(true);
|
|
});
|
|
|
|
it('deactivates previous tab when switching', () => {
|
|
devTools.switchTab('events');
|
|
|
|
const componentsTab = devTools.overlay.querySelector('[data-tab="components"]');
|
|
const componentsPane = devTools.overlay.querySelector('[data-pane="components"]');
|
|
|
|
expect(componentsTab.classList.contains('lc-devtools__tab--active')).toBe(false);
|
|
expect(componentsPane.classList.contains('lc-devtools__pane--active')).toBe(false);
|
|
});
|
|
|
|
it('refreshes content when switching tabs', () => {
|
|
devTools.renderActionLog = jest.fn();
|
|
|
|
devTools.switchTab('actions');
|
|
|
|
expect(devTools.renderActionLog).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Open/Close/Minimize', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('opens DevTools and emits event', () => {
|
|
devTools.open();
|
|
|
|
expect(devTools.isOpen).toBe(true);
|
|
expect(devTools.overlay.style.display).toBe('block');
|
|
expect(Core.emit).toHaveBeenCalledWith('devtools:opened');
|
|
});
|
|
|
|
it('closes DevTools and emits event', () => {
|
|
devTools.open();
|
|
devTools.close();
|
|
|
|
expect(devTools.isOpen).toBe(false);
|
|
expect(devTools.overlay.style.display).toBe('none');
|
|
expect(Core.emit).toHaveBeenCalledWith('devtools:closed');
|
|
});
|
|
|
|
it('toggles DevTools visibility', () => {
|
|
expect(devTools.isOpen).toBe(false);
|
|
|
|
devTools.toggle();
|
|
expect(devTools.isOpen).toBe(true);
|
|
|
|
devTools.toggle();
|
|
expect(devTools.isOpen).toBe(false);
|
|
});
|
|
|
|
it('minimizes and unminimizes DevTools', () => {
|
|
devTools.open();
|
|
|
|
devTools.toggleMinimize();
|
|
expect(devTools.overlay.classList.contains('lc-devtools--minimized')).toBe(true);
|
|
|
|
devTools.toggleMinimize();
|
|
expect(devTools.overlay.classList.contains('lc-devtools--minimized')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Keyboard Shortcuts', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('toggles DevTools with Ctrl+Shift+D', () => {
|
|
const event = new KeyboardEvent('keydown', {
|
|
key: 'D',
|
|
ctrlKey: true,
|
|
shiftKey: true
|
|
});
|
|
|
|
document.dispatchEvent(event);
|
|
|
|
expect(devTools.isOpen).toBe(true);
|
|
});
|
|
|
|
it('toggles DevTools with Cmd+Shift+D on Mac', () => {
|
|
const event = new KeyboardEvent('keydown', {
|
|
key: 'D',
|
|
metaKey: true, // Cmd key on Mac
|
|
shiftKey: true
|
|
});
|
|
|
|
document.dispatchEvent(event);
|
|
|
|
expect(devTools.isOpen).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('DOM Badges', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('creates DOM badges for components', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="counter:demo" data-component-name="Counter">
|
|
Counter
|
|
</div>
|
|
`;
|
|
|
|
devTools.updateDomBadges();
|
|
|
|
expect(devTools.domBadges.size).toBe(1);
|
|
expect(devTools.domBadges.has('counter:demo')).toBe(true);
|
|
|
|
const badge = document.querySelector('.lc-dom-badge[data-component-id="counter:demo"]');
|
|
expect(badge).not.toBeNull();
|
|
expect(badge.textContent).toContain('Counter');
|
|
});
|
|
|
|
it('updates badge activity counter', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="counter:demo" data-component-name="Counter"></div>
|
|
`;
|
|
|
|
devTools.updateDomBadges();
|
|
devTools.updateBadgeActivity('counter:demo');
|
|
|
|
const badgeData = devTools.domBadges.get('counter:demo');
|
|
expect(badgeData.actionCount).toBe(1);
|
|
|
|
const actionsSpan = badgeData.badge.querySelector('.lc-badge-actions');
|
|
expect(actionsSpan.textContent).toBe('1 action');
|
|
});
|
|
|
|
it('pluralizes action count correctly', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="counter:demo" data-component-name="Counter"></div>
|
|
`;
|
|
|
|
devTools.updateDomBadges();
|
|
devTools.updateBadgeActivity('counter:demo');
|
|
devTools.updateBadgeActivity('counter:demo');
|
|
|
|
const actionsSpan = devTools.domBadges.get('counter:demo').badge.querySelector('.lc-badge-actions');
|
|
expect(actionsSpan.textContent).toBe('2 actions');
|
|
});
|
|
|
|
it('toggles badge visibility', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="counter:demo" data-component-name="Counter"></div>
|
|
`;
|
|
|
|
devTools.updateDomBadges();
|
|
|
|
const badgeData = devTools.domBadges.get('counter:demo');
|
|
expect(badgeData.badge.style.display).not.toBe('none');
|
|
|
|
devTools.toggleBadges();
|
|
expect(badgeData.badge.style.display).toBe('none');
|
|
|
|
devTools.toggleBadges();
|
|
expect(badgeData.badge.style.display).toBe('block');
|
|
});
|
|
|
|
it('cleans up badges for removed components', () => {
|
|
document.body.innerHTML = `
|
|
<div data-component-id="counter:demo" data-component-name="Counter"></div>
|
|
`;
|
|
|
|
devTools.updateDomBadges();
|
|
expect(devTools.domBadges.size).toBe(1);
|
|
|
|
// Remove component from DOM
|
|
document.body.innerHTML = '';
|
|
|
|
devTools.cleanupRemovedBadges();
|
|
expect(devTools.domBadges.size).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Performance Profiling', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('starts performance recording', () => {
|
|
devTools.startPerformanceRecording();
|
|
|
|
expect(devTools.isRecording).toBe(true);
|
|
expect(devTools.performanceRecording).toEqual([]);
|
|
expect(devTools.memorySnapshots.length).toBeGreaterThan(0); // Initial snapshot
|
|
});
|
|
|
|
it('stops performance recording', () => {
|
|
devTools.startPerformanceRecording();
|
|
devTools.stopPerformanceRecording();
|
|
|
|
expect(devTools.isRecording).toBe(false);
|
|
});
|
|
|
|
it('records component renders', () => {
|
|
devTools.isRecording = true;
|
|
|
|
devTools.recordComponentRender('counter:demo', 25, 100, 125);
|
|
|
|
expect(devTools.performanceRecording.length).toBe(1);
|
|
expect(devTools.performanceRecording[0].type).toBe('render');
|
|
expect(devTools.performanceRecording[0].componentId).toBe('counter:demo');
|
|
expect(devTools.performanceRecording[0].duration).toBe(25);
|
|
});
|
|
|
|
it('does not record when not recording', () => {
|
|
devTools.isRecording = false;
|
|
|
|
devTools.recordComponentRender('counter:demo', 25, 100, 125);
|
|
|
|
expect(devTools.performanceRecording.length).toBe(0);
|
|
});
|
|
|
|
it('takes memory snapshots', () => {
|
|
devTools.takeMemorySnapshot();
|
|
|
|
expect(devTools.memorySnapshots.length).toBe(1);
|
|
|
|
const snapshot = devTools.memorySnapshots[0];
|
|
expect(snapshot.usedJSHeapSize).toBeDefined();
|
|
expect(snapshot.totalJSHeapSize).toBeDefined();
|
|
expect(snapshot.jsHeapSizeLimit).toBeDefined();
|
|
expect(snapshot.timestamp).toBeDefined();
|
|
});
|
|
|
|
it('maintains max 100 performance recordings', () => {
|
|
devTools.isRecording = true;
|
|
|
|
for (let i = 0; i < 110; i++) {
|
|
devTools.recordComponentRender(`component:${i}`, 10, 0, 10);
|
|
}
|
|
|
|
expect(devTools.performanceRecording.length).toBe(100);
|
|
});
|
|
|
|
it('maintains max 100 memory snapshots', () => {
|
|
for (let i = 0; i < 110; i++) {
|
|
devTools.takeMemorySnapshot();
|
|
}
|
|
|
|
expect(devTools.memorySnapshots.length).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe('Clear Operations', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('clears action log', () => {
|
|
devTools.logAction('component:1', 'action', {}, 0, 10, true);
|
|
devTools.logAction('component:2', 'action', {}, 0, 10, true);
|
|
|
|
expect(devTools.actionLog.length).toBe(2);
|
|
|
|
devTools.clearActionLog();
|
|
|
|
expect(devTools.actionLog.length).toBe(0);
|
|
});
|
|
|
|
it('clears event log', () => {
|
|
devTools.logEvent('event:1', {});
|
|
devTools.logEvent('event:2', {});
|
|
|
|
expect(devTools.eventLog.length).toBe(2);
|
|
|
|
devTools.clearEventLog();
|
|
|
|
expect(devTools.eventLog.length).toBe(0);
|
|
});
|
|
|
|
it('clears network log', () => {
|
|
devTools.networkLog = [
|
|
{ timestamp: Date.now(), method: 'GET', url: '/api/users', status: 200, duration: 50 },
|
|
{ timestamp: Date.now(), method: 'POST', url: '/api/posts', status: 201, duration: 100 }
|
|
];
|
|
|
|
expect(devTools.networkLog.length).toBe(2);
|
|
|
|
devTools.clearNetworkLog();
|
|
|
|
expect(devTools.networkLog.length).toBe(0);
|
|
});
|
|
|
|
it('clears performance data', () => {
|
|
devTools.performanceRecording = [{ type: 'action', duration: 10 }];
|
|
devTools.componentRenderTimes.set('component:1', [10, 20]);
|
|
devTools.actionExecutionTimes.set('component:1:action', [5, 10]);
|
|
devTools.memorySnapshots = [{ timestamp: Date.now() }];
|
|
|
|
devTools.clearPerformanceData();
|
|
|
|
expect(devTools.performanceRecording.length).toBe(0);
|
|
expect(devTools.componentRenderTimes.size).toBe(0);
|
|
expect(devTools.actionExecutionTimes.size).toBe(0);
|
|
expect(devTools.memorySnapshots.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Utility Methods', () => {
|
|
beforeEach(() => {
|
|
devTools = new LiveComponentDevTools();
|
|
});
|
|
|
|
it('formats bytes to human-readable string', () => {
|
|
expect(devTools.formatBytes(0)).toBe('0 B');
|
|
expect(devTools.formatBytes(1024)).toBe('1 KB');
|
|
expect(devTools.formatBytes(1048576)).toBe('1 MB');
|
|
expect(devTools.formatBytes(1073741824)).toBe('1 GB');
|
|
expect(devTools.formatBytes(1536)).toBe('1.5 KB');
|
|
});
|
|
|
|
it('checks if enabled in development mode', () => {
|
|
document.documentElement.dataset.env = 'development';
|
|
expect(devTools.checkIfEnabled()).toBe(true);
|
|
});
|
|
|
|
it('checks if enabled with localStorage override', () => {
|
|
document.documentElement.dataset.env = 'production';
|
|
mockLocalStorage['livecomponent_devtools'] = 'true';
|
|
expect(devTools.checkIfEnabled()).toBe(true);
|
|
});
|
|
|
|
it('is disabled in production without override', () => {
|
|
document.documentElement.dataset.env = 'production';
|
|
expect(devTools.checkIfEnabled()).toBe(false);
|
|
});
|
|
});
|
|
});
|