import { render, screen, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ChatMessages } from './ChatMessages'; import type { ChatMessage, Conversation } from '../store/chatStore'; const mockUseChatStore = vi.fn(); vi.mock('../store/chatStore', () => ({ useChatStore: (...args: unknown[]) => mockUseChatStore(...args), })); const CONV_ID = 'conv-123'; const mockMessages: ChatMessage[] = [ { id: '1', conversation_id: CONV_ID, sender_id: 'user-1', sender_username: 'user1', content: 'Hello, world!', created_at: '2024-01-01T10:00:00Z', }, { id: '2', conversation_id: CONV_ID, sender_id: 'user-2', sender_username: 'user2', content: 'How are you?', created_at: '2024-01-01T10:01:00Z', }, ]; const mockConversation: Conversation = { id: CONV_ID, name: 'Test Room', type: 'public', participants: ['user-1', 'user-2'], unread_count: 0, }; vi.mock('@/features/auth/hooks/useUser', () => ({ useUser: () => ({ data: { id: 'user-1', username: 'user1' }, }), })); const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const TestWrapper = ({ children }: { children: React.ReactNode }) => { const queryClient = createTestQueryClient(); return ( {children} ); }; describe('ChatMessages Component', () => { beforeEach(() => { vi.clearAllMocks(); Element.prototype.scrollIntoView = vi.fn(); mockUseChatStore.mockReturnValue({ currentConversationId: CONV_ID, conversations: [mockConversation], messages: { [CONV_ID]: mockMessages }, typingUsers: {}, }); }); it('renders messages correctly', () => { render( , ); expect(screen.getByText('Hello, world!')).toBeInTheDocument(); expect(screen.getByText('How are you?')).toBeInTheDocument(); // Current user (user-1) shows "Vous", others show "Utilisateur {sender_id}" expect(screen.getByText('Vous')).toBeInTheDocument(); expect(screen.getByText(/Utilisateur user-2/)).toBeInTheDocument(); }); it('displays message timestamps', () => { render( , ); // Locale may format as 10:00 or 11:00 AM - multiple messages have timestamps const timestamps = screen.getAllByText(/\d{1,2}:\d{2}/); expect(timestamps.length).toBeGreaterThanOrEqual(1); }); it('shows empty state when no messages', () => { mockUseChatStore.mockReturnValue({ currentConversationId: CONV_ID, conversations: [mockConversation], messages: { [CONV_ID]: [] }, typingUsers: {}, }); render( , ); expect(screen.getByText(/Aucun message dans cette conversation/)).toBeInTheDocument(); }); it('shows select conversation when no current conversation', () => { mockUseChatStore.mockReturnValue({ currentConversationId: null, conversations: [], messages: {}, typingUsers: {}, }); render( , ); expect(screen.getByText(/Sélectionnez une conversation/)).toBeInTheDocument(); }); it('displays conversation header with participant count', () => { render( , ); expect(screen.getByText('Test Room')).toBeInTheDocument(); expect(screen.getByText(/2 participant/)).toBeInTheDocument(); }); it('shows typing indicator when typingUsers present', () => { mockUseChatStore.mockReturnValue({ currentConversationId: CONV_ID, conversations: [mockConversation], messages: { [CONV_ID]: mockMessages }, typingUsers: { [CONV_ID]: ['user-2'] }, }); render( , ); expect(screen.getByText(/est en train d'écrire/)).toBeInTheDocument(); }); it('scrolls to bottom when messages change', async () => { const scrollIntoViewMock = vi.fn(); Element.prototype.scrollIntoView = scrollIntoViewMock; render( , ); await waitFor(() => { expect(scrollIntoViewMock).toHaveBeenCalled(); }); }); it('displays message reactions when present', () => { const msgsWithReactions: ChatMessage[] = [ { ...mockMessages[0], reactions: { '👍': ['user-1'], '❤️': ['user-2'] }, }, ]; mockUseChatStore.mockReturnValue({ currentConversationId: CONV_ID, conversations: [mockConversation], messages: { [CONV_ID]: msgsWithReactions }, typingUsers: {}, }); render( , ); expect(screen.getByText(/👍 1/)).toBeInTheDocument(); expect(screen.getByText(/❤️ 1/)).toBeInTheDocument(); }); });