veza/apps/web/src/features/chat/components/ChatMessages.test.tsx
2025-12-12 21:34:34 -05:00

339 lines
8.5 KiB
TypeScript

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ChatMessages } from './ChatMessages';
// Mock des services
vi.mock('@/services/websocket', () => ({
useWebSocket: () => ({
sendMessage: vi.fn(),
isConnected: true,
}),
}));
vi.mock('@/stores/chat', () => ({
useChatStore: () => ({
messages: [
{
id: '1',
content: 'Hello, world!',
sender: { id: '1', username: 'user1' },
timestamp: '2024-01-01T10:00:00Z',
type: 'text',
},
{
id: '2',
content: 'How are you?',
sender: { id: '2', username: 'user2' },
timestamp: '2024-01-01T10:01:00Z',
type: 'text',
},
],
currentConversation: 'conv-123',
isLoading: false,
error: null,
}),
}));
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
const TestWrapper = ({ children }: { children: React.ReactNode }) => {
const queryClient = createTestQueryClient();
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
describe('ChatMessages Component', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders messages correctly', () => {
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
expect(screen.getByText('Hello, world!')).toBeInTheDocument();
expect(screen.getByText('How are you?')).toBeInTheDocument();
expect(screen.getByText('user1')).toBeInTheDocument();
expect(screen.getByText('user2')).toBeInTheDocument();
});
it('displays message timestamps', () => {
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
// Vérifier que les timestamps sont affichés (format peut varier)
expect(screen.getByText(/10:00/)).toBeInTheDocument();
expect(screen.getByText(/10:01/)).toBeInTheDocument();
});
it('shows loading state when messages are loading', () => {
vi.mocked(require('@/stores/chat').useChatStore).mockReturnValue({
messages: [],
currentConversation: 'conv-123',
isLoading: true,
error: null,
});
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
expect(screen.getByText(/loading messages/i)).toBeInTheDocument();
});
it('shows error state when there is an error', () => {
vi.mocked(require('@/stores/chat').useChatStore).mockReturnValue({
messages: [],
currentConversation: 'conv-123',
isLoading: false,
error: 'Failed to load messages',
});
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
expect(screen.getByText(/failed to load messages/i)).toBeInTheDocument();
});
it('shows empty state when no messages', () => {
vi.mocked(require('@/stores/chat').useChatStore).mockReturnValue({
messages: [],
currentConversation: 'conv-123',
isLoading: false,
error: null,
});
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
expect(screen.getByText(/no messages yet/i)).toBeInTheDocument();
});
it('handles different message types', () => {
vi.mocked(require('@/stores/chat').useChatStore).mockReturnValue({
messages: [
{
id: '1',
content: 'Text message',
sender: { id: '1', username: 'user1' },
timestamp: '2024-01-01T10:00:00Z',
type: 'text',
},
{
id: '2',
content: 'https://example.com',
sender: { id: '2', username: 'user2' },
timestamp: '2024-01-01T10:01:00Z',
type: 'link',
},
{
id: '3',
content: 'Image message',
sender: { id: '3', username: 'user3' },
timestamp: '2024-01-01T10:02:00Z',
type: 'image',
imageUrl: 'https://example.com/image.jpg',
},
],
currentConversation: 'conv-123',
isLoading: false,
error: null,
});
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
expect(screen.getByText('Text message')).toBeInTheDocument();
expect(screen.getByText('https://example.com')).toBeInTheDocument();
expect(screen.getByText('Image message')).toBeInTheDocument();
});
it('scrolls to bottom when new messages arrive', async () => {
const scrollIntoViewMock = vi.fn();
Element.prototype.scrollIntoView = scrollIntoViewMock;
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
await waitFor(() => {
expect(scrollIntoViewMock).toHaveBeenCalled();
});
});
it('handles message selection', () => {
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
const message = screen.getByText('Hello, world!');
fireEvent.click(message);
// Vérifier que le message est sélectionné (selon l'implémentation)
expect(message).toHaveClass('selected');
});
it('handles message reactions', () => {
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
const message = screen.getByText('Hello, world!');
const reactionButton = message.querySelector(
'[data-testid="reaction-button"]',
);
if (reactionButton) {
fireEvent.click(reactionButton);
expect(screen.getByText('👍')).toBeInTheDocument();
}
});
it('handles message editing', async () => {
const mockUpdateMessage = vi.fn();
vi.mocked(require('@/stores/chat').useChatStore).mockReturnValue({
messages: [
{
id: '1',
content: 'Hello, world!',
sender: { id: '1', username: 'user1' },
timestamp: '2024-01-01T10:00:00Z',
type: 'text',
canEdit: true,
},
],
currentConversation: 'conv-123',
isLoading: false,
error: null,
updateMessage: mockUpdateMessage,
});
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
const message = screen.getByText('Hello, world!');
const editButton = message.querySelector('[data-testid="edit-button"]');
if (editButton) {
fireEvent.click(editButton);
const editInput = screen.getByDisplayValue('Hello, world!');
fireEvent.change(editInput, {
target: { value: 'Hello, updated world!' },
});
fireEvent.keyDown(editInput, { key: 'Enter' });
await waitFor(() => {
expect(mockUpdateMessage).toHaveBeenCalledWith(
'1',
'Hello, updated world!',
);
});
}
});
it('handles message deletion', async () => {
const mockDeleteMessage = vi.fn();
vi.mocked(require('@/stores/chat').useChatStore).mockReturnValue({
messages: [
{
id: '1',
content: 'Hello, world!',
sender: { id: '1', username: 'user1' },
timestamp: '2024-01-01T10:00:00Z',
type: 'text',
canDelete: true,
},
],
currentConversation: 'conv-123',
isLoading: false,
error: null,
deleteMessage: mockDeleteMessage,
});
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
const message = screen.getByText('Hello, world!');
const deleteButton = message.querySelector('[data-testid="delete-button"]');
if (deleteButton) {
fireEvent.click(deleteButton);
const confirmButton = screen.getByText('Delete');
fireEvent.click(confirmButton);
await waitFor(() => {
expect(mockDeleteMessage).toHaveBeenCalledWith('1');
});
}
});
it('handles keyboard navigation', () => {
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
const container = screen.getByTestId('chat-messages');
fireEvent.keyDown(container, { key: 'ArrowUp' });
fireEvent.keyDown(container, { key: 'ArrowDown' });
fireEvent.keyDown(container, { key: 'Enter' });
fireEvent.keyDown(container, { key: 'Escape' });
// Vérifier que les événements sont gérés correctement
expect(container).toBeInTheDocument();
});
it('handles window resize', () => {
render(
<TestWrapper>
<ChatMessages />
</TestWrapper>,
);
// Simuler un redimensionnement de fenêtre
fireEvent.resize(window);
// Vérifier que le composant gère le redimensionnement
expect(screen.getByTestId('chat-messages')).toBeInTheDocument();
});
});