Calendar/test/integrationtesting/test-init.js

453 lines
13 KiB
JavaScript
Raw Normal View History

/**
* 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);
});
}