/** * Test Initialization Script * Standalone initialization for test pages without requiring full calendar DOM */ // IndexedDB Service (simplified standalone version) class TestIndexedDBService { constructor() { this.DB_NAME = 'CalendarDB_Test'; // Separate test database this.DB_VERSION = 1; this.EVENTS_STORE = 'events'; this.QUEUE_STORE = 'operationQueue'; this.SYNC_STATE_STORE = 'syncState'; this.db = null; } async initialize() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.DB_NAME, this.DB_VERSION); request.onerror = () => reject(request.error); request.onsuccess = () => { this.db = request.result; resolve(); }; request.onupgradeneeded = (event) => { const db = event.target.result; // Create events store if (!db.objectStoreNames.contains(this.EVENTS_STORE)) { const eventStore = db.createObjectStore(this.EVENTS_STORE, { keyPath: 'id' }); eventStore.createIndex('start', 'start', { unique: false }); eventStore.createIndex('end', 'end', { unique: false }); eventStore.createIndex('syncStatus', 'syncStatus', { unique: false }); } // Create operation queue store if (!db.objectStoreNames.contains(this.QUEUE_STORE)) { const queueStore = db.createObjectStore(this.QUEUE_STORE, { keyPath: 'id', autoIncrement: true }); queueStore.createIndex('timestamp', 'timestamp', { unique: false }); queueStore.createIndex('eventId', 'eventId', { unique: false }); } // Create sync state store if (!db.objectStoreNames.contains(this.SYNC_STATE_STORE)) { db.createObjectStore(this.SYNC_STATE_STORE, { keyPath: 'key' }); } }; }); } async getAllEvents() { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.EVENTS_STORE], 'readonly'); const store = transaction.objectStore(this.EVENTS_STORE); const request = store.getAll(); request.onsuccess = () => { const events = request.result.map(event => ({ ...event, start: new Date(event.start), end: new Date(event.end) })); resolve(events); }; request.onerror = () => reject(request.error); }); } async getEvent(id) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.EVENTS_STORE], 'readonly'); const store = transaction.objectStore(this.EVENTS_STORE); const request = store.get(id); request.onsuccess = () => { const event = request.result; if (event) { event.start = new Date(event.start); event.end = new Date(event.end); } resolve(event || null); }; request.onerror = () => reject(request.error); }); } async saveEvent(event) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.EVENTS_STORE], 'readwrite'); const store = transaction.objectStore(this.EVENTS_STORE); const eventToSave = { ...event, start: event.start instanceof Date ? event.start.toISOString() : event.start, end: event.end instanceof Date ? event.end.toISOString() : event.end }; const request = store.put(eventToSave); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async deleteEvent(id) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.EVENTS_STORE], 'readwrite'); const store = transaction.objectStore(this.EVENTS_STORE); const request = store.delete(id); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async addToQueue(operation) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.QUEUE_STORE], 'readwrite'); const store = transaction.objectStore(this.QUEUE_STORE); const request = store.add(operation); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async getQueue() { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.QUEUE_STORE], 'readonly'); const store = transaction.objectStore(this.QUEUE_STORE); const request = store.getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async removeFromQueue(id) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.QUEUE_STORE], 'readwrite'); const store = transaction.objectStore(this.QUEUE_STORE); const request = store.delete(id); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async clearQueue() { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.QUEUE_STORE], 'readwrite'); const store = transaction.objectStore(this.QUEUE_STORE); const request = store.clear(); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } close() { if (this.db) { this.db.close(); } } } // Operation Queue (simplified standalone version) class TestOperationQueue { constructor(indexedDB) { this.indexedDB = indexedDB; } async enqueue(operation) { await this.indexedDB.addToQueue(operation); } async getAll() { return await this.indexedDB.getQueue(); } async remove(id) { await this.indexedDB.removeFromQueue(id); } async clear() { await this.indexedDB.clearQueue(); } async incrementRetryCount(operationId) { const queue = await this.getAll(); const operation = queue.find(op => op.id === operationId); if (operation) { operation.retryCount = (operation.retryCount || 0) + 1; await this.indexedDB.removeFromQueue(operationId); await this.indexedDB.addToQueue(operation); } } } // Simple EventManager for tests class TestEventManager { constructor(indexedDB, queue) { this.indexedDB = indexedDB; this.queue = queue; } async getAllEvents() { return await this.indexedDB.getAllEvents(); } async getEvent(id) { return await this.indexedDB.getEvent(id); } async addEvent(eventData) { const id = eventData.id || `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const syncStatus = eventData.syncStatus || 'pending'; const newEvent = { ...eventData, id, syncStatus }; await this.indexedDB.saveEvent(newEvent); if (syncStatus === 'pending') { await this.queue.enqueue({ type: 'create', eventId: id, data: newEvent, timestamp: Date.now(), retryCount: 0 }); } return newEvent; } async updateEvent(id, updates) { const event = await this.indexedDB.getEvent(id); if (!event) return null; const updatedEvent = { ...event, ...updates, syncStatus: 'pending' }; await this.indexedDB.saveEvent(updatedEvent); await this.queue.enqueue({ type: 'update', eventId: id, data: updates, timestamp: Date.now(), retryCount: 0 }); return updatedEvent; } async deleteEvent(id) { await this.indexedDB.deleteEvent(id); await this.queue.enqueue({ type: 'delete', eventId: id, data: null, timestamp: Date.now(), retryCount: 0 }); } } // Minimal SyncManager for tests with mock API simulation class TestSyncManager { constructor(queue, indexedDB) { this.queue = queue; this.indexedDB = indexedDB; this.isOnline = navigator.onLine; this.maxRetries = 5; this.setupNetworkListeners(); } setupNetworkListeners() { window.addEventListener('online', () => { this.isOnline = true; console.log('[TestSyncManager] Network online'); }); window.addEventListener('offline', () => { this.isOnline = false; console.log('[TestSyncManager] Network offline'); }); } async triggerManualSync() { console.log('[TestSyncManager] Manual sync triggered'); // Check if online before syncing if (!this.isOnline) { console.warn('[TestSyncManager] ⚠️ Cannot sync - offline mode'); throw new Error('Cannot sync while offline'); } const queueItems = await this.queue.getAll(); console.log(`[TestSyncManager] Queue has ${queueItems.length} items`); if (queueItems.length === 0) { console.log('[TestSyncManager] Queue is empty - nothing to sync'); return []; } // Process each operation for (const operation of queueItems) { await this.processOperation(operation); } return queueItems; } async processOperation(operation) { console.log(`[TestSyncManager] Processing operation ${operation.id} (retry: ${operation.retryCount})`); // Check if max retries exceeded if (operation.retryCount >= this.maxRetries) { console.error(`[TestSyncManager] Max retries (${this.maxRetries}) exceeded for operation ${operation.id}`); await this.queue.remove(operation.id); await this.markEventAsError(operation.eventId); return; } // Simulate API call with delay await this.simulateApiCall(); // Simulate success (80%) or failure (20%) const success = Math.random() > 0.2; if (success) { console.log(`[TestSyncManager] ✓ Operation ${operation.id} synced successfully`); await this.queue.remove(operation.id); await this.markEventAsSynced(operation.eventId); } else { console.warn(`[TestSyncManager] ✗ Operation ${operation.id} failed - will retry`); await this.queue.incrementRetryCount(operation.id); } } async simulateApiCall() { // Simulate network delay (100-500ms) const delay = Math.floor(Math.random() * 400) + 100; return new Promise(resolve => setTimeout(resolve, delay)); } async markEventAsSynced(eventId) { try { const event = await this.indexedDB.getEvent(eventId); if (event) { event.syncStatus = 'synced'; await this.indexedDB.saveEvent(event); console.log(`[TestSyncManager] Event ${eventId} marked as synced`); } } catch (error) { console.error(`[TestSyncManager] Failed to mark event ${eventId} as synced:`, error); } } async markEventAsError(eventId) { try { const event = await this.indexedDB.getEvent(eventId); if (event) { event.syncStatus = 'error'; await this.indexedDB.saveEvent(event); console.log(`[TestSyncManager] Event ${eventId} marked as error`); } } catch (error) { console.error(`[TestSyncManager] Failed to mark event ${eventId} as error:`, error); } } } // Initialize test environment async function initializeTestEnvironment() { console.log('[Test Init] Initializing test environment...'); const indexedDB = new TestIndexedDBService(); await indexedDB.initialize(); console.log('[Test Init] IndexedDB initialized'); const queue = new TestOperationQueue(indexedDB); console.log('[Test Init] Operation queue created'); const eventManager = new TestEventManager(indexedDB, queue); console.log('[Test Init] Event manager created'); const syncManager = new TestSyncManager(queue, indexedDB); console.log('[Test Init] Sync manager created'); // Seed with test data if empty const existingEvents = await indexedDB.getAllEvents(); if (existingEvents.length === 0) { console.log('[Test Init] Seeding with test data...'); try { const response = await fetch('test-events.json'); const testEvents = await response.json(); for (const event of testEvents) { const savedEvent = { ...event, start: new Date(event.start), end: new Date(event.end) }; await indexedDB.saveEvent(savedEvent); // If event is pending, also add to queue if (event.syncStatus === 'pending') { await queue.enqueue({ type: 'create', eventId: event.id, data: savedEvent, timestamp: Date.now(), retryCount: 0 }); console.log(`[Test Init] Added pending event ${event.id} to queue`); } } console.log(`[Test Init] Seeded ${testEvents.length} test events`); } catch (error) { console.warn('[Test Init] Could not load test-events.json:', error); } } else { console.log(`[Test Init] IndexedDB already has ${existingEvents.length} events`); } // Expose to window window.calendarDebug = { indexedDB, queue, eventManager, syncManager }; console.log('[Test Init] Test environment ready'); return { indexedDB, queue, eventManager, syncManager }; } // Auto-initialize if script is loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { initializeTestEnvironment().catch(error => { console.error('[Test Init] Failed to initialize:', error); }); }); } else { initializeTestEnvironment().catch(error => { console.error('[Test Init] Failed to initialize:', error); }); }