- Added action mode validation in cross-team and team messaging handlers to ensure only valid modes ('do', 'ask', 'delegate') are accepted.
- Enhanced MessageComposer and TeamDetailView to support action mode selection and pass it through to message sending functions.
- Introduced utility functions to build action mode-specific message content for better clarity in communication.
- Updated tests to cover action mode scenarios, including validation and handling of invalid modes.
- Improved user experience by integrating action mode instructions into message delivery for leads.
183 lines
5.5 KiB
TypeScript
183 lines
5.5 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
|
|
vi.mock('@shared/utils/logger', () => ({
|
|
createLogger: () => ({
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@preload/constants/ipcChannels', () => ({
|
|
CROSS_TEAM_SEND: 'cross-team:send',
|
|
CROSS_TEAM_LIST_TARGETS: 'cross-team:listTargets',
|
|
CROSS_TEAM_GET_OUTBOX: 'cross-team:getOutbox',
|
|
}));
|
|
|
|
import {
|
|
initializeCrossTeamHandlers,
|
|
registerCrossTeamHandlers,
|
|
removeCrossTeamHandlers,
|
|
} from '@main/ipc/crossTeam';
|
|
|
|
function createMockIpcMain() {
|
|
return {
|
|
handle: vi.fn(),
|
|
removeHandler: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function createMockService() {
|
|
return {
|
|
send: vi.fn(),
|
|
listAvailableTargets: vi.fn(),
|
|
getOutbox: vi.fn(),
|
|
};
|
|
}
|
|
|
|
describe('crossTeam IPC handlers', () => {
|
|
let mockIpc: ReturnType<typeof createMockIpcMain>;
|
|
let mockService: ReturnType<typeof createMockService>;
|
|
|
|
beforeEach(() => {
|
|
mockIpc = createMockIpcMain();
|
|
mockService = createMockService();
|
|
// Re-initialize with fresh service for each test
|
|
initializeCrossTeamHandlers(mockService as never);
|
|
});
|
|
|
|
it('registers 3 IPC handlers', () => {
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
|
|
expect(mockIpc.handle).toHaveBeenCalledTimes(3);
|
|
expect(mockIpc.handle).toHaveBeenCalledWith('cross-team:send', expect.any(Function));
|
|
expect(mockIpc.handle).toHaveBeenCalledWith('cross-team:listTargets', expect.any(Function));
|
|
expect(mockIpc.handle).toHaveBeenCalledWith('cross-team:getOutbox', expect.any(Function));
|
|
});
|
|
|
|
it('removes all 3 handlers', () => {
|
|
removeCrossTeamHandlers(mockIpc as never);
|
|
|
|
expect(mockIpc.removeHandler).toHaveBeenCalledTimes(3);
|
|
expect(mockIpc.removeHandler).toHaveBeenCalledWith('cross-team:send');
|
|
expect(mockIpc.removeHandler).toHaveBeenCalledWith('cross-team:listTargets');
|
|
expect(mockIpc.removeHandler).toHaveBeenCalledWith('cross-team:getOutbox');
|
|
});
|
|
|
|
it('send handler returns success on valid request', async () => {
|
|
mockService.send.mockResolvedValue({ messageId: 'msg-1', deliveredToInbox: true });
|
|
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find((c) => c[0] === 'cross-team:send')![1];
|
|
|
|
const result = await handler({} as never, {
|
|
fromTeam: 'team-a',
|
|
fromMember: 'lead',
|
|
toTeam: 'team-b',
|
|
text: 'Hello',
|
|
actionMode: 'delegate',
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
success: true,
|
|
data: { messageId: 'msg-1', deliveredToInbox: true },
|
|
});
|
|
expect(mockService.send).toHaveBeenCalledWith({
|
|
fromTeam: 'team-a',
|
|
fromMember: 'lead',
|
|
toTeam: 'team-b',
|
|
text: 'Hello',
|
|
actionMode: 'delegate',
|
|
summary: undefined,
|
|
chainDepth: undefined,
|
|
});
|
|
});
|
|
|
|
it('send handler rejects invalid actionMode', async () => {
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find((c) => c[0] === 'cross-team:send')![1];
|
|
|
|
const result = await handler({} as never, {
|
|
fromTeam: 'team-a',
|
|
fromMember: 'lead',
|
|
toTeam: 'team-b',
|
|
text: 'Hello',
|
|
actionMode: 'break-everything',
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
success: false,
|
|
error: 'actionMode must be one of: do, ask, delegate',
|
|
});
|
|
});
|
|
|
|
it('send handler returns error on service throw', async () => {
|
|
mockService.send.mockRejectedValue(new Error('Target team not found'));
|
|
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find((c) => c[0] === 'cross-team:send')![1];
|
|
|
|
const result = await handler({} as never, {
|
|
fromTeam: 'team-a',
|
|
fromMember: 'lead',
|
|
toTeam: 'nonexistent',
|
|
text: 'Hello',
|
|
});
|
|
|
|
expect(result).toEqual({ success: false, error: 'Target team not found' });
|
|
});
|
|
|
|
it('send handler rejects invalid request', async () => {
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find((c) => c[0] === 'cross-team:send')![1];
|
|
|
|
const result = await handler({} as never, null);
|
|
|
|
expect(result).toEqual({ success: false, error: 'Invalid request' });
|
|
});
|
|
|
|
it('listTargets handler calls service', async () => {
|
|
mockService.listAvailableTargets.mockResolvedValue([
|
|
{ teamName: 'team-b', displayName: 'Team B' },
|
|
]);
|
|
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find(
|
|
(c) => c[0] === 'cross-team:listTargets'
|
|
)![1];
|
|
|
|
const result = await handler({} as never, 'team-a');
|
|
|
|
expect(result).toEqual({
|
|
success: true,
|
|
data: [{ teamName: 'team-b', displayName: 'Team B' }],
|
|
});
|
|
expect(mockService.listAvailableTargets).toHaveBeenCalledWith('team-a');
|
|
});
|
|
|
|
it('getOutbox handler calls service', async () => {
|
|
mockService.getOutbox.mockResolvedValue([]);
|
|
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find(
|
|
(c) => c[0] === 'cross-team:getOutbox'
|
|
)![1];
|
|
|
|
const result = await handler({} as never, 'team-a');
|
|
|
|
expect(result).toEqual({ success: true, data: [] });
|
|
expect(mockService.getOutbox).toHaveBeenCalledWith('team-a');
|
|
});
|
|
|
|
it('getOutbox handler rejects empty teamName', async () => {
|
|
registerCrossTeamHandlers(mockIpc as never);
|
|
const handler = mockIpc.handle.mock.calls.find(
|
|
(c) => c[0] === 'cross-team:getOutbox'
|
|
)![1];
|
|
|
|
const result = await handler({} as never, '');
|
|
|
|
expect(result).toEqual({ success: false, error: 'teamName is required' });
|
|
});
|
|
});
|