Files
stack/apps/api/src/websocket/websocket.gateway.spec.ts
Jason Woltje 9ff7718f9c feat(#15): implement Gantt chart component
- 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
2026-01-29 17:44:13 -06:00

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);
});
});
});