veza/apps/web/src/features/chat/components/ChatMessages.test.tsx

203 lines
5.1 KiB
TypeScript
Raw Normal View History

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 (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
// 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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
expect(screen.getByText(/Sélectionnez une conversation/)).toBeInTheDocument();
});
it('displays conversation header with participant count', () => {
render(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
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(
<TestWrapper>
<ChatMessages />
2025-12-13 02:34:34 +00:00
</TestWrapper>,
);
expect(screen.getByText(/👍 1/)).toBeInTheDocument();
expect(screen.getByText(/❤️ 1/)).toBeInTheDocument();
});
});