mirror of
https://github.com/langgenius/dify.git
synced 2026-04-21 23:38:53 +08:00
202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
import { sleep } from '@/utils'
|
|
import AutoHeightTextarea from './index'
|
|
|
|
vi.mock('@/utils', async () => {
|
|
const actual = await vi.importActual('@/utils')
|
|
return {
|
|
...actual,
|
|
sleep: vi.fn(),
|
|
}
|
|
})
|
|
|
|
describe('AutoHeightTextarea', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', () => {
|
|
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
|
|
const textarea = container.querySelector('textarea')
|
|
expect(textarea).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render with placeholder when value is empty', () => {
|
|
render(<AutoHeightTextarea placeholder="Enter text" value="" onChange={vi.fn()} />)
|
|
expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render with value', () => {
|
|
render(<AutoHeightTextarea value="Hello World" onChange={vi.fn()} />)
|
|
const textarea = screen.getByDisplayValue('Hello World')
|
|
expect(textarea).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Props', () => {
|
|
it('should apply custom className to textarea', () => {
|
|
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} className="custom-class" />)
|
|
const textarea = container.querySelector('textarea')
|
|
expect(textarea).toHaveClass('custom-class')
|
|
})
|
|
|
|
it('should apply custom wrapperClassName to wrapper div', () => {
|
|
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} wrapperClassName="wrapper-class" />)
|
|
const wrapper = container.querySelector('div.relative')
|
|
expect(wrapper).toHaveClass('wrapper-class')
|
|
})
|
|
|
|
it('should apply minHeight and maxHeight styles to hidden div', () => {
|
|
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} minHeight={50} maxHeight={200} />)
|
|
const hiddenDiv = container.querySelector('div.invisible')
|
|
expect(hiddenDiv).toHaveStyle({ minHeight: '50px', maxHeight: '200px' })
|
|
})
|
|
|
|
it('should use default minHeight and maxHeight when not provided', () => {
|
|
const { container } = render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
|
|
const hiddenDiv = container.querySelector('div.invisible')
|
|
expect(hiddenDiv).toHaveStyle({ minHeight: '36px', maxHeight: '96px' })
|
|
})
|
|
|
|
it('should set autoFocus on textarea', () => {
|
|
const focusSpy = vi.spyOn(HTMLTextAreaElement.prototype, 'focus')
|
|
render(<AutoHeightTextarea value="" onChange={vi.fn()} autoFocus />)
|
|
expect(focusSpy).toHaveBeenCalled()
|
|
focusSpy.mockRestore()
|
|
})
|
|
})
|
|
|
|
describe('User Interactions', () => {
|
|
it('should call onChange when textarea value changes', () => {
|
|
const handleChange = vi.fn()
|
|
render(<AutoHeightTextarea value="" onChange={handleChange} />)
|
|
const textarea = screen.getByRole('textbox')
|
|
|
|
fireEvent.change(textarea, { target: { value: 'new value' } })
|
|
|
|
expect(handleChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call onKeyDown when key is pressed', () => {
|
|
const handleKeyDown = vi.fn()
|
|
render(<AutoHeightTextarea value="" onChange={vi.fn()} onKeyDown={handleKeyDown} />)
|
|
const textarea = screen.getByRole('textbox')
|
|
|
|
fireEvent.keyDown(textarea, { key: 'Enter' })
|
|
|
|
expect(handleKeyDown).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call onKeyUp when key is released', () => {
|
|
const handleKeyUp = vi.fn()
|
|
render(<AutoHeightTextarea value="" onChange={vi.fn()} onKeyUp={handleKeyUp} />)
|
|
const textarea = screen.getByRole('textbox')
|
|
|
|
fireEvent.keyUp(textarea, { key: 'Enter' })
|
|
|
|
expect(handleKeyUp).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle empty string value', () => {
|
|
render(<AutoHeightTextarea value="" onChange={vi.fn()} />)
|
|
const textarea = screen.getByRole('textbox')
|
|
expect(textarea).toHaveValue('')
|
|
})
|
|
|
|
it('should handle whitespace-only value', () => {
|
|
render(<AutoHeightTextarea value=" " onChange={vi.fn()} />)
|
|
const textarea = screen.getByRole('textbox')
|
|
expect(textarea).toHaveValue(' ')
|
|
})
|
|
|
|
it('should handle very long text (>10000 chars)', () => {
|
|
const longText = 'a'.repeat(10001)
|
|
render(<AutoHeightTextarea value={longText} onChange={vi.fn()} />)
|
|
const textarea = screen.getByDisplayValue(longText)
|
|
expect(textarea).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle newlines in value', () => {
|
|
const textWithNewlines = 'line1\nline2\nline3'
|
|
render(<AutoHeightTextarea value={textWithNewlines} onChange={vi.fn()} />)
|
|
const textarea = screen.getByRole('textbox')
|
|
expect(textarea).toHaveValue(textWithNewlines)
|
|
})
|
|
|
|
it('should handle special characters in value', () => {
|
|
const specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?'
|
|
render(<AutoHeightTextarea value={specialChars} onChange={vi.fn()} />)
|
|
const textarea = screen.getByDisplayValue(specialChars)
|
|
expect(textarea).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Ref forwarding', () => {
|
|
it('should accept ref and allow focusing', () => {
|
|
const ref = { current: null as HTMLTextAreaElement | null }
|
|
render(<AutoHeightTextarea ref={ref as React.RefObject<HTMLTextAreaElement>} value="" onChange={vi.fn()} />)
|
|
|
|
expect(ref.current).not.toBeNull()
|
|
expect(ref.current?.tagName).toBe('TEXTAREA')
|
|
})
|
|
})
|
|
|
|
describe('controlFocus prop', () => {
|
|
it('should call focus when controlFocus changes', () => {
|
|
const focusSpy = vi.spyOn(HTMLTextAreaElement.prototype, 'focus')
|
|
const { rerender } = render(<AutoHeightTextarea value="" onChange={vi.fn()} controlFocus={1} />)
|
|
|
|
expect(focusSpy).toHaveBeenCalledTimes(1)
|
|
|
|
rerender(<AutoHeightTextarea value="" onChange={vi.fn()} controlFocus={2} />)
|
|
|
|
expect(focusSpy).toHaveBeenCalledTimes(2)
|
|
focusSpy.mockRestore()
|
|
})
|
|
|
|
it('should retry focus recursively when ref is not ready during autoFocus', async () => {
|
|
const delayedRef = {} as React.RefObject<HTMLTextAreaElement>
|
|
let assignedNode: HTMLTextAreaElement | null = null
|
|
let exposedNode: HTMLTextAreaElement | null = null
|
|
|
|
Object.defineProperty(delayedRef, 'current', {
|
|
get: () => exposedNode,
|
|
set: (value: HTMLTextAreaElement | null) => {
|
|
assignedNode = value
|
|
},
|
|
})
|
|
|
|
const sleepMock = vi.mocked(sleep)
|
|
let sleepCalls = 0
|
|
sleepMock.mockImplementation(async () => {
|
|
sleepCalls += 1
|
|
if (sleepCalls === 2)
|
|
exposedNode = assignedNode
|
|
})
|
|
|
|
const focusSpy = vi.spyOn(HTMLTextAreaElement.prototype, 'focus')
|
|
const setSelectionRangeSpy = vi.spyOn(HTMLTextAreaElement.prototype, 'setSelectionRange')
|
|
|
|
render(<AutoHeightTextarea ref={delayedRef} value="" onChange={vi.fn()} autoFocus />)
|
|
|
|
await waitFor(() => {
|
|
expect(sleepMock).toHaveBeenCalledTimes(2)
|
|
expect(focusSpy).toHaveBeenCalled()
|
|
expect(setSelectionRangeSpy).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
focusSpy.mockRestore()
|
|
setSelectionRangeSpy.mockRestore()
|
|
})
|
|
})
|
|
|
|
describe('displayName', () => {
|
|
it('should have displayName set', () => {
|
|
expect(AutoHeightTextarea.displayName).toBe('AutoHeightTextarea')
|
|
})
|
|
})
|
|
})
|