339 lines
8.5 KiB
TypeScript
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();
|
|
});
|
|
});
|