import { render, screen } from '@testing-library/react-native'; import React from 'react'; import { MessageList } from '../components/chat/MessageList'; import { ChatMessage } from '../store/useAppStore'; jest.mock('react-native-reanimated', () => { const { View } = require('react-native'); const chain: any = new Proxy(() => chain, { get: () => chain }); return { __esModule: true, default: { View }, FadeInDown: chain }; }); const mockRenders: Record = {}; jest.mock('../components/chat/MessageBubble', () => { const { Text } = require('react-native'); return { MessageBubble: ({ message }: any) => { mockRenders[message.id] = (mockRenders[message.id] ?? 0) + 1; return {`${message.id}:${message.content}`}; }, }; }); jest.mock('../components/chat/TypingIndicator', () => { const { Text } = require('react-native'); return { TypingIndicator: () => typing-indicator }; }); const msg = (id: string, role: ChatMessage['role'], content: string): ChatMessage => ({ id, role, content, timestamp: 0, }); const handlers = { onEdit: jest.fn(), onDelete: jest.fn(), onRegenerate: jest.fn() }; beforeEach(() => { Object.keys(mockRenders).forEach((k) => delete mockRenders[k]); }); describe('MessageList', () => { it('renders every message', () => { render( , ); expect(screen.getByText('u1:q')).toBeTruthy(); expect(screen.getByText('a1:a')).toBeTruthy(); }); it('re-renders only the message that changed while an answer streams in', () => { const user = msg('u1', 'user', 'question'); const earlier = msg('a0', 'assistant', 'earlier answer'); const streaming = msg('a1', 'assistant', 'He'); const { rerender } = render( , ); expect(mockRenders).toEqual({ u1: 1, a0: 1, a1: 1 }); rerender( , ); expect(screen.getByText('a1:Hello')).toBeTruthy(); expect(mockRenders.u1).toBe(1); expect(mockRenders.a0).toBe(1); expect(mockRenders.a1).toBe(2); }); it('shows the typing indicator while the last assistant message is still empty', () => { render( , ); expect(screen.getByText('typing-indicator')).toBeTruthy(); }); });