Skip to content

Commit ccb718b

Browse files
Chuanyi SuChuanyi Su
authored andcommitted
test: improve coverage for channel.handler, interaction-manager
1 parent 5e3236e commit ccb718b

2 files changed

Lines changed: 324 additions & 0 deletions

File tree

packages/cli/src/components/interaction-manager.test.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,4 +1061,181 @@ describe('InteractionManager', () => {
10611061
}).not.toThrow();
10621062
});
10631063
});
1064+
1065+
describe('Shortcut Dispatch Coverage', () => {
1066+
let interactionManager: any;
1067+
let mockScreen: any;
1068+
let mockComponent: any;
1069+
1070+
beforeEach(() => {
1071+
mockScreen = {
1072+
key: jest.fn(),
1073+
on: jest.fn(),
1074+
program: { key: jest.fn(), on: jest.fn() },
1075+
};
1076+
mockComponent = {
1077+
getId: () => 'comp1',
1078+
getType: () => 'panel',
1079+
canFocus: () => true,
1080+
getFocusState: () => ({ focused: false, tabIndex: 0 }),
1081+
handleKey: () => false,
1082+
handleMouse: () => false,
1083+
focus: jest.fn(),
1084+
blur: jest.fn(),
1085+
};
1086+
});
1087+
1088+
it('should emit refresh-all action', () => {
1089+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1090+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1091+
interactionManager.registerComponent(mockComponent);
1092+
// Access private method to test shortcut dispatch
1093+
interactionManager.registerShortcut({
1094+
key: 'r',
1095+
action: 'refresh-all',
1096+
description: 'Refresh all',
1097+
});
1098+
// Trigger key handler
1099+
interactionManager.handleKeyboardEvent({ key: 'r', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1100+
expect(emitSpy).toHaveBeenCalledWith('action:refresh-all');
1101+
});
1102+
1103+
it('should emit refresh-current action', () => {
1104+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1105+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1106+
interactionManager.registerComponent(mockComponent);
1107+
interactionManager.registerShortcut({
1108+
key: 'R',
1109+
action: 'refresh-current',
1110+
description: 'Refresh current',
1111+
});
1112+
interactionManager.handleKeyboardEvent({ key: 'R', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1113+
expect(emitSpy).toHaveBeenCalledWith('action:refresh-current');
1114+
});
1115+
1116+
it('should emit help action', () => {
1117+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1118+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1119+
interactionManager.registerComponent(mockComponent);
1120+
interactionManager.registerShortcut({ key: '?', action: 'help', description: 'Help' });
1121+
interactionManager.handleKeyboardEvent({ key: '?', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1122+
expect(emitSpy).toHaveBeenCalledWith('action:help');
1123+
});
1124+
1125+
it('should emit exit action', () => {
1126+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1127+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1128+
interactionManager.registerComponent(mockComponent);
1129+
interactionManager.registerShortcut({ key: 'q', action: 'exit', description: 'Exit' });
1130+
interactionManager.handleKeyboardEvent({ key: 'q', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1131+
expect(emitSpy).toHaveBeenCalledWith('action:exit');
1132+
});
1133+
1134+
it('should emit cancel action', () => {
1135+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1136+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1137+
interactionManager.registerComponent(mockComponent);
1138+
interactionManager.registerShortcut({ key: 'escape', action: 'cancel', description: 'Cancel' });
1139+
interactionManager.handleKeyboardEvent({ key: 'escape', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1140+
expect(emitSpy).toHaveBeenCalledWith('action:cancel');
1141+
});
1142+
1143+
it('should emit fullscreen action', () => {
1144+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1145+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1146+
interactionManager.registerComponent(mockComponent);
1147+
interactionManager.registerShortcut({ key: 'f', action: 'fullscreen', description: 'Fullscreen' });
1148+
interactionManager.handleKeyboardEvent({ key: 'f', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1149+
expect(emitSpy).toHaveBeenCalledWith('action:fullscreen');
1150+
});
1151+
1152+
it('should emit clear-screen action', () => {
1153+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1154+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1155+
interactionManager.registerComponent(mockComponent);
1156+
interactionManager.registerShortcut({ key: 'l', action: 'clear-screen', description: 'Clear' });
1157+
interactionManager.handleKeyboardEvent({ key: 'l', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1158+
expect(emitSpy).toHaveBeenCalledWith('action:clear-screen');
1159+
});
1160+
1161+
it('should emit search action', () => {
1162+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1163+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1164+
interactionManager.registerComponent(mockComponent);
1165+
interactionManager.registerShortcut({ key: '/', action: 'search', description: 'Search' });
1166+
interactionManager.handleKeyboardEvent({ key: '/', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1167+
expect(emitSpy).toHaveBeenCalledWith('action:search');
1168+
});
1169+
1170+
it('should emit layout action for layout: prefix', () => {
1171+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1172+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1173+
interactionManager.registerComponent(mockComponent);
1174+
interactionManager.registerShortcut({ key: '1', action: 'layout:1', description: 'Layout 1' });
1175+
interactionManager.handleKeyboardEvent({ key: '1', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1176+
expect(emitSpy).toHaveBeenCalledWith('action:layout', { layout: '1' });
1177+
});
1178+
1179+
it('should emit panel action for panel: prefix', () => {
1180+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1181+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1182+
interactionManager.registerComponent(mockComponent);
1183+
interactionManager.registerShortcut({ key: 'p', action: 'panel:3', description: 'Panel 3' });
1184+
interactionManager.handleKeyboardEvent({ key: 'p', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1185+
expect(emitSpy).toHaveBeenCalledWith('action:panel', { panel: '3' });
1186+
});
1187+
1188+
it('should emit custom action for unknown prefix', () => {
1189+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1190+
const emitSpy = jest.spyOn(interactionManager, 'emit');
1191+
interactionManager.registerComponent(mockComponent);
1192+
interactionManager.registerShortcut({ key: 'x', action: 'custom:action', description: 'Custom' });
1193+
interactionManager.handleKeyboardEvent({ key: 'x', shift: false, ctrl: false, alt: false, meta: false, full: '', ch: '' });
1194+
expect(emitSpy).toHaveBeenCalledWith('action:custom', expect.objectContaining({ action: 'custom:action' }));
1195+
});
1196+
1197+
it('should return all shortcuts via getAllShortcuts', () => {
1198+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1199+
interactionManager.registerComponent(mockComponent);
1200+
interactionManager.registerShortcut({ key: 'q', action: 'exit', description: 'Exit' });
1201+
const shortcuts = interactionManager.getAllShortcuts();
1202+
expect(shortcuts.length).toBeGreaterThan(0);
1203+
expect(shortcuts.some(s => s.action === 'exit')).toBe(true);
1204+
});
1205+
1206+
it('should return current focus via getCurrentFocus', () => {
1207+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1208+
interactionManager.registerComponent(mockComponent);
1209+
interactionManager.setFocus('comp1');
1210+
expect(interactionManager.getCurrentFocus()).toBe('comp1');
1211+
});
1212+
1213+
it('should return components via getComponents', () => {
1214+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1215+
interactionManager.registerComponent(mockComponent);
1216+
expect(interactionManager.getComponents()).toHaveLength(1);
1217+
});
1218+
1219+
it('should return shortcuts via getShortcuts', () => {
1220+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1221+
interactionManager.registerShortcut({ key: 'q', action: 'exit', description: 'Exit' });
1222+
const shortcuts = interactionManager.getShortcuts();
1223+
expect(shortcuts.length).toBeGreaterThan(0);
1224+
expect(shortcuts.some(s => s.action === 'exit')).toBe(true);
1225+
});
1226+
1227+
it('should return component by id via getComponent', () => {
1228+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1229+
interactionManager.registerComponent(mockComponent);
1230+
expect(interactionManager.getComponent('comp1')).toBeDefined();
1231+
expect(interactionManager.getComponent('nonexistent')).toBeUndefined();
1232+
});
1233+
1234+
it('should return destroyed state via isInteractionManagerDestroyed', () => {
1235+
interactionManager = new InteractionManager(mockScreen, { keyboard: true, mouse: false, shortcuts: [] });
1236+
expect(interactionManager.isInteractionManagerDestroyed()).toBe(false);
1237+
interactionManager.destroy();
1238+
expect(interactionManager.isInteractionManagerDestroyed()).toBe(true);
1239+
});
1240+
});
10641241
});

packages/cli/src/handlers/channel.handler.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,4 +1099,151 @@ describe('ChannelHandler', () => {
10991099
});
11001100
});
11011101
});
1102+
1103+
// ============================================
1104+
// deleteChannel Tests
1105+
// ============================================
1106+
1107+
describe('deleteChannel', () => {
1108+
it('should delete single channel successfully', async () => {
1109+
const mockChannelService = {
1110+
deleteChannel: jest.fn().mockResolvedValue({}),
1111+
};
1112+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1113+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1114+
1115+
await handler.deleteChannel({ channelId: '123456', force: true });
1116+
1117+
expect(mockChannelService.deleteChannel).toHaveBeenCalled();
1118+
});
1119+
1120+
it('should throw PolyVValidationError for missing channelId', async () => {
1121+
const mockChannelService = { deleteChannel: jest.fn() };
1122+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1123+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1124+
1125+
await expect(handler.deleteChannel({ channelId: '' }))
1126+
.rejects.toThrow(PolyVValidationError);
1127+
});
1128+
1129+
it('should throw PolyVValidationError for non-string channelId', async () => {
1130+
const mockChannelService = { deleteChannel: jest.fn() };
1131+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1132+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1133+
1134+
await expect(handler.deleteChannel({ channelId: 123 as any }))
1135+
.rejects.toThrow(PolyVValidationError);
1136+
});
1137+
1138+
it('should throw PolyVValidationError for invalid force flag', async () => {
1139+
const mockChannelService = { deleteChannel: jest.fn() };
1140+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1141+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1142+
1143+
await expect(handler.deleteChannel({ channelId: '123', force: 'yes' as any }))
1144+
.rejects.toThrow(PolyVValidationError);
1145+
});
1146+
1147+
it('should throw PolyVValidationError for invalid output format', async () => {
1148+
const mockChannelService = { deleteChannel: jest.fn() };
1149+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1150+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1151+
1152+
await expect(handler.deleteChannel({ channelId: '123', output: 'xml' as any }))
1153+
.rejects.toThrow(PolyVValidationError);
1154+
});
1155+
1156+
it('should throw PolyVError when service fails', async () => {
1157+
const mockChannelService = {
1158+
deleteChannel: jest.fn().mockRejectedValue(new PolyVError('API error')),
1159+
};
1160+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1161+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1162+
1163+
await expect(handler.deleteChannel({ channelId: '123', force: true }))
1164+
.rejects.toThrow(PolyVError);
1165+
});
1166+
});
1167+
1168+
// ============================================
1169+
// deleteChannels (batch) Tests
1170+
// ============================================
1171+
1172+
describe('deleteChannels', () => {
1173+
it('should delete multiple channels successfully', async () => {
1174+
const mockChannelService = {
1175+
batchDeleteChannels: jest.fn().mockResolvedValue({}),
1176+
};
1177+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1178+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1179+
1180+
await handler.deleteChannels({ channelIds: ['111', '222', '333'] });
1181+
1182+
expect(mockChannelService.batchDeleteChannels).toHaveBeenCalled();
1183+
});
1184+
1185+
it('should throw for missing channelIds', async () => {
1186+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1187+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1188+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1189+
1190+
await expect(handler.deleteChannels({}))
1191+
.rejects.toThrow(PolyVValidationError);
1192+
});
1193+
1194+
it('should throw for non-array channelIds', async () => {
1195+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1196+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1197+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1198+
1199+
await expect(handler.deleteChannels({ channelIds: '123' as any }))
1200+
.rejects.toThrow(PolyVValidationError);
1201+
});
1202+
1203+
it('should throw for empty channelIds array', async () => {
1204+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1205+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1206+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1207+
1208+
await expect(handler.deleteChannels({ channelIds: [] }))
1209+
.rejects.toThrow(PolyVValidationError);
1210+
});
1211+
1212+
it('should throw for more than 100 channelIds', async () => {
1213+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1214+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1215+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1216+
1217+
const ids = Array.from({ length: 101 }, (_, i) => `ch${i}`);
1218+
await expect(handler.deleteChannels({ channelIds: ids }))
1219+
.rejects.toThrow(PolyVValidationError);
1220+
});
1221+
1222+
it('should throw for non-string channelId in array', async () => {
1223+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1224+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1225+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1226+
1227+
await expect(handler.deleteChannels({ channelIds: [123 as any] }))
1228+
.rejects.toThrow(PolyVValidationError);
1229+
});
1230+
1231+
it('should throw for empty string channelId in array', async () => {
1232+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1233+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1234+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1235+
1236+
await expect(handler.deleteChannels({ channelIds: [''] }))
1237+
.rejects.toThrow(PolyVValidationError);
1238+
});
1239+
1240+
it('should throw for invalid output format in batch delete', async () => {
1241+
const mockChannelService = { batchDeleteChannels: jest.fn() };
1242+
MockedChannelService.mockImplementation(() => mockChannelService as any);
1243+
const handler = new ChannelHandler(mockAuthConfig, mockServiceConfig);
1244+
1245+
await expect(handler.deleteChannels({ channelIds: ['123'], output: 'xml' as any }))
1246+
.rejects.toThrow(PolyVValidationError);
1247+
});
1248+
});
11021249
});

0 commit comments

Comments
 (0)