dify/web/app/components/workflow/comment/mention-input.spec.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

176 lines
5.1 KiB
TypeScript

import type { UserProfile } from '@/app/components/workflow/comment/types'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { useState } from 'react'
import { MentionInput } from './mention-input'
const mockFetchMentionableUsers = vi.hoisted(() => vi.fn())
const mockSetMentionableUsersLoading = vi.hoisted(() => vi.fn())
const mockSetMentionableUsersCache = vi.hoisted(() => vi.fn())
const mentionStoreState = vi.hoisted(() => ({
mentionableUsersCache: {} as Record<string, UserProfile[]>,
mentionableUsersLoading: {} as Record<string, boolean>,
setMentionableUsersLoading: (appId: string, loading: boolean) => {
mockSetMentionableUsersLoading(appId, loading)
mentionStoreState.mentionableUsersLoading[appId] = loading
},
setMentionableUsersCache: (appId: string, users: UserProfile[]) => {
mockSetMentionableUsersCache(appId, users)
mentionStoreState.mentionableUsersCache[appId] = users
},
}))
vi.mock('@/next/navigation', () => ({
useParams: () => ({ appId: 'app-1' }),
}))
vi.mock('@/service/client', () => ({
consoleClient: {
apps: {
byAppId: {
workflow: {
comments: {
mentionUsers: {
get: (...args: unknown[]) => mockFetchMentionableUsers(...args),
},
},
},
},
},
},
}))
vi.mock('../store', () => ({
useStore: (selector: (state: typeof mentionStoreState) => unknown) => selector(mentionStoreState),
useWorkflowStore: () => ({
getState: () => mentionStoreState,
}),
}))
vi.mock('@langgenius/dify-ui/avatar', () => ({
Avatar: ({ name }: { name: string }) => <div data-testid="mention-avatar">{name}</div>,
}))
const mentionUsers: UserProfile[] = [
{
id: 'user-2',
name: 'Alice',
email: 'alice@example.com',
avatar_url: 'alice.png',
},
{
id: 'user-3',
name: 'Bob',
email: 'bob@example.com',
avatar_url: 'bob.png',
},
]
function ControlledMentionInput({
onSubmit,
}: {
onSubmit: (content: string, mentionedUserIds: string[]) => void
}) {
const [value, setValue] = useState('')
return <MentionInput value={value} onChange={setValue} onSubmit={onSubmit} />
}
describe('MentionInput', () => {
beforeEach(() => {
vi.clearAllMocks()
mentionStoreState.mentionableUsersCache = {}
mentionStoreState.mentionableUsersLoading = {}
mockFetchMentionableUsers.mockResolvedValue({ users: mentionUsers })
})
it('loads mentionable users when cache is empty', async () => {
render(<MentionInput value="" onChange={vi.fn()} onSubmit={vi.fn()} />)
await waitFor(() => {
expect(mockFetchMentionableUsers).toHaveBeenCalledWith({
params: { app_id: 'app-1' },
})
})
expect(mockSetMentionableUsersLoading).toHaveBeenCalledWith('app-1', true)
expect(mockSetMentionableUsersCache).toHaveBeenCalledWith('app-1', mentionUsers)
expect(mockSetMentionableUsersLoading).toHaveBeenCalledWith('app-1', false)
})
it('selects a mention and submits with mentioned user ids', async () => {
mentionStoreState.mentionableUsersCache['app-1'] = mentionUsers
const onSubmit = vi.fn()
render(<ControlledMentionInput onSubmit={onSubmit} />)
const textarea = screen.getByPlaceholderText(
'workflow.comments.placeholder.add',
) as HTMLTextAreaElement
textarea.focus()
textarea.setSelectionRange(4, 4)
fireEvent.change(textarea, { target: { value: '@Ali' } })
await waitFor(() => {
expect(screen.getByText('alice@example.com')).toBeInTheDocument()
})
fireEvent.click(screen.getByText('alice@example.com'))
fireEvent.change(textarea, { target: { value: '@Alice hi' } })
fireEvent.keyDown(textarea, { key: 'Enter' })
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith('@Alice hi', ['user-2'])
})
})
it('supports editing mode cancel and save actions', async () => {
mentionStoreState.mentionableUsersCache['app-1'] = mentionUsers
const onSubmit = vi.fn()
const onCancel = vi.fn()
render(
<MentionInput
value=" updated reply "
onChange={vi.fn()}
onSubmit={onSubmit}
onCancel={onCancel}
isEditing
/>,
)
fireEvent.click(screen.getByText('common.operation.cancel'))
expect(onCancel).toHaveBeenCalledTimes(1)
fireEvent.click(screen.getByText('common.operation.save'))
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith('updated reply', [])
})
})
it('focuses the textarea at the end when autoFocus is enabled', () => {
vi.useFakeTimers()
try {
mentionStoreState.mentionableUsersCache['app-1'] = mentionUsers
const { unmount } = render(
<MentionInput value="draft" onChange={vi.fn()} onSubmit={vi.fn()} autoFocus />,
)
const textarea = screen.getByPlaceholderText(
'workflow.comments.placeholder.add',
) as HTMLTextAreaElement
act(() => {
vi.runOnlyPendingTimers()
})
expect(document.activeElement).toBe(textarea)
expect(textarea.selectionStart).toBe(5)
expect(textarea.selectionEnd).toBe(5)
unmount()
} finally {
vi.useRealTimers()
}
})
})