- ChatMessages: fix mock structure, align with store shape (messages Record, conversations) - ChatInput: add tests for render, submit, disabled state - ChatMessage: add tests for content, reactions, addReaction - fix ChatMessage.tsx: remove stray // ... comment Co-authored-by: Cursor <cursoragent@cursor.com>
202 lines
5.1 KiB
TypeScript
202 lines
5.1 KiB
TypeScript
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 />
|
|
</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 />
|
|
</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 />
|
|
</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 />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
expect(screen.getByText(/Sélectionnez une conversation/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays conversation header with participant count', () => {
|
|
render(
|
|
<TestWrapper>
|
|
<ChatMessages />
|
|
</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 />
|
|
</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 />
|
|
</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 />
|
|
</TestWrapper>,
|
|
);
|
|
|
|
expect(screen.getByText(/👍 1/)).toBeInTheDocument();
|
|
expect(screen.getByText(/❤️ 1/)).toBeInTheDocument();
|
|
});
|
|
});
|