import { act, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { ChatContextProvider } from '@/app/components/base/chat/chat/context' import ThinkBlock from './think-block' // Mock react-i18next vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => { const translations: Record = { 'chat.thinking': 'Thinking...', 'chat.thought': 'Thought', } return translations[key] || key }, }), })) // Helper to wrap component with ChatContextProvider const renderWithContext = ( children: React.ReactNode, isResponding: boolean = true, ) => { return render( {children} , ) } describe('ThinkBlock', () => { beforeEach(() => { vi.clearAllMocks() vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) describe('Rendering', () => { it('should render regular details element when data-think is false', () => { render(

Regular content

, ) expect(screen.getByText('Regular content')).toBeInTheDocument() }) it('should render think block with thinking state when data-think is true', () => { renderWithContext(

Thinking content

, true, ) expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() expect(screen.getByText('Thinking content')).toBeInTheDocument() }) it('should render thought state when content has ENDTHINKFLAG', () => { renderWithContext(

Completed thinking[ENDTHINKFLAG]

, true, ) expect(screen.getByText(/Thought/)).toBeInTheDocument() }) }) describe('Timer behavior', () => { it('should update elapsed time while thinking', () => { renderWithContext(

Thinking...

, true, ) // Initial state should show 0.0s expect(screen.getByText(/\(0\.0s\)/)).toBeInTheDocument() // Advance timer by 500ms and run pending timers act(() => { vi.advanceTimersByTime(500) }) // Should show approximately 0.5s expect(screen.getByText(/\(0\.5s\)/)).toBeInTheDocument() }) it('should stop timer when isResponding becomes false', () => { const { rerender } = render(

Thinking content

, ) // Verify initial thinking state expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() // Advance timer act(() => { vi.advanceTimersByTime(1000) }) // Simulate user clicking stop (isResponding becomes false) rerender(

Thinking content

, ) // Should now show "Thought" instead of "Thinking..." expect(screen.getByText(/Thought/)).toBeInTheDocument() }) it('should NOT stop timer when isResponding is undefined (outside ChatContextProvider)', () => { // Render without ChatContextProvider render(

Content without ENDTHINKFLAG

, ) // Initial state should show "Thinking..." expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() // Advance timer act(() => { vi.advanceTimersByTime(2000) }) // Timer should still be running (showing "Thinking..." not "Thought") expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() expect(screen.getByText(/\(2\.0s\)/)).toBeInTheDocument() }) }) describe('ENDTHINKFLAG handling', () => { it('should remove ENDTHINKFLAG from displayed content', () => { renderWithContext(

Content[ENDTHINKFLAG]

, true, ) expect(screen.getByText('Content')).toBeInTheDocument() expect(screen.queryByText('[ENDTHINKFLAG]')).not.toBeInTheDocument() }) it('should detect ENDTHINKFLAG in nested children', () => { renderWithContext(
Nested content[ENDTHINKFLAG]
, true, ) // Should show "Thought" since ENDTHINKFLAG is present expect(screen.getByText(/Thought/)).toBeInTheDocument() }) it('should detect ENDTHINKFLAG in array children', () => { renderWithContext( {['Part 1', 'Part 2[ENDTHINKFLAG]']} , true, ) expect(screen.getByText(/Thought/)).toBeInTheDocument() }) }) describe('Edge cases', () => { it('should handle empty children', () => { renderWithContext( , true, ) expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() }) it('should handle null children gracefully', () => { renderWithContext( {null} , true, ) expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() }) }) })