- Create GanttChart component with timeline visualization - Add task bars with status-based color coding - Implement PDA-friendly language (Target passed vs OVERDUE) - Support task click interactions - Comprehensive test coverage (96.18%) - 33 tests passing (22 component + 11 helper tests) - Fully accessible with ARIA labels and keyboard navigation - Demo page at /demo/gantt - Responsive design with customizable height Technical details: - Uses Next.js 16 + React 19 + TypeScript - Strict typing (NO any types) - Helper functions to convert Task to GanttTask - Timeline calculation with automatic range detection - Status indicators: completed, in-progress, paused, not-started Refs #15
176 lines
5.0 KiB
TypeScript
176 lines
5.0 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { WebSocketGateway } from './websocket.gateway';
|
|
import { Server, Socket } from 'socket.io';
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
interface AuthenticatedSocket extends Socket {
|
|
data: {
|
|
userId: string;
|
|
workspaceId: string;
|
|
};
|
|
}
|
|
|
|
describe('WebSocketGateway', () => {
|
|
let gateway: WebSocketGateway;
|
|
let mockServer: Server;
|
|
let mockClient: AuthenticatedSocket;
|
|
|
|
beforeEach(async () => {
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [WebSocketGateway],
|
|
}).compile();
|
|
|
|
gateway = module.get<WebSocketGateway>(WebSocketGateway);
|
|
|
|
// Mock Socket.IO server
|
|
mockServer = {
|
|
to: vi.fn().mockReturnThis(),
|
|
emit: vi.fn(),
|
|
} as unknown as Server;
|
|
|
|
// Mock authenticated client
|
|
mockClient = {
|
|
id: 'test-socket-id',
|
|
join: vi.fn(),
|
|
leave: vi.fn(),
|
|
emit: vi.fn(),
|
|
data: {
|
|
userId: 'user-123',
|
|
workspaceId: 'workspace-456',
|
|
},
|
|
handshake: {
|
|
auth: {
|
|
token: 'valid-token',
|
|
},
|
|
},
|
|
} as unknown as AuthenticatedSocket;
|
|
|
|
gateway.server = mockServer;
|
|
});
|
|
|
|
describe('handleConnection', () => {
|
|
it('should join client to workspace room on connection', async () => {
|
|
await gateway.handleConnection(mockClient);
|
|
|
|
expect(mockClient.join).toHaveBeenCalledWith('workspace:workspace-456');
|
|
});
|
|
|
|
it('should reject connection without authentication', async () => {
|
|
const unauthClient = {
|
|
...mockClient,
|
|
data: {},
|
|
disconnect: vi.fn(),
|
|
} as unknown as AuthenticatedSocket;
|
|
|
|
await gateway.handleConnection(unauthClient);
|
|
|
|
expect(unauthClient.disconnect).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('handleDisconnect', () => {
|
|
it('should leave workspace room on disconnect', () => {
|
|
gateway.handleDisconnect(mockClient);
|
|
|
|
expect(mockClient.leave).toHaveBeenCalledWith('workspace:workspace-456');
|
|
});
|
|
});
|
|
|
|
describe('emitTaskCreated', () => {
|
|
it('should emit task:created event to workspace room', () => {
|
|
const task = {
|
|
id: 'task-1',
|
|
title: 'Test Task',
|
|
workspaceId: 'workspace-456',
|
|
};
|
|
|
|
gateway.emitTaskCreated('workspace-456', task);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('task:created', task);
|
|
});
|
|
});
|
|
|
|
describe('emitTaskUpdated', () => {
|
|
it('should emit task:updated event to workspace room', () => {
|
|
const task = {
|
|
id: 'task-1',
|
|
title: 'Updated Task',
|
|
workspaceId: 'workspace-456',
|
|
};
|
|
|
|
gateway.emitTaskUpdated('workspace-456', task);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('task:updated', task);
|
|
});
|
|
});
|
|
|
|
describe('emitTaskDeleted', () => {
|
|
it('should emit task:deleted event to workspace room', () => {
|
|
const taskId = 'task-1';
|
|
|
|
gateway.emitTaskDeleted('workspace-456', taskId);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('task:deleted', { id: taskId });
|
|
});
|
|
});
|
|
|
|
describe('emitEventCreated', () => {
|
|
it('should emit event:created event to workspace room', () => {
|
|
const event = {
|
|
id: 'event-1',
|
|
title: 'Test Event',
|
|
workspaceId: 'workspace-456',
|
|
};
|
|
|
|
gateway.emitEventCreated('workspace-456', event);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('event:created', event);
|
|
});
|
|
});
|
|
|
|
describe('emitEventUpdated', () => {
|
|
it('should emit event:updated event to workspace room', () => {
|
|
const event = {
|
|
id: 'event-1',
|
|
title: 'Updated Event',
|
|
workspaceId: 'workspace-456',
|
|
};
|
|
|
|
gateway.emitEventUpdated('workspace-456', event);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('event:updated', event);
|
|
});
|
|
});
|
|
|
|
describe('emitEventDeleted', () => {
|
|
it('should emit event:deleted event to workspace room', () => {
|
|
const eventId = 'event-1';
|
|
|
|
gateway.emitEventDeleted('workspace-456', eventId);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('event:deleted', { id: eventId });
|
|
});
|
|
});
|
|
|
|
describe('emitProjectUpdated', () => {
|
|
it('should emit project:updated event to workspace room', () => {
|
|
const project = {
|
|
id: 'project-1',
|
|
name: 'Updated Project',
|
|
workspaceId: 'workspace-456',
|
|
};
|
|
|
|
gateway.emitProjectUpdated('workspace-456', project);
|
|
|
|
expect(mockServer.to).toHaveBeenCalledWith('workspace:workspace-456');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('project:updated', project);
|
|
});
|
|
});
|
|
});
|