mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 20:09:20 +08:00
test: improve coverage for some test files (#32916)
Signed-off-by: edvatar <88481784+toroleapinc@users.noreply.github.com> Signed-off-by: -LAN- <laipz8200@outlook.com> Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: majiayu000 <1835304752@qq.com> Co-authored-by: Poojan <poojan@infocusp.com> Co-authored-by: sahil-infocusp <73810410+sahil-infocusp@users.noreply.github.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: Pandaaaa906 <ye.pandaaaa906@gmail.com> Co-authored-by: Asuka Minato <i@asukaminato.eu.org> Co-authored-by: heyszt <270985384@qq.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Ijas <ijas.ahmd.ap@gmail.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: 木之本澪 <kinomotomiovo@gmail.com> Co-authored-by: KinomotoMio <200703522+KinomotoMio@users.noreply.github.com> Co-authored-by: 不做了睡大觉 <64798754+stakeswky@users.noreply.github.com> Co-authored-by: User <user@example.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: edvatar <88481784+toroleapinc@users.noreply.github.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: Leilei <138381132+Inlei@users.noreply.github.com> Co-authored-by: HaKu <104669497+haku-ink@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: wangxiaolei <fatelei@gmail.com> Co-authored-by: Varun Chawla <34209028+veeceey@users.noreply.github.com> Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: tda <95275462+tda1017@users.noreply.github.com> Co-authored-by: root <root@DESKTOP-KQLO90N> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Co-authored-by: Niels Kaspers <153818647+nielskaspers@users.noreply.github.com> Co-authored-by: hj24 <mambahj24@gmail.com> Co-authored-by: Tyson Cung <45380903+tysoncung@users.noreply.github.com> Co-authored-by: Stephen Zhou <hi@hyoban.cc> Co-authored-by: FFXN <31929997+FFXN@users.noreply.github.com> Co-authored-by: slegarraga <64795732+slegarraga@users.noreply.github.com> Co-authored-by: 99 <wh2099@pm.me> Co-authored-by: Br1an <932039080@qq.com> Co-authored-by: L1nSn0w <l1nsn0w@qq.com> Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai> Co-authored-by: akkoaya <151345394+akkoaya@users.noreply.github.com> Co-authored-by: 盐粒 Yanli <yanli@dify.ai> Co-authored-by: lif <1835304752@qq.com> Co-authored-by: weiguang li <codingpunk@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: HanWenbo <124024253+hwb96@users.noreply.github.com> Co-authored-by: Coding On Star <447357187@qq.com> Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: Stable Genius <stablegenius043@gmail.com> Co-authored-by: Stable Genius <259448942+stablegenius49@users.noreply.github.com> Co-authored-by: ふるい <46769295+Echo0ff@users.noreply.github.com> Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
This commit is contained in:
@@ -4,12 +4,11 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { AppData, AppMeta, ConversationItem } from '@/models/share'
|
||||
import type { HumanInputFormData } from '@/types/workflow'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import {
|
||||
fetchSuggestedQuestions,
|
||||
stopChatMessageResponding,
|
||||
submitHumanInputForm,
|
||||
} from '@/service/share'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { useChat } from '../../chat/hooks'
|
||||
@@ -501,6 +500,34 @@ describe('ChatWrapper', () => {
|
||||
expect(handleSwitchSibling).toHaveBeenCalledWith('1', expect.any(Object))
|
||||
})
|
||||
|
||||
it('should call fetchSuggestedQuestions from workflow resumption options callback', () => {
|
||||
const handleSwitchSibling = vi.fn()
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
chatList: [],
|
||||
handleSwitchSibling,
|
||||
} as unknown as ChatHookReturn)
|
||||
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
appPrevChatTree: [{
|
||||
id: 'resume-node',
|
||||
content: 'Paused answer',
|
||||
isAnswer: true,
|
||||
workflow_run_id: 'workflow-1',
|
||||
humanInputFormDataList: [{ label: 'resume' }] as unknown as HumanInputFormData[],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
|
||||
render(<ChatWrapper />)
|
||||
|
||||
expect(handleSwitchSibling).toHaveBeenCalledWith('resume-node', expect.any(Object))
|
||||
const resumeOptions = handleSwitchSibling.mock.calls[0][1]
|
||||
resumeOptions.onGetSuggestedQuestions('response-from-resume')
|
||||
expect(fetchSuggestedQuestions).toHaveBeenCalledWith('response-from-resume', 'webApp', 'test-app-id')
|
||||
})
|
||||
|
||||
it('should handle workflow resumption with nested children (DFS)', () => {
|
||||
const handleSwitchSibling = vi.fn()
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
@@ -760,6 +787,47 @@ describe('ChatWrapper', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle human input form submission for web app', async () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
isInstalledApp: false,
|
||||
})
|
||||
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
chatList: [
|
||||
{ id: 'q1', content: 'Question' },
|
||||
{
|
||||
id: 'a1',
|
||||
isAnswer: true,
|
||||
content: '',
|
||||
humanInputFormDataList: [{
|
||||
id: 'node1',
|
||||
form_id: 'form1',
|
||||
form_token: 'token-web-1',
|
||||
node_id: 'node1',
|
||||
node_title: 'Node Web 1',
|
||||
display_in_ui: true,
|
||||
form_content: '{{#$output.test#}}',
|
||||
inputs: [{ variable: 'test', label: 'Test', type: 'paragraph', required: true, output_variable_name: 'test', default: { type: 'text', value: '' } }],
|
||||
actions: [{ id: 'run', title: 'Run', button_style: 'primary' }],
|
||||
}] as unknown as HumanInputFormData[],
|
||||
},
|
||||
],
|
||||
} as unknown as ChatHookReturn)
|
||||
|
||||
render(<ChatWrapper />)
|
||||
expect(await screen.findByText('Node Web 1')).toBeInTheDocument()
|
||||
|
||||
const input = screen.getAllByRole('textbox').find(el => el.closest('.chat-answer-container')) || screen.getAllByRole('textbox')[0]
|
||||
fireEvent.change(input, { target: { value: 'web-test' } })
|
||||
fireEvent.click(screen.getByText('Run'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(submitHumanInputForm).toHaveBeenCalledWith('token-web-1', expect.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
it('should filter opening statement in new conversation with single item', () => {
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
@@ -888,8 +956,16 @@ describe('ChatWrapper', () => {
|
||||
})
|
||||
|
||||
it('should render answer icon when configured', () => {
|
||||
const appDataWithAnswerIcon = {
|
||||
site: {
|
||||
...mockAppData.site,
|
||||
use_icon_as_answer_icon: true,
|
||||
},
|
||||
} as unknown as AppData
|
||||
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
appData: appDataWithAnswerIcon,
|
||||
} as ChatWithHistoryContextValue)
|
||||
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
@@ -899,6 +975,7 @@ describe('ChatWrapper', () => {
|
||||
|
||||
render(<ChatWrapper />)
|
||||
expect(screen.getByText('Answer')).toBeInTheDocument()
|
||||
expect(screen.getByAltText('answer icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render question icon when user avatar is available', () => {
|
||||
@@ -920,6 +997,26 @@ describe('ChatWrapper', () => {
|
||||
expect(avatar).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should use fallback values for nullable appData, appMeta and user name', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
appData: null as unknown as AppData,
|
||||
appMeta: null as unknown as AppMeta,
|
||||
initUserVariables: {
|
||||
avatar_url: 'https://example.com/avatar-fallback.png',
|
||||
},
|
||||
})
|
||||
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
chatList: [{ id: 'q1', content: 'Question with fallback avatar name' }],
|
||||
} as unknown as ChatHookReturn)
|
||||
|
||||
render(<ChatWrapper />)
|
||||
expect(screen.getByText('Question with fallback avatar name')).toBeInTheDocument()
|
||||
expect(screen.getByAltText('user')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should set handleStop on currentChatInstanceRef', () => {
|
||||
const handleStop = vi.fn()
|
||||
const currentChatInstanceRef = { current: { handleStop: vi.fn() } } as ChatWithHistoryContextValue['currentChatInstanceRef']
|
||||
@@ -1212,20 +1309,45 @@ describe('ChatWrapper', () => {
|
||||
|
||||
it('should handle doRegenerate with editedQuestion', async () => {
|
||||
const handleSend = vi.fn()
|
||||
|
||||
const mockFiles = [
|
||||
{
|
||||
id: 'file-q1',
|
||||
name: 'q1.txt',
|
||||
type: 'text/plain',
|
||||
size: 100,
|
||||
url: 'https://example.com/q1.txt',
|
||||
extension: 'txt',
|
||||
mime_type: 'text/plain',
|
||||
} as unknown as FileEntity,
|
||||
] as FileEntity[]
|
||||
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
chatList: [
|
||||
{ id: 'q1', content: 'Original question', message_files: [] },
|
||||
{ id: 'q1', content: 'Original question', message_files: mockFiles },
|
||||
{ id: 'a1', isAnswer: true, content: 'Answer', parentMessageId: 'q1' },
|
||||
],
|
||||
handleSend,
|
||||
} as unknown as ChatHookReturn)
|
||||
|
||||
const { container } = render(<ChatWrapper />)
|
||||
render(<ChatWrapper />)
|
||||
|
||||
// This would test line 198-200 - the editedQuestion path
|
||||
// The actual regenerate with edited question happens through the UI
|
||||
expect(container).toBeInTheDocument()
|
||||
fireEvent.click(await screen.findByTestId('edit-btn'))
|
||||
const editedTextarea = await screen.findByDisplayValue('Original question')
|
||||
fireEvent.change(editedTextarea, { target: { value: 'Edited question text' } })
|
||||
fireEvent.click(screen.getByTestId('save-edit-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleSend).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
query: 'Edited question text',
|
||||
files: mockFiles,
|
||||
}),
|
||||
expect.any(Object),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle doRegenerate when parentAnswer is not a valid generated answer', async () => {
|
||||
@@ -1692,4 +1814,31 @@ describe('ChatWrapper', () => {
|
||||
// Should not be disabled because it's not required
|
||||
expect(container).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle fallback branches for appParams, appId and empty chat instance ref', async () => {
|
||||
const handleSend = vi.fn()
|
||||
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
appParams: undefined as unknown as ChatConfig,
|
||||
appId: '',
|
||||
currentConversationId: '',
|
||||
currentChatInstanceRef: { current: null } as unknown as ChatWithHistoryContextValue['currentChatInstanceRef'],
|
||||
})
|
||||
|
||||
vi.mocked(useChat).mockReturnValue({
|
||||
...defaultChatHookReturn,
|
||||
handleSend,
|
||||
} as unknown as ChatHookReturn)
|
||||
|
||||
render(<ChatWrapper />)
|
||||
|
||||
const textarea = screen.getByRole('textbox')
|
||||
fireEvent.change(textarea, { target: { value: 'trigger fallback path' } })
|
||||
fireEvent.keyDown(textarea, { key: 'Enter', code: 'Enter', keyCode: 13 })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleSend).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { i18n } from 'i18next'
|
||||
import type { ChatConfig } from '../../types'
|
||||
import type { ChatWithHistoryContextValue } from '../context'
|
||||
import type { AppData, AppMeta, ConversationItem } from '@/models/share'
|
||||
import type { AppData, AppMeta } from '@/models/share'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as ReactI18next from 'react-i18next'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useChatWithHistoryContext } from '../context'
|
||||
import HeaderInMobile from '../header-in-mobile'
|
||||
@@ -80,7 +80,14 @@ vi.mock('@/app/components/base/modal', () => ({
|
||||
|
||||
// Sidebar mock removed to use real component
|
||||
|
||||
const mockAppData = { site: { title: 'Test Chat', chat_color_theme: 'blue' } } as unknown as AppData
|
||||
const mockAppData: AppData = {
|
||||
app_id: 'test-app',
|
||||
custom_config: null,
|
||||
site: {
|
||||
title: 'Test Chat',
|
||||
chat_color_theme: 'blue',
|
||||
},
|
||||
}
|
||||
const defaultContextValue: ChatWithHistoryContextValue = {
|
||||
appData: mockAppData,
|
||||
currentConversationId: '',
|
||||
@@ -104,18 +111,27 @@ const defaultContextValue: ChatWithHistoryContextValue = {
|
||||
currentChatInstanceRef: { current: { handleStop: vi.fn() } } as ChatWithHistoryContextValue['currentChatInstanceRef'],
|
||||
setIsResponding: vi.fn(),
|
||||
setClearChatList: vi.fn(),
|
||||
appParams: { system_parameters: { vision_config: { enabled: false } } } as unknown as ChatConfig,
|
||||
appMeta: {} as AppMeta,
|
||||
appParams: {
|
||||
system_parameters: {
|
||||
audio_file_size_limit: 10,
|
||||
file_size_limit: 10,
|
||||
image_file_size_limit: 10,
|
||||
video_file_size_limit: 10,
|
||||
workflow_file_upload_limit: 10,
|
||||
},
|
||||
more_like_this: { enabled: false },
|
||||
} as ChatConfig,
|
||||
appMeta: { tool_icons: {} } as AppMeta,
|
||||
appPrevChatTree: [],
|
||||
newConversationInputs: {},
|
||||
newConversationInputsRef: { current: {} } as ChatWithHistoryContextValue['newConversationInputsRef'],
|
||||
newConversationInputsRef: { current: {} },
|
||||
appChatListDataLoading: false,
|
||||
chatShouldReloadKey: '',
|
||||
isMobile: true,
|
||||
currentConversationInputs: null,
|
||||
setCurrentConversationInputs: vi.fn(),
|
||||
allInputsHidden: false,
|
||||
conversationRenaming: false, // Added missing property
|
||||
conversationRenaming: false,
|
||||
}
|
||||
|
||||
describe('HeaderInMobile', () => {
|
||||
@@ -134,7 +150,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
})
|
||||
|
||||
render(<HeaderInMobile />)
|
||||
@@ -270,7 +286,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handlePinConversation: handlePin,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
@@ -292,9 +308,9 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleUnpinConversation: handleUnpin,
|
||||
pinnedConversationList: [{ id: '1' }] as unknown as ConversationItem[],
|
||||
pinnedConversationList: [{ id: '1', name: 'Conv 1', inputs: null, introduction: '' }],
|
||||
})
|
||||
|
||||
render(<HeaderInMobile />)
|
||||
@@ -314,7 +330,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleRenameConversation: handleRename,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
@@ -342,7 +358,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleRenameConversation: handleRename,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
@@ -373,7 +389,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleRenameConversation: vi.fn(),
|
||||
conversationRenaming: true, // Loading state
|
||||
pinnedConversationList: [],
|
||||
@@ -396,7 +412,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleDeleteConversation: handleDelete,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
@@ -422,7 +438,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleDeleteConversation: handleDelete,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
@@ -454,7 +470,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: '' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: '', inputs: null, introduction: '' },
|
||||
})
|
||||
|
||||
render(<HeaderInMobile />)
|
||||
@@ -485,16 +501,17 @@ describe('HeaderInMobile', () => {
|
||||
})
|
||||
|
||||
it('should render app icon and title correctly', () => {
|
||||
const appDataWithIcon = {
|
||||
const appDataWithIcon: AppData = {
|
||||
app_id: 'test-app',
|
||||
custom_config: null,
|
||||
site: {
|
||||
title: 'My App',
|
||||
icon: 'emoji',
|
||||
icon_type: 'emoji',
|
||||
icon_url: '',
|
||||
icon_background: '#FF0000',
|
||||
chat_color_theme: 'blue',
|
||||
},
|
||||
} as unknown as AppData
|
||||
}
|
||||
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
@@ -512,7 +529,7 @@ describe('HeaderInMobile', () => {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1' } as unknown as ConversationItem,
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleRenameConversation: handleRename,
|
||||
handleDeleteConversation: handleDelete,
|
||||
pinnedConversationList: [],
|
||||
@@ -524,4 +541,59 @@ describe('HeaderInMobile', () => {
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('share.chat.deleteConversation.title')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should use empty string fallback for delete content translation', async () => {
|
||||
const handleDelete = vi.fn()
|
||||
const useTranslationSpy = vi.spyOn(ReactI18next, 'useTranslation')
|
||||
useTranslationSpy.mockReturnValue({
|
||||
t: (key: string) => key === 'chat.deleteConversation.content' ? '' : key,
|
||||
i18n: {} as unknown as i18n,
|
||||
ready: true,
|
||||
tReady: true,
|
||||
} as unknown as ReturnType<typeof ReactI18next.useTranslation>)
|
||||
|
||||
try {
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: 'Conv 1', inputs: null, introduction: '' },
|
||||
handleDeleteConversation: handleDelete,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
|
||||
render(<HeaderInMobile />)
|
||||
fireEvent.click(await screen.findByText('Conv 1'))
|
||||
fireEvent.click(await screen.findByText(/sidebar\.action\.delete/i))
|
||||
|
||||
expect(await screen.findByRole('button', { name: /common\.operation\.confirm|operation\.confirm/i })).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: /common\.operation\.confirm|operation\.confirm/i }))
|
||||
expect(handleDelete).toHaveBeenCalledWith('1', expect.any(Object))
|
||||
}
|
||||
finally {
|
||||
useTranslationSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('should use empty string fallback for rename modal name', async () => {
|
||||
const handleRename = vi.fn()
|
||||
vi.mocked(useChatWithHistoryContext).mockReturnValue({
|
||||
...defaultContextValue,
|
||||
currentConversationId: '1',
|
||||
currentConversationItem: { id: '1', name: '', inputs: null, introduction: '' },
|
||||
handleRenameConversation: handleRename,
|
||||
pinnedConversationList: [],
|
||||
})
|
||||
|
||||
const { container } = render(<HeaderInMobile />)
|
||||
const operationTrigger = container.querySelector('.system-md-semibold')?.parentElement as HTMLElement
|
||||
fireEvent.click(operationTrigger)
|
||||
fireEvent.click(await screen.findByText(/explore\.sidebar\.action\.rename|sidebar\.action\.rename/i))
|
||||
|
||||
const input = await screen.findByRole('textbox')
|
||||
expect(input).toHaveValue('')
|
||||
|
||||
fireEvent.change(input, { target: { value: 'Renamed from empty' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/i }))
|
||||
expect(handleRename).toHaveBeenCalledWith('1', 'Renamed from empty', expect.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,7 @@ import type { RefObject } from 'react'
|
||||
import type { ChatConfig } from '../../types'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import type { AppConversationData, AppData, AppMeta, ConversationItem } from '@/models/share'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useChatWithHistory } from '../hooks'
|
||||
@@ -113,81 +111,22 @@ describe('ChatWithHistory', () => {
|
||||
vi.mocked(useChatWithHistory).mockReturnValue(defaultHookReturn)
|
||||
})
|
||||
|
||||
it('renders desktop view with expanded sidebar and builds theme', () => {
|
||||
it('renders desktop view with expanded sidebar and builds theme', async () => {
|
||||
vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc)
|
||||
|
||||
render(<ChatWithHistory />)
|
||||
|
||||
// Checks if the desktop elements render correctly
|
||||
// Checks if the desktop elements render correctly
|
||||
// Sidebar real component doesn't have data-testid="sidebar", so we check for its presence via class or content.
|
||||
// Sidebar usually has "New Chat" button or similar.
|
||||
// However, looking at the Sidebar mock it was just a div.
|
||||
// Real Sidebar -> web/app/components/base/chat/chat-with-history/sidebar/index.tsx
|
||||
// It likely has some text or distinct element.
|
||||
// ChatWrapper also removed mock.
|
||||
// Header also removed mock.
|
||||
|
||||
// For now, let's verify some key elements that should be present in these components.
|
||||
// Sidebar: "Explore" or "Chats" or verify navigation structure.
|
||||
// Header: Title or similar.
|
||||
// ChatWrapper: "Start a new chat" or similar.
|
||||
|
||||
// Given the complexity of real components and lack of testIds, we might need to rely on:
|
||||
// 1. Adding testIds to real components (preferred but might be out of scope if I can't touch them? Guidelines say "don't mock base components", but adding testIds is fine).
|
||||
// But I can't see those files right now.
|
||||
// 2. Use getByText for known static content.
|
||||
|
||||
// Let's assume some content based on `mockAppData` title 'Test Chat'.
|
||||
// Header should contain 'Test Chat'.
|
||||
// Check for "Test Chat" - might appear multiple times (header, sidebar, document title etc)
|
||||
// header-in-mobile renders 'Test Chat'.
|
||||
const titles = screen.getAllByText('Test Chat')
|
||||
expect(titles.length).toBeGreaterThan(0)
|
||||
|
||||
// Sidebar should be present.
|
||||
// We can check for a specific element in sidebar, e.g. "New Chat" button if it exists.
|
||||
// Or we can check for the sidebar container class if possible.
|
||||
// Let's look at `index.tsx` logic.
|
||||
// Sidebar is rendered.
|
||||
// Let's try to query by something generic or update to use `container.querySelector`.
|
||||
// But `screen` is better.
|
||||
|
||||
// ChatWrapper is rendered.
|
||||
// It renders "ChatWrapper" text? No, it's the real component now.
|
||||
// Real ChatWrapper renders "Welcome" or chat list.
|
||||
// In `chat-wrapper.spec.tsx`, we saw it renders "Welcome" or "Q1".
|
||||
// Here `defaultHookReturn` returns empty chat list/conversation.
|
||||
// So it might render nothing or empty state?
|
||||
// Let's wait and see what `chat-wrapper.spec.tsx` expectations were.
|
||||
// It expects "Welcome" if `isOpeningStatement` is true.
|
||||
// In `index.spec.tsx` mock hook return:
|
||||
// `currentConversationItem` is undefined.
|
||||
// `conversationList` is [].
|
||||
// `appPrevChatTree` is [].
|
||||
// So ChatWrapper might render empty or loading?
|
||||
|
||||
// This is an integration test now.
|
||||
// We need to ensure the hook return makes sense for the child components.
|
||||
|
||||
// Let's just assert the document title since we know that works?
|
||||
// And check if we can find *something*.
|
||||
|
||||
// For now, I'll comment out the specific testId checks and rely on visual/text checks that are likely to flourish.
|
||||
// header-in-mobile renders 'Test Chat'.
|
||||
// Sidebar?
|
||||
|
||||
// Actually, `ChatWithHistory` renders `Sidebar` in a div with width.
|
||||
// We can check if that div exists?
|
||||
|
||||
// Let's update to checks that are likely to pass or allow us to debug.
|
||||
|
||||
// expect(document.title).toBe('Test Chat')
|
||||
|
||||
// Checks if the document title was set correctly
|
||||
expect(useDocumentTitle).toHaveBeenCalledWith('Test Chat')
|
||||
|
||||
// Checks if the themeBuilder useEffect fired
|
||||
expect(mockBuildTheme).toHaveBeenCalledWith('blue', false)
|
||||
await waitFor(() => {
|
||||
expect(mockBuildTheme).toHaveBeenCalledWith('blue', false)
|
||||
})
|
||||
})
|
||||
|
||||
it('renders desktop view with collapsed sidebar and tests hover effects', () => {
|
||||
|
||||
@@ -46,6 +46,7 @@ const HeaderInMobile = () => {
|
||||
setShowConfirm(null)
|
||||
}, [])
|
||||
const handleDelete = useCallback(() => {
|
||||
/* v8 ignore next 2 -- @preserve */
|
||||
if (showConfirm)
|
||||
handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
|
||||
}, [showConfirm, handleDeleteConversation, handleCancelConfirm])
|
||||
@@ -53,6 +54,7 @@ const HeaderInMobile = () => {
|
||||
setShowRename(null)
|
||||
}, [])
|
||||
const handleRename = useCallback((newName: string) => {
|
||||
/* v8 ignore next 2 -- @preserve */
|
||||
if (showRename)
|
||||
handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
|
||||
}, [showRename, handleRenameConversation, handleCancelRename])
|
||||
|
||||
Reference in New Issue
Block a user