/**
* BreakpointManager Tests
*
* Comprehensive unit tests for the BreakpointManager class
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { EventEmitter } from 'events';
import { BreakpointManager, BreakpointCommand, BreakpointResult } from '../src/managers/BreakpointManager.js';
import { ConfigManager } from '../src/config/ConfigManager.js';
import { ChromeDebugger, BreakpointLocation, BreakpointOptions, BreakpointInfo, BreakpointHit } from '../src/debuggers/ChromeDebugger.js';
// Mock dependencies
vi.mock('../src/config/ConfigManager.js');
vi.mock('../src/debuggers/ChromeDebugger.js');
describe('BreakpointManager', () => {
let breakpointManager: BreakpointManager;
let mockConfigManager: ConfigManager;
let mockChromeDebugger: ChromeDebugger;
const mockConfig = {
breakpoints: {
maxRecentHits: 100,
autoRemoveAfterHits: undefined,
enableAnalytics: true,
persistBreakpoints: true,
logpointTimeout: 5000,
enableConditionalBreakpoints: true,
enableLogpoints: true
}
};
beforeEach(() => {
// Create mock instances
mockConfigManager = new ConfigManager();
mockChromeDebugger = new ChromeDebugger(mockConfigManager);
// Setup mock implementations
vi.mocked(mockConfigManager.getConfig).mockReturnValue(mockConfig);
vi.mocked(mockConfigManager.on).mockImplementation(() => mockConfigManager);
vi.mocked(mockChromeDebugger.on).mockImplementation(() => mockChromeDebugger);
vi.mocked(mockChromeDebugger.isDebuggerEnabled).mockReturnValue(true);
vi.mocked(mockChromeDebugger.getBreakpoints).mockReturnValue([]);
vi.mocked(mockChromeDebugger.getBreakpoint).mockReturnValue(undefined);
// Create BreakpointManager instance
breakpointManager = new BreakpointManager(mockConfigManager, mockChromeDebugger);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('Initialization', () => {
it('should initialize successfully', async () => {
await expect(breakpointManager.initialize()).resolves.not.toThrow();
});
it('should load configuration correctly', () => {
expect(mockConfigManager.getConfig).toHaveBeenCalled();
});
it('should set up event listeners', () => {
expect(mockChromeDebugger.on).toHaveBeenCalledWith('breakpointHit', expect.any(Function));
expect(mockChromeDebugger.on).toHaveBeenCalledWith('breakpointSet', expect.any(Function));
expect(mockChromeDebugger.on).toHaveBeenCalledWith('breakpointRemoved', expect.any(Function));
});
});
describe('Setting Breakpoints', () => {
const mockLocation: BreakpointLocation = {
filePath: 'src/test.js',
lineNumber: 10
};
const mockOptions: BreakpointOptions = {
condition: 'x > 5',
enabled: true
};
it('should set a breakpoint successfully', async () => {
const mockBreakpoint: BreakpointInfo = {
id: 'bp-123',
location: mockLocation,
options: mockOptions,
hitCount: 0,
enabled: true,
timestamp: new Date()
};
vi.mocked(mockChromeDebugger.setBreakpoint).mockResolvedValue(mockBreakpoint);
const command: BreakpointCommand = {
action: 'set',
location: mockLocation,
options: mockOptions
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.breakpoint).toEqual(mockBreakpoint);
expect(mockChromeDebugger.setBreakpoint).toHaveBeenCalledWith(mockLocation, mockOptions);
});
it('should handle breakpoint setting failure', async () => {
vi.mocked(mockChromeDebugger.setBreakpoint).mockRejectedValue(new Error('Failed to set breakpoint'));
const command: BreakpointCommand = {
action: 'set',
location: mockLocation,
options: mockOptions
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('Failed to set breakpoint');
});
it('should fail when debugger is not enabled', async () => {
vi.mocked(mockChromeDebugger.isDebuggerEnabled).mockReturnValue(false);
const command: BreakpointCommand = {
action: 'set',
location: mockLocation,
options: mockOptions
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('Chrome debugger not connected');
});
});
describe('Removing Breakpoints', () => {
it('should remove a breakpoint successfully', async () => {
vi.mocked(mockChromeDebugger.removeBreakpoint).mockResolvedValue(true);
const command: BreakpointCommand = {
action: 'remove',
breakpointId: 'bp-123'
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.message).toContain('removed successfully');
expect(mockChromeDebugger.removeBreakpoint).toHaveBeenCalledWith('bp-123');
});
it('should handle breakpoint not found', async () => {
vi.mocked(mockChromeDebugger.removeBreakpoint).mockResolvedValue(false);
const command: BreakpointCommand = {
action: 'remove',
breakpointId: 'bp-nonexistent'
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('not found');
});
});
describe('Listing Breakpoints', () => {
it('should list all breakpoints', async () => {
const mockBreakpoints: BreakpointInfo[] = [
{
id: 'bp-1',
location: { filePath: 'src/test1.js', lineNumber: 10 },
options: { enabled: true },
hitCount: 5,
enabled: true,
timestamp: new Date()
},
{
id: 'bp-2',
location: { filePath: 'src/test2.js', lineNumber: 20 },
options: { enabled: true },
hitCount: 2,
enabled: true,
timestamp: new Date()
}
];
vi.mocked(mockChromeDebugger.getBreakpoints).mockReturnValue(mockBreakpoints);
const command: BreakpointCommand = {
action: 'list'
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.breakpoints).toEqual(mockBreakpoints);
expect(result.message).toContain('Found 2 breakpoint(s)');
});
});
describe('Clearing Breakpoints', () => {
it('should clear all breakpoints successfully', async () => {
vi.mocked(mockChromeDebugger.clearAllBreakpoints).mockResolvedValue();
const command: BreakpointCommand = {
action: 'clear'
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.message).toContain('cleared successfully');
expect(mockChromeDebugger.clearAllBreakpoints).toHaveBeenCalled();
});
});
describe('Toggling Breakpoints', () => {
it('should enable breakpoints successfully', async () => {
vi.mocked(mockChromeDebugger.setBreakpointsActive).mockResolvedValue();
const command: BreakpointCommand = {
action: 'toggle',
active: true
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.message).toContain('enabled successfully');
expect(mockChromeDebugger.setBreakpointsActive).toHaveBeenCalledWith(true);
});
it('should disable breakpoints successfully', async () => {
vi.mocked(mockChromeDebugger.setBreakpointsActive).mockResolvedValue();
const command: BreakpointCommand = {
action: 'toggle',
active: false
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.message).toContain('disabled successfully');
expect(mockChromeDebugger.setBreakpointsActive).toHaveBeenCalledWith(false);
});
});
describe('Analytics', () => {
it('should return analytics when enabled', async () => {
const mockBreakpoints: BreakpointInfo[] = [
{
id: 'bp-1',
location: { filePath: 'src/test1.js', lineNumber: 10 },
options: { enabled: true },
hitCount: 5,
enabled: true,
timestamp: new Date()
},
{
id: 'bp-2',
location: { filePath: 'src/test2.js', lineNumber: 20 },
options: { enabled: true },
hitCount: 3,
enabled: true,
timestamp: new Date()
}
];
vi.mocked(mockChromeDebugger.getBreakpoints).mockReturnValue(mockBreakpoints);
const command: BreakpointCommand = {
action: 'analytics'
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(true);
expect(result.analytics).toBeDefined();
expect(result.analytics!.totalBreakpoints).toBe(2);
expect(result.analytics!.activeBreakpoints).toBe(2);
expect(result.analytics!.totalHits).toBe(8);
expect(result.analytics!.averageHitsPerBreakpoint).toBe(4);
expect(result.analytics!.mostHitBreakpoint).toEqual(mockBreakpoints[0]);
});
it('should fail when analytics is disabled', async () => {
const disabledConfig = {
...mockConfig,
breakpoints: {
...mockConfig.breakpoints,
enableAnalytics: false
}
};
vi.mocked(mockConfigManager.getConfig).mockReturnValue(disabledConfig);
// Recreate manager with disabled analytics
breakpointManager = new BreakpointManager(mockConfigManager, mockChromeDebugger);
const command: BreakpointCommand = {
action: 'analytics'
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('Analytics disabled');
});
});
describe('Breakpoint Hit Handling', () => {
it('should handle breakpoint hits and update analytics', () => {
const mockBreakpoint: BreakpointInfo = {
id: 'bp-123',
location: { filePath: 'src/test.js', lineNumber: 10 },
options: { enabled: true },
hitCount: 0,
enabled: true,
timestamp: new Date()
};
const mockHit: BreakpointHit = {
breakpointId: 'bp-123',
timestamp: new Date(),
callFrames: [],
reason: 'breakpoint'
};
vi.mocked(mockChromeDebugger.getBreakpoint).mockReturnValue(mockBreakpoint);
// Simulate breakpoint hit event
const hitHandler = vi.mocked(mockChromeDebugger.on).mock.calls
.find(call => call[0] === 'breakpointHit')?.[1] as Function;
expect(hitHandler).toBeDefined();
// Trigger the hit handler
hitHandler(mockHit);
// Verify that the hit was processed (this would update internal state)
// Since we can't directly access private members, we verify through behavior
expect(mockChromeDebugger.getBreakpoint).toHaveBeenCalledWith('bp-123');
});
});
describe('Unknown Commands', () => {
it('should handle unknown commands gracefully', async () => {
const command = {
action: 'unknown' as any
};
const result = await breakpointManager.executeCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('Unknown breakpoint action');
});
});
describe('Shutdown', () => {
it('should shutdown gracefully', async () => {
await expect(breakpointManager.shutdown()).resolves.not.toThrow();
});
});
});