import React from 'react'; import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { setLocale } from '../src/i18n'; import ChatMessageList from '../src/components/chat/ChatMessageList'; const mocks = vi.hoisted(() => ({ apiOpenSkillPath: vi.fn(), apiOpenSkillReadme: vi.fn(), writeText: vi.fn(), })); vi.mock('../src/lib/skills-api', () => ({ apiOpenSkillPath: mocks.apiOpenSkillPath, apiOpenSkillReadme: mocks.apiOpenSkillReadme, })); describe('ChatMessageList', () => { beforeEach(() => { setLocale('en'); vi.clearAllMocks(); mocks.apiOpenSkillPath.mockResolvedValue(undefined); mocks.apiOpenSkillReadme.mockResolvedValue(undefined); mocks.writeText.mockResolvedValue(undefined); Object.defineProperty(window.navigator, 'clipboard', { value: { writeText: mocks.writeText, }, configurable: true, }); }); it('renders standalone streaming tool status cards when there is no streaming assistant text yet', () => { render( , ); expect(screen.getByText('Running tools...')).toBeTruthy(); expect(screen.getByText('Install Skill')).toBeTruthy(); expect(screen.getByText('Running')).toBeTruthy(); expect(screen.getByText('Installing minimax-xlsx')).toBeTruthy(); }); it('renders streaming tool status cards above a streaming assistant message', () => { render( , ); expect(screen.getByText('Open Webpage')).toBeTruthy(); expect(screen.getByText('Completed')).toBeTruthy(); expect(screen.getByText('1.2s')).toBeTruthy(); expect(screen.getByText('Opened http://www.baidu.com/')).toBeTruthy(); expect(screen.getByText('The page is open.')).toBeTruthy(); }); it('renders persistent skill-install tool cards with follow-up actions', async () => { render( , ); expect(screen.getAllByText('Installed and enabled minimax-xlsx at /tmp/minimax-xlsx')).toHaveLength(1); expect(screen.getByText('Skill')).toBeTruthy(); expect(screen.getByText('/tmp/minimax-xlsx')).toBeTruthy(); expect(screen.getByText('Next actions')).toBeTruthy(); fireEvent.click(screen.getByText('Open folder')); await waitFor(() => { expect(mocks.apiOpenSkillPath).toHaveBeenCalledWith( 'minimax-xlsx', 'minimax-xlsx', '/tmp/minimax-xlsx', ); }); fireEvent.click(screen.getByText('Copy path')); await waitFor(() => { expect(mocks.writeText).toHaveBeenCalledWith('/tmp/minimax-xlsx'); }); expect(screen.getByText('Path copied')).toBeTruthy(); }); it('renders spreadsheet analysis cards from structured tool results', () => { render( , ); const spreadsheetCard = screen.getByTestId('tool-spreadsheet-preview'); expect(within(spreadsheetCard).getByText('Analysis overview')).toBeTruthy(); expect(within(spreadsheetCard).getByText('1 file')).toBeTruthy(); expect(within(spreadsheetCard).getAllByText('1 sheet').length).toBeGreaterThan(0); expect(within(spreadsheetCard).getAllByText('18,358 rows').length).toBeGreaterThan(0); expect(within(spreadsheetCard).getByText('hotel-sales.xls')).toBeTruthy(); expect(within(spreadsheetCard).getByText('Grid Results')).toBeTruthy(); expect(within(spreadsheetCard).getByText('12 cols')).toBeTruthy(); expect(within(spreadsheetCard).getByText(/Columns: channel, sales_amount, sales_date, room_type/)).toBeTruthy(); expect(within(spreadsheetCard).getByText('Douyin')).toBeTruthy(); expect(within(spreadsheetCard).getByText('615.53')).toBeTruthy(); expect(within(spreadsheetCard).getByText('sales_date missing in 2,566 rows')).toBeTruthy(); expect(screen.getByText('Artifacts')).toBeTruthy(); expect(screen.getByText('F:\\Downloads\\hotel-sales.xls')).toBeTruthy(); }); it('renders generic document-analysis structured data when there is no spreadsheet preview', () => { render( , ); const structuredCard = screen.getByTestId('tool-structured-result'); expect(within(structuredCard).getByText('Structured result')).toBeTruthy(); expect(within(structuredCard).getByText('Confidence')).toBeTruthy(); expect(within(structuredCard).getByText('0.94')).toBeTruthy(); expect(within(structuredCard).getByText('Documents')).toBeTruthy(); expect(within(structuredCard).getByText('master-service-agreement.pdf')).toBeTruthy(); expect(within(structuredCard).getByText('Termination')).toBeTruthy(); expect(within(structuredCard).getByText('Needs review')).toBeTruthy(); }); it('renders dedicated search result cards for search-family tool results', () => { render( , ); const searchCard = screen.getByTestId('tool-search-results'); expect(within(searchCard).getByText('Search overview')).toBeTruthy(); expect(within(searchCard).getByText('Provider: brave')).toBeTruthy(); expect(within(searchCard).getByText('2 results')).toBeTruthy(); expect(within(searchCard).getByText('Query')).toBeTruthy(); expect(within(searchCard).getByText('hotel revenue trends')).toBeTruthy(); expect(within(searchCard).getByText('Answer')).toBeTruthy(); expect(within(searchCard).getByText('Revenue continues to improve across the sector.')).toBeTruthy(); expect(within(searchCard).getByText('Hotel Revenue Trends Q1')).toBeTruthy(); expect(within(searchCard).getByText('https://example.com/revenue-q1')).toBeTruthy(); expect(within(searchCard).getByText('Source: Example News')).toBeTruthy(); expect(within(searchCard).getByText('Score: 0.9821')).toBeTruthy(); }); it('renders install commands in the search result card for command-style tool results', () => { render( , ); const searchCard = screen.getByTestId('tool-search-results'); expect(within(searchCard).getByText('react-best-practices')).toBeTruthy(); expect(within(searchCard).getByText('Install command')).toBeTruthy(); expect(within(searchCard).getByText('npx skills add vercel-labs/agent-skills@react-best-practices -g -y')).toBeTruthy(); }); });