@uplink-protocol/calendar-controller
Version:
Flexible calendar API supporting both calendar and date-picker integrations for any JavaScript framework or library
359 lines (300 loc) • 15.2 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Event Listener Duplicate Prevention Test</title>
<link rel="stylesheet" href="css/advanced-floating-calendar.css">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.test-controls {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.test-button {
background: #007acc;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
font-size: 14px;
}
.test-button:hover {
background: #005a9e;
}
.status {
margin-top: 15px;
padding: 10px;
background: #f0f8ff;
border-left: 4px solid #007acc;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.warning {
background: #fff3cd;
border-left-color: #ffc107;
}
.success {
background: #d4edda;
border-left-color: #28a745;
}
.error {
background: #f8d7da;
border-left-color: #dc3545;
}
</style>
</head>
<body>
<div class="test-controls">
<h3>🔍 Event Listener Duplicate Prevention Test</h3>
<p><strong>This test validates that event listeners are not duplicated on re-render/re-initialization.</strong></p>
<button class="test-button" onclick="testEventListenerDuplication()">Test Event Listener Duplication</button>
<button class="test-button" onclick="forceReInitialize()">Force Re-Initialize</button>
<button class="test-button" onclick="testResizeListeners()">Test Resize Listeners</button>
<button class="test-button" onclick="testDragListeners()">Test Drag Listeners</button>
<button class="test-button" onclick="simulateMultipleRenders()">Simulate Multiple Renders</button>
<button class="test-button" onclick="countEventListeners()">Count Active Listeners</button>
<button class="test-button" onclick="testCleanup()">Test Cleanup</button>
<div class="status" id="status">
Ready for testing. Click buttons above to run tests.
</div>
</div>
<!-- Floating Calendar Widget -->
<div id="floating-calendar-widget" class="floating-calendar-widget">
<div class="calendar-header">
<div class="calendar-controls">
<button class="minimize-btn">−</button>
<button class="close-btn">×</button>
</div>
<h3>Calendar Widget</h3>
<div class="quick-actions">
<button class="quick-action" data-action="today">Today</button>
<button class="quick-action" data-action="tomorrow">Tomorrow</button>
<button class="quick-action" data-action="next-week">Next Week</button>
</div>
</div>
<div class="calendar-content">
<div class="calendar-navigation">
<button class="nav-btn prev-btn">‹</button>
<span class="current-month">December 2024</span>
<button class="nav-btn next-btn">›</button>
</div>
<div class="calendar-grid">
<!-- Calendar will be generated here -->
</div>
</div>
</div>
<!-- Include the advanced floating calendar script -->
<script src="js/calendar-controller.js"></script>
<script src="js/advanced-floating-calendar.js"></script>
<script>
let testCounter = 0;
let initialListenerCounts = {};
function updateStatus(message, type = 'info') {
const statusEl = document.getElementById('status');
statusEl.className = `status ${type}`;
statusEl.innerHTML = `<strong>[Test ${++testCounter}]</strong> ${message}`;
}
// Track event listener additions by monkey-patching addEventListener
function setupEventListenerTracking() {
const listenerCounts = {
document: { keydown: 0, mousemove: 0, mouseup: 0 },
window: { resize: 0 },
total: 0
};
const originalAddEventListener = EventTarget.prototype.addEventListener;
const originalRemoveEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (this === document) {
if (listenerCounts.document[type] !== undefined) {
listenerCounts.document[type]++;
listenerCounts.total++;
}
} else if (this === window) {
if (listenerCounts.window[type] !== undefined) {
listenerCounts.window[type]++;
listenerCounts.total++;
}
}
return originalAddEventListener.call(this, type, listener, options);
};
EventTarget.prototype.removeEventListener = function(type, listener, options) {
if (this === document) {
if (listenerCounts.document[type] !== undefined && listenerCounts.document[type] > 0) {
listenerCounts.document[type]--;
listenerCounts.total--;
}
} else if (this === window) {
if (listenerCounts.window[type] !== undefined && listenerCounts.window[type] > 0) {
listenerCounts.window[type]--;
listenerCounts.total--;
}
}
return originalRemoveEventListener.call(this, type, listener, options);
};
return listenerCounts;
}
function testEventListenerDuplication() {
updateStatus('Testing event listener duplication prevention...', 'info');
const counts = setupEventListenerTracking();
initialListenerCounts = { ...counts };
// Re-initialize the calendar multiple times
for (let i = 0; i < 5; i++) {
if (window.advancedCalendar) {
window.advancedCalendar.setupEventListeners();
}
}
const duplicates = [];
Object.keys(counts.document).forEach(type => {
if (counts.document[type] > 1) {
duplicates.push(`document.${type}: ${counts.document[type]}`);
}
});
Object.keys(counts.window).forEach(type => {
if (counts.window[type] > 1) {
duplicates.push(`window.${type}: ${counts.window[type]}`);
}
});
if (duplicates.length > 0) {
updateStatus(`❌ Found duplicate listeners: ${duplicates.join(', ')}`, 'error');
} else {
updateStatus('✅ No duplicate event listeners detected!', 'success');
}
}
function forceReInitialize() {
updateStatus('Force re-initializing calendar...', 'info');
if (window.advancedCalendar) {
// Simulate re-initialization
window.advancedCalendar.setupEventListeners();
window.advancedCalendar.setupDragFunctionality();
updateStatus('✅ Calendar re-initialized. Check for duplicates with other tests.', 'success');
} else {
updateStatus('❌ Calendar not found', 'error');
}
}
function testResizeListeners() {
updateStatus('Testing resize event listener handling...', 'info');
let resizeCallCount = 0;
const originalHandleResize = window.advancedCalendar?.handleResize;
if (originalHandleResize) {
window.advancedCalendar.handleResize = function() {
resizeCallCount++;
return originalHandleResize.call(this);
};
// Trigger resize event
window.dispatchEvent(new Event('resize'));
setTimeout(() => {
if (resizeCallCount === 1) {
updateStatus('✅ Resize handler called exactly once', 'success');
} else {
updateStatus(`❌ Resize handler called ${resizeCallCount} times`, 'error');
}
// Restore original method
window.advancedCalendar.handleResize = originalHandleResize;
}, 200);
} else {
updateStatus('❌ Calendar resize handler not found', 'error');
}
}
function testDragListeners() {
updateStatus('Testing drag event listener handling...', 'info');
let mouseMoveCallCount = 0;
let mouseUpCallCount = 0;
const originalHandleMouseMove = window.advancedCalendar?.handleMouseMove;
const originalHandleMouseUp = window.advancedCalendar?.handleMouseUp;
if (originalHandleMouseMove && originalHandleMouseUp) {
window.advancedCalendar.handleMouseMove = function(e) {
mouseMoveCallCount++;
return originalHandleMouseMove.call(this, e);
};
window.advancedCalendar.handleMouseUp = function(e) {
mouseUpCallCount++;
return originalHandleMouseUp.call(this, e);
};
// Trigger drag events
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 100, clientY: 100 }));
document.dispatchEvent(new MouseEvent('mouseup'));
setTimeout(() => {
const results = [];
if (mouseMoveCallCount <= 1) results.push('✅ MouseMove: OK');
else results.push(`❌ MouseMove called ${mouseMoveCallCount} times`);
if (mouseUpCallCount <= 1) results.push('✅ MouseUp: OK');
else results.push(`❌ MouseUp called ${mouseUpCallCount} times`);
updateStatus(`Drag listeners test: ${results.join(', ')}`, mouseMoveCallCount <= 1 && mouseUpCallCount <= 1 ? 'success' : 'error');
// Restore original methods
window.advancedCalendar.handleMouseMove = originalHandleMouseMove;
window.advancedCalendar.handleMouseUp = originalHandleMouseUp;
}, 100);
} else {
updateStatus('❌ Calendar drag handlers not found', 'error');
}
}
function simulateMultipleRenders() {
updateStatus('Simulating multiple calendar renders...', 'info');
if (window.advancedCalendar) {
// Render multiple times and check for listener accumulation
for (let i = 0; i < 10; i++) {
window.advancedCalendar.render();
}
updateStatus('✅ Multiple renders completed. Use other tests to check for issues.', 'success');
} else {
updateStatus('❌ Calendar not found', 'error');
}
}
function countEventListeners() {
updateStatus('Counting active event listeners...', 'info');
if (window.advancedCalendar) {
const calendar = window.advancedCalendar;
const counts = {
documentListeners: calendar.documentListeners ? calendar.documentListeners.size : 'N/A',
windowListeners: calendar.windowListeners ? calendar.windowListeners.size : 'N/A',
dateCells: calendar.widget.querySelectorAll('[data-date]').length
};
updateStatus(`Active listeners - Document: ${counts.documentListeners}, Window: ${counts.windowListeners}, Date cells: ${counts.dateCells}`, 'info');
} else {
updateStatus('❌ Calendar not found', 'error');
}
}
function testCleanup() {
updateStatus('Testing event listener cleanup...', 'info');
if (window.advancedCalendar) {
const calendar = window.advancedCalendar;
// Check initial state
const initialDocListeners = calendar.documentListeners.size;
const initialWinListeners = calendar.windowListeners.size;
// Test cleanup
calendar.removeEventListeners();
const finalDocListeners = calendar.documentListeners.size;
const finalWinListeners = calendar.windowListeners.size;
if (finalDocListeners === 0 && finalWinListeners === 0) {
updateStatus(`✅ Cleanup successful! Removed ${initialDocListeners} document and ${initialWinListeners} window listeners`, 'success');
// Re-setup listeners for continued testing
calendar.setupEventListeners();
} else {
updateStatus(`❌ Cleanup failed! Document: ${finalDocListeners}, Window: ${finalWinListeners}`, 'error');
}
} else {
updateStatus('❌ Calendar not found', 'error');
}
}
// Initialize test when page loads
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
updateStatus('Event listener test page loaded. Calendar should be initialized.', 'success');
}, 500);
});
</script>
</body>
</html>