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-provider'
import ThinkBlock from '../think-block'
// Mock react-i18next
// 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(/chat\.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(/chat\.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(/chat\.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(/chat\.thought/)).toBeInTheDocument()
})
it('should stop timer when isResponding is undefined (historical conversation outside active response)', () => {
// Render without ChatContextProvider — simulates historical conversation
render(
Content without ENDTHINKFLAG
,
)
// Timer should be stopped immediately — isResponding undefined means not in active response
expect(screen.getByText(/chat\.thought/)).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(/chat\.thought/)).toBeInTheDocument()
})
it('should detect ENDTHINKFLAG in array children', () => {
renderWithContext(
{['Part 1', 'Part 2[ENDTHINKFLAG]']},
true,
)
expect(screen.getByText(/chat\.thought/)).toBeInTheDocument()
})
})
describe('Edge cases', () => {
it('should handle empty children', () => {
renderWithContext(, true)
expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument()
})
it('should handle null children gracefully', () => {
renderWithContext({null}, true)
expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument()
})
})
})