mocha-multiple-sessions-detailed-runner
Version:
High-level API for multi-session Mocha tests with detailed reporting
1,124 lines (1,112 loc) • 45.3 kB
JavaScript
var MultiSessionDetailedRunner = (function (exports) {
'use strict';
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;
}
exports.MultiSessionDetailedRunner = MultiSessionDetailedRunner;
exports.createMultiSessionDetailedRunner = createMultiSessionDetailedRunner;
return exports;
})({});
//# sourceMappingURL=multi-session-detailed-runner.js.map