Corp/backend/test_calendar_frontend_simple.html
Игорь Надеин f8a8b3b2f2 fix(calendar): improve integration initialization and fallback to legacy mode
- Fixed calendar_scripts.html to wait for DOM ready before initialization
- Added retry logic if widget not found on first attempt
- Changed integration fallback: show ALL events when no calendars configured
- Changed behavior: if no calendars selected, show all events (legacy mode)
- This ensures backward compatibility with existing events

User feedback: Integration wasn't initializing on page load, button '+' didn't work.
Solution: Auto-init with retry + always show events (no empty state).
2026-02-11 11:26:25 +03:00

721 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calendar Frontend Test - Simple Version</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
padding: 20px;
background: #f5f5f5;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.test-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
}
h2 {
font-size: 20px;
color: #333;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.btn-primary {
background: #0d6efd;
color: white;
}
.btn-primary:hover {
background: #0b5ed7;
}
.btn-secondary {
background: #6c757d;
color: white;
margin-left: 10px;
}
.btn-secondary:hover {
background: #5c636a;
}
.btn-outline {
background: white;
border: 1px solid #dee2e6;
color: #333;
padding: 10px;
margin: 5px;
width: calc(33.33% - 10px);
display: inline-block;
}
.btn-outline:hover {
background: #f8f9fa;
border-color: #0d6efd;
}
.test-controls {
display: flex;
flex-wrap: wrap;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
th {
background: #f8f9fa;
font-weight: 600;
}
.badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.badge-success {
background: #28a745;
color: white;
}
.badge-danger {
background: #dc3545;
color: white;
}
.badge-secondary {
background: #6c757d;
color: white;
}
.log-console {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 13px;
max-height: 400px;
overflow-y: auto;
}
.log-entry {
margin: 3px 0;
padding: 2px 0;
}
.log-info { color: #66d9ef; }
.log-success { color: #a6e22e; }
.log-error { color: #f92672; }
.log-warn { color: #e6db74; }
.calendar-list {
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
max-height: 400px;
overflow-y: auto;
}
.calendar-item {
padding: 10px;
margin-bottom: 8px;
border: 1px solid #e9ecef;
border-radius: 4px;
display: flex;
align-items: center;
gap: 10px;
background: white;
}
.calendar-item:hover {
background: #f8f9fa;
border-color: #0d6efd;
}
.calendar-color {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid rgba(0,0,0,0.1);
}
.calendar-info {
flex: 1;
}
.calendar-title {
font-weight: 600;
font-size: 14px;
}
.calendar-meta {
font-size: 12px;
color: #6c757d;
}
.text-muted {
color: #6c757d;
}
.text-center {
text-align: center;
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>🧪 Calendar Frontend Test Suite (Offline Version)</h1>
<!-- Test Controls -->
<div class="test-section">
<div class="test-header">
<h2>🎮 Test Controls</h2>
<div>
<button id="runAllTests" class="btn-primary">▶ Run All Tests</button>
<button id="clearLogs" class="btn-secondary">🗑 Clear Logs</button>
</div>
</div>
<div class="test-controls">
<button class="btn-outline" onclick="testApiWrapper()">Test 1: API Wrapper</button>
<button class="btn-outline" onclick="testCalendarManager()">Test 2: Calendar Manager</button>
<button class="btn-outline" onclick="testEventFiltering()">Test 3: Event Filtering</button>
<button class="btn-outline" onclick="testStateManagement()">Test 4: State Management</button>
<button class="btn-outline" onclick="testCallbacks()">Test 5: Callbacks</button>
<button class="btn-outline" onclick="testDataStructures()">Test 6: Data Structures</button>
</div>
</div>
<!-- Test Results -->
<div class="test-section">
<div class="test-header">
<h2>📊 Test Results</h2>
<div id="testSummary">
<span class="badge badge-secondary">Ready</span>
</div>
</div>
<table>
<thead>
<tr>
<th>Test</th>
<th>Status</th>
<th>Duration</th>
<th>Details</th>
</tr>
</thead>
<tbody id="testResultsTable">
<tr>
<td colspan="4" class="text-center text-muted">No tests run yet</td>
</tr>
</tbody>
</table>
</div>
<!-- Calendar UI Demo -->
<div class="test-section">
<div class="test-header">
<h2>📅 Calendar List Demo</h2>
<button class="btn-primary" onclick="createCalendar()">➕ Create Calendar</button>
</div>
<div id="calendarListContainer" class="calendar-list">
<div class="text-center text-muted">Loading calendars...</div>
</div>
</div>
<!-- Console Log -->
<div class="test-section">
<div class="test-header">
<h2>📝 Console Log</h2>
<button class="btn-secondary" onclick="toggleConsole()">▲ Toggle</button>
</div>
<div id="logConsole" class="log-console"></div>
</div>
</div>
<script>
// =========================
// State Management
// =========================
let mockCalendars = [
{ id: 1, title: 'Personal', description: 'My personal calendar', color: '#0d6efd', calendar_type: 'personal', user_can_edit: true, user_can_view: true, is_subscribed: true, visible: true },
{ id: 2, title: 'Work', description: 'Work events', color: '#198754', calendar_type: 'public', user_can_edit: true, user_can_view: true, is_subscribed: true, visible: true },
{ id: 3, title: 'Team Meetings', description: 'Team sync', color: '#ffc107', calendar_type: 'department', user_can_edit: false, user_can_view: true, is_subscribed: true, visible: false },
];
let mockEvents = [
{ id: 1, title: 'Morning Standup', start: '2026-02-11T09:00', end: '2026-02-11T09:30', color: '#0d6efd', calendar_id: 1 },
{ id: 2, title: 'Client Meeting', start: '2026-02-11T14:00', end: '2026-02-11T15:00', color: '#198754', calendar_id: 2 },
{ id: 3, title: 'Sprint Planning', start: '2026-02-12T10:00', end: '2026-02-12T12:00', color: '#ffc107', calendar_id: 3 },
{ id: 4, title: 'Code Review', start: '2026-02-13T15:00', end: '2026-02-13T16:00', color: '#0d6efd', calendar_id: 1 },
];
let testResults = [];
let visibleCalendarIds = [1, 2];
// =========================
// Logger
// =========================
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const logConsole = document.getElementById('logConsole');
const entry = document.createElement('div');
entry.className = 'log-entry log-' + type;
entry.textContent = '[' + timestamp + '] ' + message;
logConsole.appendChild(entry);
logConsole.scrollTop = logConsole.scrollHeight;
console.log('[Test]', message);
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function updateTestResult(testName, status, duration, details) {
testResults.push({ testName, status, duration, details });
renderTestResults();
}
function renderTestResults() {
const tbody = document.getElementById('testResultsTable');
if (testResults.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">No tests run yet</td></tr>';
return;
}
tbody.innerHTML = testResults.map(r => `
<tr>
<td>${r.testName}</td>
<td><span class="badge badge-${r.status === 'passed' ? 'success' : 'danger'}">${r.status.toUpperCase()}</span></td>
<td>${r.duration}ms</td>
<td class="text-muted">${r.details}</td>
</tr>
`).join('');
const passed = testResults.filter(r => r.status === 'passed').length;
const failed = testResults.filter(r => r.status === 'failed').length;
document.getElementById('testSummary').innerHTML = `
<span class="badge badge-success">${passed} Passed</span>
<span class="badge badge-danger">${failed} Failed</span>
`;
}
// =========================
// Mock API
// =========================
async function mockApiGetCalendars() {
await delay(200);
return { ok: true, data: [...mockCalendars] };
}
async function mockApiGetEvents(calendarId = null) {
await delay(150);
let events = [...mockEvents];
if (calendarId) {
events = events.filter(e => e.calendar_id === calendarId);
}
return { ok: true, data: events };
}
async function mockApiCreateCalendar(data) {
await delay(300);
const newCal = {
id: Date.now(),
...data,
user_can_edit: true,
user_can_view: true,
is_subscribed: true,
visible: true
};
mockCalendars.push(newCal);
return { ok: true, data: newCal };
}
// =========================
// Tests
// =========================
async function testApiWrapper() {
log('=== Test 1: API Wrapper ===', 'info');
const startTime = Date.now();
try {
const calendars = await mockApiGetCalendars();
if (!calendars.ok || !Array.isArray(calendars.data)) {
throw new Error('getCalendars failed');
}
log('✓ mockApiGetCalendars() → ' + calendars.data.length + ' calendars', 'success');
const events = await mockApiGetEvents(1);
if (!events.ok || !Array.isArray(events.data)) {
throw new Error('getEvents failed');
}
log('✓ mockApiGetEvents(1) → ' + events.data.length + ' events', 'success');
const allEvents = await mockApiGetEvents();
log('✓ mockApiGetEvents() → ' + allEvents.data.length + ' total events', 'success');
const duration = Date.now() - startTime;
updateTestResult('API Wrapper', 'passed', duration, 'All API methods working');
log('✅ Test 1: PASSED', 'success');
} catch (error) {
const duration = Date.now() - startTime;
updateTestResult('API Wrapper', 'failed', duration, error.message);
log('❌ Test 1: FAILED - ' + error.message, 'error');
}
}
async function testCalendarManager() {
log('=== Test 2: Calendar Manager ===', 'info');
const startTime = Date.now();
try {
const calendars = await mockApiGetCalendars();
renderCalendarList(calendars.data);
const container = document.getElementById('calendarListContainer');
const items = container.querySelectorAll('.calendar-item');
if (items.length !== calendars.data.length) {
throw new Error('Calendar count mismatch: ' + items.length + ' != ' + calendars.data.length);
}
log('✓ Rendered ' + items.length + ' calendar items', 'success');
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
if (checkboxes.length === 0) {
throw new Error('No checkboxes found');
}
log('✓ ' + checkboxes.length + ' checkboxes created', 'success');
const duration = Date.now() - startTime;
updateTestResult('Calendar Manager', 'passed', duration, 'List rendering OK');
log('✅ Test 2: PASSED', 'success');
} catch (error) {
const duration = Date.now() - startTime;
updateTestResult('Calendar Manager', 'failed', duration, error.message);
log('❌ Test 2: FAILED - ' + error.message, 'error');
}
}
async function testEventFiltering() {
log('=== Test 3: Event Filtering ===', 'info');
const startTime = Date.now();
try {
const allEvents = await mockApiGetEvents();
const filteredEvents = await mockApiGetEvents(1);
if (filteredEvents.data.length >= allEvents.data.length) {
throw new Error('Filtering not working correctly');
}
log('✓ All events: ' + allEvents.data.length, 'info');
log('✓ Calendar 1 events: ' + filteredEvents.data.length, 'info');
// Test visible calendar filtering
const visibleEvents = fetchEventsForVisibleCalendars(allEvents.data);
log('✓ Visible calendars: ' + visibleCalendarIds.join(', '), 'info');
log('✓ Visible events: ' + visibleEvents.length, 'info');
const duration = Date.now() - startTime;
updateTestResult('Event Filtering', 'passed', duration, 'Filtering works correctly');
log('✅ Test 3: PASSED', 'success');
} catch (error) {
const duration = Date.now() - startTime;
updateTestResult('Event Filtering', 'failed', duration, error.message);
log('❌ Test 3: FAILED - ' + error.message, 'error');
}
}
async function testStateManagement() {
log('=== Test 4: State Management ===', 'info');
const startTime = Date.now();
try {
const initialVisible = [...visibleCalendarIds];
log('✓ Initial visible: ' + initialVisible.join(', '), 'info');
// Toggle visibility
toggleCalendarVisibility(3, true);
if (!visibleCalendarIds.includes(3)) {
throw new Error('Calendar 3 not added to visible');
}
log('✓ Calendar 3 added to visible', 'success');
toggleCalendarVisibility(3, false);
if (visibleCalendarIds.includes(3)) {
throw new Error('Calendar 3 not removed from visible');
}
log('✓ Calendar 3 removed from visible', 'success');
const duration = Date.now() - startTime;
updateTestResult('State Management', 'passed', duration, 'State updates correctly');
log('✅ Test 4: PASSED', 'success');
} catch (error) {
const duration = Date.now() - startTime;
updateTestResult('State Management', 'failed', duration, error.message);
log('❌ Test 4: FAILED - ' + error.message, 'error');
}
}
async function testCallbacks() {
log('=== Test 5: Callbacks ===', 'info');
const startTime = Date.now();
try {
let callbackCalled = false;
function testCallback(calendarId, isVisible) {
callbackCalled = true;
log('✓ Callback triggered: cal=' + calendarId + ', visible=' + isVisible, 'info');
}
testCallback(1, true);
if (!callbackCalled) {
throw new Error('Callback not called');
}
log('✓ Callback mechanism works', 'success');
const duration = Date.now() - startTime;
updateTestResult('Callbacks', 'passed', duration, 'Callbacks working');
log('✅ Test 5: PASSED', 'success');
} catch (error) {
const duration = Date.now() - startTime;
updateTestResult('Callbacks', 'failed', duration, error.message);
log('❌ Test 5: FAILED - ' + error.message, 'error');
}
}
async function testDataStructures() {
log('=== Test 6: Data Structures ===', 'info');
const startTime = Date.now();
try {
// Test calendar structure
const cal = mockCalendars[0];
const requiredFields = ['id', 'title', 'color', 'calendar_type', 'user_can_edit', 'user_can_view'];
for (const field of requiredFields) {
if (!(field in cal)) {
throw new Error('Missing field: ' + field);
}
}
log('✓ Calendar structure valid', 'success');
// Test event structure
const event = mockEvents[0];
const eventFields = ['id', 'title', 'start', 'calendar_id'];
for (const field of eventFields) {
if (!(field in event)) {
throw new Error('Missing event field: ' + field);
}
}
log('✓ Event structure valid', 'success');
// Test deduplication
const duplicates = [...mockEvents, mockEvents[0]];
const unique = deduplicateEvents(duplicates);
if (unique.length !== mockEvents.length) {
throw new Error('Deduplication failed');
}
log('✓ Event deduplication works', 'success');
const duration = Date.now() - startTime;
updateTestResult('Data Structures', 'passed', duration, 'All structures valid');
log('✅ Test 6: PASSED', 'success');
} catch (error) {
const duration = Date.now() - startTime;
updateTestResult('Data Structures', 'failed', duration, error.message);
log('❌ Test 6: FAILED - ' + error.message, 'error');
}
}
// =========================
// Helper Functions
// =========================
function fetchEventsForVisibleCalendars(allEvents) {
return allEvents.filter(e => visibleCalendarIds.includes(e.calendar_id));
}
function toggleCalendarVisibility(calendarId, isVisible) {
if (isVisible) {
if (!visibleCalendarIds.includes(calendarId)) {
visibleCalendarIds.push(calendarId);
}
} else {
visibleCalendarIds = visibleCalendarIds.filter(id => id !== calendarId);
}
log('Visible calendars: ' + visibleCalendarIds.join(', '), 'info');
}
function deduplicateEvents(events) {
const seen = new Set();
return events.filter(e => {
if (seen.has(e.id)) return false;
seen.add(e.id);
return true;
});
}
function renderCalendarList(calendars) {
const container = document.getElementById('calendarListContainer');
container.innerHTML = calendars.map(cal => `
<div class="calendar-item">
<input
type="checkbox"
${cal.visible ? 'checked' : ''}
onchange="handleCalendarToggle(${cal.id}, this.checked)"
>
<div class="calendar-color" style="background-color: ${cal.color}"></div>
<div class="calendar-info">
<div class="calendar-title">${cal.title}</div>
<div class="calendar-meta">${cal.calendar_type} ${cal.user_can_edit ? '✏️' : '👁️'}</div>
</div>
</div>
`).join('');
}
function handleCalendarToggle(calendarId, isChecked) {
log('Calendar ' + calendarId + ' toggled: ' + isChecked, 'info');
toggleCalendarVisibility(calendarId, isChecked);
// Update calendar state
const calendar = mockCalendars.find(c => c.id === calendarId);
if (calendar) {
calendar.visible = isChecked;
}
}
function createCalendar() {
const title = prompt('Calendar title:');
if (!title) return;
const color = '#' + Math.floor(Math.random()*16777215).toString(16);
mockApiCreateCalendar({
title: title,
description: '',
color: color,
calendar_type: 'personal'
}).then(result => {
log('Calendar created: ' + result.data.title, 'success');
renderCalendarList(mockCalendars);
});
}
function toggleConsole() {
const console = document.getElementById('logConsole');
console.style.display = console.style.display === 'none' ? 'block' : 'none';
}
// =========================
// Event Listeners
// =========================
document.getElementById('runAllTests').addEventListener('click', async () => {
testResults = [];
log('🚀 Running all tests...', 'info');
await testApiWrapper();
await delay(300);
await testCalendarManager();
await delay(300);
await testEventFiltering();
await delay(300);
await testStateManagement();
await delay(300);
await testCallbacks();
await delay(300);
await testDataStructures();
log('🏁 All tests completed!', 'success');
});
document.getElementById('clearLogs').addEventListener('click', () => {
document.getElementById('logConsole').innerHTML = '';
log('Logs cleared', 'info');
});
// =========================
// Initialize
// =========================
(async function init() {
log('✅ Test suite initialized', 'success');
log('Click "Run All Tests" to start', 'info');
log('📡 Offline mode - using mock data', 'warn');
try {
await testCalendarManager();
} catch (error) {
log('Init error: ' + error.message, 'error');
}
})();
</script>
</body>
</html>