mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 21:28:25 +08:00
test(chat): enhance unit tests for chat hooks and answer component visibility
This commit is contained in:
parent
45b0751826
commit
a25e3b66c1
@ -1836,6 +1836,82 @@ describe('useChatWithHistory', () => {
|
||||
expect(messageWithFiles?.children?.[0]?.message_files).toHaveLength(1)
|
||||
expect(messageWithFiles?.children?.[0]?.agent_thoughts?.[0]?.message_files).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should pass through workflow_run_id from item and created_at', async () => {
|
||||
const listData = createConversationData({
|
||||
data: [createConversationItem({ id: 'conversation-1' })],
|
||||
})
|
||||
mockFetchConversations.mockResolvedValue(listData)
|
||||
mockFetchChatList.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
id: 'msg-running',
|
||||
query: 'Running query',
|
||||
answer: 'Running answer',
|
||||
message_files: [],
|
||||
feedback: null,
|
||||
retriever_resources: [],
|
||||
agent_thoughts: null,
|
||||
parent_message_id: null,
|
||||
inputs: {},
|
||||
status: 'normal',
|
||||
workflow_run_id: 'wf-direct-id',
|
||||
created_at: 1700000000,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const { result } = await renderWithClient(() => useChatWithHistory())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result!.current.appPrevChatTree.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
const answerNode = result!.current.appPrevChatTree[0]?.children?.[0]
|
||||
expect(answerNode?.workflow_run_id).toBe('wf-direct-id')
|
||||
expect(answerNode?.created_at).toBe(1700000000)
|
||||
})
|
||||
|
||||
it('should prefer item.workflow_run_id over extra_contents workflow_run_id', async () => {
|
||||
const listData = createConversationData({
|
||||
data: [createConversationItem({ id: 'conversation-1' })],
|
||||
})
|
||||
mockFetchConversations.mockResolvedValue(listData)
|
||||
mockFetchChatList.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
id: 'msg-both',
|
||||
query: 'Both query',
|
||||
answer: 'Both answer',
|
||||
message_files: [],
|
||||
feedback: null,
|
||||
retriever_resources: [],
|
||||
agent_thoughts: null,
|
||||
parent_message_id: null,
|
||||
inputs: {},
|
||||
status: 'paused',
|
||||
workflow_run_id: 'wf-item-level',
|
||||
extra_contents: [
|
||||
{
|
||||
type: 'human_input',
|
||||
submitted: false,
|
||||
form_definition: { fields: [] },
|
||||
workflow_run_id: 'wf-extra-level',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const { result } = await renderWithClient(() => useChatWithHistory())
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result!.current.appPrevChatTree.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
const answerNode = result!.current.appPrevChatTree[0]?.children?.[0]
|
||||
expect(answerNode?.workflow_run_id).toBe('wf-item-level')
|
||||
})
|
||||
})
|
||||
|
||||
// Scenario: newConversation merge replaces existing conversation item when id already exists.
|
||||
|
||||
@ -1292,7 +1292,7 @@ describe('useChat', () => {
|
||||
})
|
||||
|
||||
expect(sseGet).toHaveBeenCalledWith(
|
||||
'/workflow/wr-reconnect/events?include_state_snapshot=true',
|
||||
'/workflow/wr-reconnect/events?include_state_snapshot=true&replay=true',
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
)
|
||||
@ -1540,6 +1540,89 @@ describe('useChat', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('onWorkflowStarted re-enables responding in handleSend', () => {
|
||||
it('should set isResponding back to true when onWorkflowStarted fires after stop', () => {
|
||||
let callbacks: HookCallbacks
|
||||
vi.mocked(ssePost).mockImplementation(async (_url, _params, options) => {
|
||||
callbacks = options as HookCallbacks
|
||||
})
|
||||
|
||||
const stopChat = vi.fn()
|
||||
const { result } = renderHook(() => useChat(undefined, undefined, undefined, stopChat))
|
||||
|
||||
act(() => {
|
||||
result.current.handleSend('test-url', { query: 'workflow restart' }, {})
|
||||
})
|
||||
expect(result.current.isResponding).toBe(true)
|
||||
|
||||
act(() => {
|
||||
callbacks.onWorkflowStarted({ workflow_run_id: 'wr-1', task_id: 't-1' })
|
||||
callbacks.onData('part', true, { messageId: 'm-1', conversationId: 'c-1', taskId: 't-1' })
|
||||
})
|
||||
|
||||
act(() => {
|
||||
result.current.handleStop()
|
||||
})
|
||||
expect(result.current.isResponding).toBe(false)
|
||||
|
||||
act(() => {
|
||||
callbacks.onWorkflowStarted({ workflow_run_id: 'wr-2', task_id: 't-2' })
|
||||
})
|
||||
expect(result.current.isResponding).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('abortInflightRequests on unmount', () => {
|
||||
it('should abort all in-flight requests when the hook unmounts', () => {
|
||||
let callbacks: HookCallbacks
|
||||
vi.mocked(ssePost).mockImplementation(async (_url, _params, options) => {
|
||||
callbacks = options as HookCallbacks
|
||||
})
|
||||
|
||||
const workflowAbort = createAbortControllerMock()
|
||||
const conversationAbort = createAbortControllerMock()
|
||||
const suggestedAbort = createAbortControllerMock()
|
||||
|
||||
const config = { suggested_questions_after_answer: { enabled: true } }
|
||||
const onGetConversationMessages = vi.fn().mockImplementation(async (_id: string, setAbort: (ac: AbortController) => void) => {
|
||||
setAbort(conversationAbort)
|
||||
return {
|
||||
data: [{
|
||||
id: 'm-1',
|
||||
answer: 'a',
|
||||
message: [{ role: 'assistant', text: 'a' }],
|
||||
created_at: Date.now(),
|
||||
answer_tokens: 1,
|
||||
message_tokens: 1,
|
||||
provider_response_latency: 0.1,
|
||||
inputs: {},
|
||||
query: 'q',
|
||||
}],
|
||||
}
|
||||
})
|
||||
const onGetSuggestedQuestions = vi.fn().mockImplementation(async (_id: string, setAbort: (ac: AbortController) => void) => {
|
||||
setAbort(suggestedAbort)
|
||||
return { data: [] }
|
||||
})
|
||||
|
||||
const { result, unmount } = renderHook(() => useChat(config as ChatConfig))
|
||||
|
||||
act(() => {
|
||||
result.current.handleSend('test-url', { query: 'unmount' }, {
|
||||
onGetConversationMessages,
|
||||
onGetSuggestedQuestions,
|
||||
})
|
||||
})
|
||||
act(() => {
|
||||
callbacks.getAbortController(workflowAbort)
|
||||
})
|
||||
|
||||
unmount()
|
||||
|
||||
expect(workflowAbort.abort).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('annotations and siblings', () => {
|
||||
const prevChatTree = [{
|
||||
id: 'q-1',
|
||||
|
||||
@ -143,6 +143,67 @@ describe('Answer Component', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('ContentSwitch visibility while responding', () => {
|
||||
it('should hide ContentSwitch when responding in non-human-inputs layout', () => {
|
||||
render(
|
||||
<Answer
|
||||
{...defaultProps}
|
||||
responding={true}
|
||||
item={{
|
||||
...defaultProps.item,
|
||||
siblingCount: 3,
|
||||
siblingIndex: 1,
|
||||
prevSibling: 'msg-0',
|
||||
nextSibling: 'msg-2',
|
||||
} as unknown as ChatItem}
|
||||
switchSibling={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide ContentSwitch when responding in human-inputs layout with content', () => {
|
||||
render(
|
||||
<Answer
|
||||
{...defaultProps}
|
||||
responding={true}
|
||||
item={{
|
||||
...defaultProps.item,
|
||||
content: 'partial response',
|
||||
siblingCount: 3,
|
||||
siblingIndex: 1,
|
||||
prevSibling: 'msg-0',
|
||||
nextSibling: 'msg-2',
|
||||
humanInputFormDataList: [{ id: 'form1' }],
|
||||
} as unknown as ChatItem}
|
||||
switchSibling={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show ContentSwitch when not responding with siblings', () => {
|
||||
render(
|
||||
<Answer
|
||||
{...defaultProps}
|
||||
responding={false}
|
||||
item={{
|
||||
...defaultProps.item,
|
||||
siblingCount: 3,
|
||||
siblingIndex: 1,
|
||||
prevSibling: 'msg-0',
|
||||
nextSibling: 'msg-2',
|
||||
} as unknown as ChatItem}
|
||||
switchSibling={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByRole('button', { name: 'Previous' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Interactions', () => {
|
||||
it('should handle switch sibling', () => {
|
||||
const mockSwitchSibling = vi.fn()
|
||||
|
||||
@ -834,6 +834,34 @@ describe('useEmbeddedChatbot', () => {
|
||||
const question = chatList.find((m: unknown) => (m as Record<string, unknown>).id === 'question-msg-no-files')
|
||||
expect(question).toBeDefined()
|
||||
})
|
||||
|
||||
it('should pass through workflow_run_id and created_at from message items', async () => {
|
||||
localStorage.setItem(CONVERSATION_ID_INFO, JSON.stringify({ 'app-1': { DEFAULT: 'conversation-1' } }))
|
||||
mockFetchConversations.mockResolvedValue(
|
||||
createConversationData({ data: [createConversationItem({ id: 'conversation-1' })] }),
|
||||
)
|
||||
mockFetchChatList.mockResolvedValue({
|
||||
data: [{
|
||||
id: 'msg-wf',
|
||||
query: 'Running workflow',
|
||||
answer: 'Partial',
|
||||
workflow_run_id: 'wf-embedded-1',
|
||||
created_at: 1700000000,
|
||||
}],
|
||||
})
|
||||
|
||||
const { result } = await renderWithClient(() => useEmbeddedChatbot(AppSourceType.webApp))
|
||||
await waitFor(() => expect(result.current.appPrevChatList.length).toBeGreaterThan(0), { timeout: 3000 })
|
||||
|
||||
const questionNode = result.current.appPrevChatList.find(
|
||||
(m: unknown) => (m as Record<string, unknown>).id === 'question-msg-wf',
|
||||
) as Record<string, unknown> | undefined
|
||||
expect(questionNode).toBeDefined()
|
||||
const answerNode = (questionNode!.children as Record<string, unknown>[])?.[0]
|
||||
expect(answerNode).toBeDefined()
|
||||
expect(answerNode!.workflow_run_id).toBe('wf-embedded-1')
|
||||
expect(answerNode!.created_at).toBe(1700000000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('currentConversationItem from pinned list', () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user