mirror of
https://github.com/langgenius/dify.git
synced 2026-03-01 12:55:13 +00:00
fix(web): harden ime composition submit handling
This commit is contained in:
@@ -245,6 +245,50 @@ describe('Question component', () => {
|
||||
expect(onRegenerate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not confirm editing when Enter is pressed during IME composition', () => {
|
||||
const onRegenerate = vi.fn() as unknown as OnRegenerate
|
||||
|
||||
renderWithProvider(makeItem(), onRegenerate)
|
||||
|
||||
fireEvent.click(screen.getByTestId('edit-btn'))
|
||||
const textbox = screen.getByRole('textbox')
|
||||
|
||||
fireEvent.compositionStart(textbox)
|
||||
fireEvent.keyDown(textbox, { key: 'Enter', code: 'Enter' })
|
||||
|
||||
expect(onRegenerate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should keep Enter suppressed if a new composition starts before previous composition-end timer finishes', () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
try {
|
||||
const onRegenerate = vi.fn() as unknown as OnRegenerate
|
||||
renderWithProvider(makeItem(), onRegenerate)
|
||||
|
||||
fireEvent.click(screen.getByTestId('edit-btn'))
|
||||
const textbox = screen.getByRole('textbox')
|
||||
|
||||
fireEvent.compositionStart(textbox)
|
||||
fireEvent.compositionEnd(textbox)
|
||||
fireEvent.compositionStart(textbox)
|
||||
|
||||
vi.advanceTimersByTime(50)
|
||||
|
||||
fireEvent.keyDown(textbox, { key: 'Enter', code: 'Enter' })
|
||||
expect(onRegenerate).not.toHaveBeenCalled()
|
||||
|
||||
fireEvent.compositionEnd(textbox)
|
||||
vi.advanceTimersByTime(50)
|
||||
|
||||
fireEvent.keyDown(textbox, { key: 'Enter', code: 'Enter' })
|
||||
expect(onRegenerate).toHaveBeenCalledTimes(1)
|
||||
}
|
||||
finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('should switch siblings when prev/next buttons are clicked', async () => {
|
||||
const user = userEvent.setup()
|
||||
const switchSibling = vi.fn()
|
||||
|
||||
@@ -57,6 +57,7 @@ const Question: FC<QuestionProps> = ({
|
||||
const [contentWidth, setContentWidth] = useState(0)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const isComposingRef = useRef(false)
|
||||
const compositionEndTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
setIsEditing(true)
|
||||
@@ -84,15 +85,26 @@ const Question: FC<QuestionProps> = ({
|
||||
handleResend()
|
||||
}, [handleResend])
|
||||
|
||||
const handleCompositionStart = useCallback(() => {
|
||||
isComposingRef.current = true
|
||||
const clearCompositionEndTimer = useCallback(() => {
|
||||
if (!compositionEndTimerRef.current)
|
||||
return
|
||||
|
||||
clearTimeout(compositionEndTimerRef.current)
|
||||
compositionEndTimerRef.current = null
|
||||
}, [])
|
||||
|
||||
const handleCompositionStart = useCallback(() => {
|
||||
clearCompositionEndTimer()
|
||||
isComposingRef.current = true
|
||||
}, [clearCompositionEndTimer])
|
||||
|
||||
const handleCompositionEnd = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
clearCompositionEndTimer()
|
||||
compositionEndTimerRef.current = setTimeout(() => {
|
||||
isComposingRef.current = false
|
||||
compositionEndTimerRef.current = null
|
||||
}, 50)
|
||||
}, [])
|
||||
}, [clearCompositionEndTimer])
|
||||
|
||||
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
|
||||
if (direction === 'prev') {
|
||||
@@ -122,6 +134,12 @@ const Question: FC<QuestionProps> = ({
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearCompositionEndTimer()
|
||||
}
|
||||
}, [clearCompositionEndTimer])
|
||||
|
||||
return (
|
||||
<div className="mb-2 flex justify-end last:mb-0">
|
||||
<div className={cn('group relative mr-4 flex max-w-full items-start overflow-x-hidden pl-14', isEditing && 'flex-1')}>
|
||||
|
||||
Reference in New Issue
Block a user