UNPKG

mocha-multiple-sessions-detailed-runner

Version:
1,116 lines (1,107 loc) 40.7 kB
class SessionMetaRepository { constructor() { this.sessions = new Map(); } save(sessionMeta) { this.sessions.set(sessionMeta.label, sessionMeta); } findByLabel(label) { return this.sessions.get(label) || null; } findAll() { return Array.from(this.sessions.values()); } delete(label) { return this.sessions.delete(label); } clear() { this.sessions.clear(); } exists(label) { return this.sessions.has(label); } updateState(label, state) { const session = this.sessions.get(label); if (session) { Object.assign(session, state); } } getStates() { const states = {}; this.sessions.forEach((meta, label) => { states[label] = { label: meta.label, status: meta.status, ...(meta.timestamp && { timestamp: meta.timestamp }), ...(meta.duration && { duration: meta.duration }) }; }); return states; } markAsRunning(label) { this.updateState(label, { status: 'running', timestamp: new Date() }); } markAsCompleted(label, result) { this.updateState(label, { status: 'passed', timestamp: new Date(), duration: result.duration }); } markAsFailed(label, error) { const session = this.sessions.get(label); if (session) { session.status = 'failed'; session.error = error; session.timestamp = new Date(); } } getByStatus(status) { return this.findAll().filter(session => session.status === status); } getExecutionSummary() { const all = this.findAll(); const lastExecutionTime = all.reduce((latest, session) => { if (session.timestamp && (!latest || session.timestamp > latest)) { return session.timestamp; } return latest; }, undefined); return { totalSessions: all.length, readySessions: all.filter(s => s.status === 'ready').length, runningSessions: all.filter(s => s.status === 'running').length, completedSessions: all.filter(s => s.status === 'passed').length, failedSessions: all.filter(s => s.status === 'failed').length, ...(lastExecutionTime && { lastExecutionTime }) }; } } class ReportRepository { constructor() { this.PREFIX = 'mochaSession__'; } generateResultKey(sessionLabel) { return `${this.PREFIX}${sessionLabel.replace(/\s+/g, '_').replace(/-/g, '_')}`; } getReportData(resultKey) { return this.getWindowVariable(resultKey); } hasReport(resultKey) { return this.getWindowVariable(resultKey) !== undefined; } clearReport(resultKey) { this.deleteWindowVariable(resultKey); } getAllReports() { const reports = {}; // Get all window variables that match our prefix for (const key in window) { if (key.startsWith(this.PREFIX)) { const value = this.getWindowVariable(key); if (value) { reports[key] = value; } } } return reports; } getReportMetadata(resultKey) { const data = this.getReportData(resultKey); if (!data) return null; const sessionLabel = resultKey.replace(this.PREFIX, '').replace(/_/g, '-'); return { size: JSON.stringify(data).length, generatedAt: data.timestamp ? new Date(data.timestamp) : new Date(), sessionLabel, stats: data.stats || {}, isValid: this.validateReport(data) }; } async waitForReport(resultKey, timeoutMs = 30000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkInterval = setInterval(() => { const report = this.getReportData(resultKey); if (report) { clearInterval(checkInterval); resolve(report); return; } if (Date.now() - startTime > timeoutMs) { clearInterval(checkInterval); reject(new Error(`Timeout waiting for report: ${resultKey}`)); } }, 100); }); } watchReport(resultKey, callback) { let isWatching = true; const checkInterval = setInterval(() => { if (!isWatching) { clearInterval(checkInterval); return; } const report = this.getReportData(resultKey); if (report) { callback(report); clearInterval(checkInterval); isWatching = false; } }, 100); // Return cleanup function return () => { isWatching = false; clearInterval(checkInterval); }; } getWindowVariable(key) { return window[key]; } setWindowVariable(key, value) { window[key] = value; } deleteWindowVariable(key) { delete window[key]; } getReportSize(resultKey) { const data = this.getReportData(resultKey); return data ? JSON.stringify(data).length : 0; } validateReport(reportData) { if (!reportData || typeof reportData !== 'object') return false; // Basic validation - check for required properties return !!(reportData.stats && typeof reportData.stats === 'object' && typeof reportData.stats.tests === 'number'); } } class UnifiedEventBus { constructor() { this.runnerListeners = new Map(); this.sessionListeners = new Map(); this.anyEventListeners = new Set(); this.eventHistory = []; this.maxHistorySize = 1000; } emitRunnerEvent(event) { // Add to history this.addToHistory(event); // Emit to specific listeners const listeners = this.runnerListeners.get(event.type); if (listeners) { listeners.forEach(callback => { try { callback(event); } catch (error) { console.error('Error in runner event listener:', error); } }); } // Emit to universal listeners this.anyEventListeners.forEach(callback => { try { callback(event); } catch (error) { console.error('Error in universal event listener:', error); } }); } onRunnerEvent(eventType, callback) { if (!this.runnerListeners.has(eventType)) { this.runnerListeners.set(eventType, new Set()); } this.runnerListeners.get(eventType).add(callback); // Return unsubscribe function return () => { const listeners = this.runnerListeners.get(eventType); if (listeners) { listeners.delete(callback); if (listeners.size === 0) { this.runnerListeners.delete(eventType); } } }; } emitSessionEvent(event) { // Add to history this.addToHistory(event); // Emit to specific listeners const listeners = this.sessionListeners.get(event.type); if (listeners) { listeners.forEach(callback => { try { callback(event); } catch (error) { console.error('Error in session event listener:', error); } }); } // Emit to universal listeners this.anyEventListeners.forEach(callback => { try { callback(event); } catch (error) { console.error('Error in universal event listener:', error); } }); } onSessionEvent(eventType, callback) { if (!this.sessionListeners.has(eventType)) { this.sessionListeners.set(eventType, new Set()); } this.sessionListeners.get(eventType).add(callback); // Return unsubscribe function return () => { const listeners = this.sessionListeners.get(eventType); if (listeners) { listeners.delete(callback); if (listeners.size === 0) { this.sessionListeners.delete(eventType); } } }; } onAnyEvent(callback) { this.anyEventListeners.add(callback); // Return unsubscribe function return () => { this.anyEventListeners.delete(callback); }; } getEventHistory(filter) { let filtered = [...this.eventHistory]; if (filter) { if (filter.eventTypes) { filtered = filtered.filter(event => filter.eventTypes.includes(event.type)); } if (filter.sessionLabel) { filtered = filtered.filter(event => 'sessionLabel' in event && event.sessionLabel === filter.sessionLabel); } if (filter.timeRange) { filtered = filtered.filter(event => { const eventTime = new Date(event.timestamp); return eventTime >= filter.timeRange.start && eventTime <= filter.timeRange.end; }); } if (filter.maxEvents) { filtered = filtered.slice(-filter.maxEvents); } } return filtered; } clearEventHistory() { this.eventHistory = []; } hasListeners(eventType) { if (!eventType) { return this.runnerListeners.size > 0 || this.sessionListeners.size > 0 || this.anyEventListeners.size > 0; } return (this.runnerListeners.get(eventType)?.size || 0) > 0 || (this.sessionListeners.get(eventType)?.size || 0) > 0; } getListenerCount(eventType) { if (!eventType) { let total = this.anyEventListeners.size; this.runnerListeners.forEach(listeners => total += listeners.size); this.sessionListeners.forEach(listeners => total += listeners.size); return total; } const runnerCount = this.runnerListeners.get(eventType)?.size || 0; const sessionCount = this.sessionListeners.get(eventType)?.size || 0; return runnerCount + sessionCount; } destroy() { this.runnerListeners.clear(); this.sessionListeners.clear(); this.anyEventListeners.clear(); this.eventHistory = []; } addToHistory(event) { this.eventHistory.push(event); // Trim history if it gets too large if (this.eventHistory.length > this.maxHistorySize) { this.eventHistory = this.eventHistory.slice(-this.maxHistorySize); } } } class MochaIntegration { constructor() { this.isConfiguredFlag = false; this.currentConfig = null; } setupTestEnvironment(config) { this.validateEnvironment(); const defaultConfig = { createMochaInstance: (sessionLabel) => { const resultKey = config.windowVariablePrefix + sessionLabel.replace(/\s+/g, '_').replace(/-/g, '_'); if (!window.MochaDetailedReporter) { throw new Error('MochaDetailedReporter not available'); } return new window.Mocha({ ui: 'bdd', reporter: window.MochaDetailedReporter.DetailedJsonReporter, reporterOptions: { outputToWindow: 'true', windowVariableName: resultKey, captureConsoleLog: config.captureConsoleLog ? 'true' : 'false', sourceCode: config.sourceCode ? 'true' : 'false', attachStats: config.attachStats ? 'true' : 'false' } }); }, injectGlobals: (mochaInstance) => { mochaInstance.suite.emit('pre-require', window, null, mochaInstance); if (window.chai) { window.expect = window.chai.expect; } } }; // Use provided config or defaults const finalConfig = { createMochaInstance: config.createMochaInstance || defaultConfig.createMochaInstance, injectGlobals: config.injectGlobals || defaultConfig.injectGlobals }; // Setup with mocha-multiple-sessions if (window.MochaMultipleSessions?.testSessionSetup) { window.MochaMultipleSessions.testSessionSetup(finalConfig); this.currentConfig = finalConfig; this.isConfiguredFlag = true; } else { throw new Error('MochaMultipleSessions library not available'); } } isConfigured() { return this.isConfiguredFlag; } getConfiguration() { return this.currentConfig; } integrateWithExistingLibrary() { if (!window.MochaMultipleSessions) { throw new Error('MochaMultipleSessions library not found'); } // Integration is handled in setupTestEnvironment } setupDefaultReporter(sessionLabel) { const resultKey = `mochaSession__${sessionLabel.replace(/\s+/g, '_').replace(/-/g, '_')}`; if (!window.MochaDetailedReporter) { throw new Error('MochaDetailedReporter not available'); } return new window.Mocha({ ui: 'bdd', reporter: window.MochaDetailedReporter.DetailedJsonReporter, reporterOptions: { outputToWindow: 'true', windowVariableName: resultKey, captureConsoleLog: 'true', sourceCode: 'true', attachStats: 'true' } }); } bridgeSessionEvents() { // This would be implemented if we need to bridge events // For now, the existing library handles events } validateEnvironment() { const errors = []; const warnings = []; const missing = []; const deps = this.checkDependencies(); if (!deps.mocha) { errors.push('Mocha library not found'); missing.push('mocha'); } if (!deps.chai) { warnings.push('Chai library not found - tests may not work properly'); missing.push('chai'); } if (!deps.detailedReporter) { errors.push('MochaDetailedReporter not found'); missing.push('mocha-detailed-json-reporter'); } if (!deps.multipleSessionsLibrary) { errors.push('MochaMultipleSessions library not found'); missing.push('mocha-multiple-sessions-ts'); } const validation = { isValid: errors.length === 0, errors, warnings, missingDependencies: missing }; if (!validation.isValid) { throw new Error(`Environment validation failed: ${errors.join(', ')}`); } return validation; } checkDependencies() { return { mocha: typeof window.Mocha !== 'undefined' && typeof window.mocha !== 'undefined', chai: typeof window.chai !== 'undefined', detailedReporter: typeof window.MochaDetailedReporter !== 'undefined', multipleSessionsLibrary: typeof window.MochaMultipleSessions !== 'undefined', reporterUI: typeof window.MochaDetailedReporterUI !== 'undefined' }; } } class ReportService { constructor(reportRepository, sessionRepository, eventBus) { this.reportRepository = reportRepository; this.sessionRepository = sessionRepository; this.eventBus = eventBus; } getSessionReport(label) { const sessionMeta = this.sessionRepository.findByLabel(label); if (!sessionMeta) return null; const reportData = this.reportRepository.getReportData(sessionMeta.resultKey); if (!reportData) return null; const detailedReport = { data: reportData, stats: reportData.stats || { tests: 0, passes: 0, failures: 0, pending: 0 }, timestamp: reportData.timestamp ? new Date(reportData.timestamp) : new Date(), sessionLabel: label, size: JSON.stringify(reportData).length }; return detailedReport; } async waitForSessionReport(label, timeoutMs = 30000) { const sessionMeta = this.sessionRepository.findByLabel(label); if (!sessionMeta) { throw new Error(`Session '${label}' not found`); } const reportData = await this.reportRepository.waitForReport(sessionMeta.resultKey, timeoutMs); return { data: reportData, stats: reportData.stats || { tests: 0, passes: 0, failures: 0, pending: 0 }, timestamp: reportData.timestamp ? new Date(reportData.timestamp) : new Date(), sessionLabel: label, size: JSON.stringify(reportData).length }; } hasSessionReport(label) { const sessionMeta = this.sessionRepository.findByLabel(label); if (!sessionMeta) return false; return this.reportRepository.hasReport(sessionMeta.resultKey); } generateCombinedReport() { const allSessions = this.sessionRepository.findAll(); const sessions = {}; let totalTests = 0; let totalPasses = 0; let totalFailures = 0; let totalPending = 0; let successfulSessions = 0; let failedSessions = 0; for (const sessionMeta of allSessions) { const report = this.getSessionReport(sessionMeta.label); if (report) { sessions[sessionMeta.label] = report; totalTests += report.stats.tests; totalPasses += report.stats.passes; totalFailures += report.stats.failures; totalPending += report.stats.pending; if (report.stats.failures === 0) { successfulSessions++; } else { failedSessions++; } } } const combinedReport = { generatedAt: new Date().toISOString(), summary: { totalTests, totalPasses, totalFailures, totalPending, totalSessions: allSessions.length, successfulSessions, failedSessions }, sessions }; // Emit report generated event this.eventBus.emitRunnerEvent({ type: 'runner:combined-report-generated', timestamp: new Date(), data: { reportType: 'combined', totalSessions: allSessions.length, reportSize: JSON.stringify(combinedReport).length, generatedAt: new Date(), summary: combinedReport.summary } }); return combinedReport; } async getCombinedReportAsync() { // For now, just return the sync version // In the future, this could wait for all reports to be ready return this.generateCombinedReport(); } watchSessionReport(label, callback) { const sessionMeta = this.sessionRepository.findByLabel(label); if (!sessionMeta) { throw new Error(`Session '${label}' not found`); } return this.reportRepository.watchReport(sessionMeta.resultKey, (reportData) => { const detailedReport = { data: reportData, stats: reportData.stats || { tests: 0, passes: 0, failures: 0, pending: 0 }, timestamp: reportData.timestamp ? new Date(reportData.timestamp) : new Date(), sessionLabel: label, size: JSON.stringify(reportData).length }; callback(detailedReport); }); } watchAllReports(callback) { const unsubscribeFunctions = []; const allSessions = this.sessionRepository.findAll(); for (const sessionMeta of allSessions) { const unsubscribe = this.watchSessionReport(sessionMeta.label, (report) => { callback(sessionMeta.label, report); }); unsubscribeFunctions.push(unsubscribe); } // Return function to unsubscribe from all return () => { unsubscribeFunctions.forEach(fn => fn()); }; } getReportSummary() { const allSessions = this.sessionRepository.findAll(); const reportSizes = {}; let totalReports = 0; let totalTests = 0; let totalPasses = 0; let totalFailures = 0; let oldestReport; let newestReport; for (const sessionMeta of allSessions) { const report = this.getSessionReport(sessionMeta.label); if (report) { totalReports++; totalTests += report.stats.tests; totalPasses += report.stats.passes; totalFailures += report.stats.failures; reportSizes[sessionMeta.label] = report.size || 0; if (!oldestReport || report.timestamp < oldestReport) { oldestReport = report.timestamp; } if (!newestReport || report.timestamp > newestReport) { newestReport = report.timestamp; } } } return { totalReports, totalTests, totalPasses, totalFailures, reportSizes, ...(oldestReport && { oldestReport }), ...(newestReport && { newestReport }) }; } async exportReports(format) { const combinedReport = this.generateCombinedReport(); if (format === 'json') { return JSON.stringify(combinedReport, null, 2); } else { // Basic HTML export - could be enhanced return ` <html> <head><title>Test Report</title></head> <body> <h1>Combined Test Report</h1> <p>Generated: ${combinedReport.generatedAt}</p> <h2>Summary</h2> <pre>${JSON.stringify(combinedReport.summary, null, 2)}</pre> <h2>Sessions</h2> <pre>${JSON.stringify(combinedReport.sessions, null, 2)}</pre> </body> </html> `; } } clearAllReports() { const allSessions = this.sessionRepository.findAll(); for (const sessionMeta of allSessions) { if (this.reportRepository.hasReport(sessionMeta.resultKey)) { this.reportRepository.clearReport(sessionMeta.resultKey); } } } validateSessionReport(sessionLabel) { const report = this.getSessionReport(sessionLabel); return report !== null && this.reportRepository.validateReport(report.data); } getReportValidationErrors(sessionLabel) { const errors = []; const report = this.getSessionReport(sessionLabel); if (!report) { errors.push('Report not found'); return errors; } if (!report.data) { errors.push('Report data is missing'); } if (!report.stats) { errors.push('Report stats are missing'); } else { if (typeof report.stats.tests !== 'number') { errors.push('Invalid test count'); } if (typeof report.stats.passes !== 'number') { errors.push('Invalid pass count'); } if (typeof report.stats.failures !== 'number') { errors.push('Invalid failure count'); } } return errors; } } class InitializeRunnerUseCase { constructor(mochaIntegration, eventBus) { this.mochaIntegration = mochaIntegration; this.eventBus = eventBus; } execute(config) { const finalConfig = { windowVariablePrefix: 'mochaSession__', autoInit: true, captureConsoleLog: true, sourceCode: true, attachStats: true, ...config }; try { this.mochaIntegration.setupTestEnvironment(finalConfig); this.eventBus.emitRunnerEvent({ type: 'runner:initialized', timestamp: new Date(), data: { configUsed: finalConfig, sessionCount: 0, timestamp: new Date() } }); } catch (error) { this.eventBus.emitRunnerEvent({ type: 'runner:config-updated', timestamp: new Date(), data: { oldConfig: {}, newConfig: finalConfig, changes: ['initialization_failed'] } }); throw error; } } } class DefineSessionUseCase { constructor(sessionRepository, reportRepository, eventBus) { this.sessionRepository = sessionRepository; this.reportRepository = reportRepository; this.eventBus = eventBus; } execute(label, setupFn) { const resultKey = this.reportRepository.generateResultKey(label); const sessionMeta = { label, resultKey, setupFn, status: 'ready', timestamp: new Date() }; this.sessionRepository.save(sessionMeta); this.eventBus.emitRunnerEvent({ type: 'runner:session-defined', timestamp: new Date(), data: { sessionLabel: label, sessionMeta, totalSessions: this.sessionRepository.findAll().length } }); } } class RunSingleSessionUseCase { constructor(sessionRepository, reportRepository, eventBus) { this.sessionRepository = sessionRepository; this.reportRepository = reportRepository; this.eventBus = eventBus; } async execute(label) { const sessionMeta = this.sessionRepository.findByLabel(label); if (!sessionMeta) { throw new Error(`Session '${label}' not found`); } try { // Mark as running this.sessionRepository.markAsRunning(label); // Use the existing mocha-multiple-sessions library if (!window.MochaMultipleSessions) { throw new Error('MochaMultipleSessions library not available'); } // Create the session first with better error handling try { await window.MochaMultipleSessions.testSession(label, sessionMeta.setupFn); console.log(`Session '${label}' created successfully`); } catch (sessionCreationError) { console.error(`Failed to create session '${label}':`, sessionCreationError); throw new Error(`Session creation failed for '${label}': ${sessionCreationError}`); } // Run the session with better error handling let result; try { result = await window.MochaMultipleSessions.runSession(label); console.log(`Session '${label}' execution completed:`, result); } catch (sessionRunError) { console.error(`Failed to run session '${label}':`, sessionRunError); throw new Error(`Session execution failed for '${label}': ${sessionRunError}`); } // Wait for detailed report to be available try { await this.reportRepository.waitForReport(sessionMeta.resultKey, 5000); console.log(`Detailed report available for '${label}'`); } catch (waitError) { console.warn(`Timeout waiting for detailed report for ${label}:`, waitError); // Don't throw here, continue with basic result } // Mark as completed this.sessionRepository.markAsCompleted(label, result); const sessionResult = { label, status: result.success ? 'passed' : 'failed', success: result.success, timestamp: new Date(), duration: result.duration, stats: result.stats }; console.log(`Session '${label}' result:`, sessionResult); return sessionResult; } catch (error) { console.error(`Error in session '${label}':`, error); this.sessionRepository.markAsFailed(label, error); const sessionResult = { label, status: 'failed', success: false, timestamp: new Date(), error: error }; return sessionResult; } } } class RunAllSessionsUseCase { constructor(sessionRepository, runSingleSessionUseCase, eventBus) { this.sessionRepository = sessionRepository; this.runSingleSessionUseCase = runSingleSessionUseCase; this.eventBus = eventBus; } async execute(options) { const allSessions = this.sessionRepository.findAll(); if (allSessions.length === 0) { throw new Error('No sessions defined'); } const sessionLabels = allSessions.map(s => s.label); const startTime = Date.now(); // Emit batch start event this.eventBus.emitRunnerEvent({ type: 'runner:batch-started', timestamp: new Date(), data: { sessionLabels, totalSessions: sessionLabels.length, executionMode: options?.mode || 'sequential', options } }); const results = {}; let completed = 0; try { if (options?.mode === 'parallel') { // Run in parallel const promises = sessionLabels.map(async (label) => { const result = await this.runSingleSessionUseCase.execute(label); results[label] = result; completed++; this.eventBus.emitRunnerEvent({ type: 'runner:batch-progress', timestamp: new Date(), data: { completed, total: sessionLabels.length, currentSession: label, completedSessions: Object.keys(results), failedSessions: Object.keys(results).filter((l) => !results[l]?.success), progress: Math.round((completed / sessionLabels.length) * 100) } }); return result; }); await Promise.all(promises); } else { // Run sequentially for (const label of sessionLabels) { if (options?.stopOnFirstFailure && completed > 0) { const hasFailures = Object.values(results).some((r) => !r.success); if (hasFailures) break; } const result = await this.runSingleSessionUseCase.execute(label); results[label] = result; completed++; this.eventBus.emitRunnerEvent({ type: 'runner:batch-progress', timestamp: new Date(), data: { completed, total: sessionLabels.length, currentSession: label, completedSessions: Object.keys(results), failedSessions: Object.keys(results).filter((l) => !results[l]?.success), progress: Math.round((completed / sessionLabels.length) * 100) } }); } } const duration = Date.now() - startTime; const successfulSessions = Object.values(results).filter(r => r.success).length; const failedSessions = Object.values(results).filter(r => !r.success).length; // Emit completion event this.eventBus.emitRunnerEvent({ type: 'runner:batch-completed', timestamp: new Date(), data: { totalSessions: sessionLabels.length, successfulSessions, failedSessions, duration, sessionResults: results, overallSuccess: failedSessions === 0 } }); return results; } catch (error) { // Emit failure event this.eventBus.emitRunnerEvent({ type: 'runner:batch-failed', timestamp: new Date(), data: { error: error, completedSessions: Object.keys(results), partialResults: results } }); throw error; } } } class ClearAllSessionsUseCase { constructor(sessionRepository, reportRepository, eventBus) { this.sessionRepository = sessionRepository; this.reportRepository = reportRepository; this.eventBus = eventBus; } async execute() { const allSessions = this.sessionRepository.findAll(); const clearedSessions = allSessions.map(s => s.label); const clearedReports = []; // Clear all reports for (const session of allSessions) { if (this.reportRepository.hasReport(session.resultKey)) { this.reportRepository.clearReport(session.resultKey); clearedReports.push(session.resultKey); } } // Clear session repository this.sessionRepository.clear(); // Emit cleared event this.eventBus.emitRunnerEvent({ type: 'runner:cleared', timestamp: new Date(), data: { clearedSessions, clearedReports } }); } } class DestroyRunnerUseCase { constructor(clearAllSessionsUseCase, eventBus) { this.clearAllSessionsUseCase = clearAllSessionsUseCase; this.eventBus = eventBus; } async execute() { // Clear all sessions first await this.clearAllSessionsUseCase.execute(); // Emit destroy event this.eventBus.emitRunnerEvent({ type: 'runner:destroyed', timestamp: new Date() }); // Destroy event bus this.eventBus.destroy(); } } // Implementation imports class MultiSessionDetailedRunner { constructor() { // State this.initialized = false; this.destroyed = false; // Initialize dependencies this.sessionRepository = new SessionMetaRepository(); this.reportRepository = new ReportRepository(); this.eventBus = new UnifiedEventBus(); this.mochaIntegration = new MochaIntegration(); this.reportService = new ReportService(this.reportRepository, this.sessionRepository, this.eventBus); // Initialize use cases this.initializeUseCase = new InitializeRunnerUseCase(this.mochaIntegration, this.eventBus); this.defineSessionUseCase = new DefineSessionUseCase(this.sessionRepository, this.reportRepository, this.eventBus); this.runSingleSessionUseCase = new RunSingleSessionUseCase(this.sessionRepository, this.reportRepository, this.eventBus); this.runAllSessionsUseCase = new RunAllSessionsUseCase(this.sessionRepository, this.runSingleSessionUseCase, this.eventBus); this.clearAllSessionsUseCase = new ClearAllSessionsUseCase(this.sessionRepository, this.reportRepository, this.eventBus); this.destroyRunnerUseCase = new DestroyRunnerUseCase(this.clearAllSessionsUseCase, this.eventBus); // Bridge session events from the existing library this.bridgeExistingSessionEvents(); } // Core Management init(config) { if (this.destroyed) { throw new Error('Runner has been destroyed'); } // Set initialized flag before calling use case to avoid timing issues this.initialized = true; try { this.initializeUseCase.execute(config); } catch (error) { // Reset initialized flag on error this.initialized = false; throw error; } } define(label, setupFn) { this.ensureInitialized(); this.defineSessionUseCase.execute(label, setupFn); } async run(label) { this.ensureInitialized(); return this.runSingleSessionUseCase.execute(label); } async runAll(options) { this.ensureInitialized(); return this.runAllSessionsUseCase.execute(options); } // Report Access getReport(label) { this.ensureInitialized(); return this.reportService.getSessionReport(label); } getSessionStates() { this.ensureInitialized(); return this.sessionRepository.getStates(); } getCombinedReport() { this.ensureInitialized(); return this.reportService.generateCombinedReport(); } // Event System onRunnerEvent(eventType, callback) { return this.eventBus.onRunnerEvent(eventType, callback); } onSessionEvent(eventType, callback) { return this.eventBus.onSessionEvent(eventType, callback); } onAnyEvent(callback) { return this.eventBus.onAnyEvent(callback); } // State Queries isInitialized() { return this.initialized && !this.destroyed; } hasSession(label) { return this.sessionRepository.exists(label); } getSessionCount() { return this.sessionRepository.findAll().length; } // Utility async clear() { this.ensureInitialized(); await this.clearAllSessionsUseCase.execute(); } async destroy() { if (this.destroyed) return; await this.destroyRunnerUseCase.execute(); this.destroyed = true; this.initialized = false; } // Private helper methods ensureInitialized() { if (this.destroyed) { throw new Error('Runner has been destroyed'); } if (!this.initialized) { throw new Error('Runner not initialized. Call init() first.'); } } bridgeExistingSessionEvents() { // Bridge events from the existing mocha-multiple-sessions library if (typeof window !== 'undefined' && window.MochaMultipleSessions?.onAnySessionEvent) { window.MochaMultipleSessions.onAnySessionEvent((event) => { // Convert and forward the event const sessionEvent = { type: event.type, sessionLabel: event.sessionLabel, timestamp: event.timestamp || new Date(), data: event.data, source: 'mocha-multiple-sessions' }; this.eventBus.emitSessionEvent(sessionEvent); }); } } } function createMultiSessionDetailedRunner(config) { const runner = new MultiSessionDetailedRunner(); // Auto-initialize if autoInit is not explicitly false if (config?.autoInit !== false) { runner.init(config); } return runner; } export { MultiSessionDetailedRunner, createMultiSessionDetailedRunner }; //# sourceMappingURL=multi-session-detailed-runner.mjs.map