Skip to content

Commit 8d26e6a

Browse files
iamjoelCopilot
andauthored
chore: some tests for components (langgenius#30194)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 61d255a commit 8d26e6a

8 files changed

Lines changed: 814 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import type { DataSet } from '@/models/datasets'
2+
import { act, fireEvent, render, screen } from '@testing-library/react'
3+
import * as React from 'react'
4+
5+
import { describe, expect, it, vi } from 'vitest'
6+
import { IndexingType } from '@/app/components/datasets/create/step-two'
7+
import { DatasetPermission } from '@/models/datasets'
8+
import { RETRIEVE_METHOD } from '@/types/app'
9+
import SelectDataSet from './index'
10+
11+
vi.mock('@/i18n-config/i18next-config', () => ({
12+
__esModule: true,
13+
default: {
14+
changeLanguage: vi.fn(),
15+
addResourceBundle: vi.fn(),
16+
use: vi.fn().mockReturnThis(),
17+
init: vi.fn(),
18+
addResource: vi.fn(),
19+
hasResourceBundle: vi.fn().mockReturnValue(true),
20+
},
21+
}))
22+
const mockUseInfiniteScroll = vi.fn()
23+
vi.mock('ahooks', async (importOriginal) => {
24+
const actual = await importOriginal()
25+
return {
26+
...(typeof actual === 'object' && actual !== null ? actual : {}),
27+
useInfiniteScroll: (...args: any[]) => mockUseInfiniteScroll(...args),
28+
}
29+
})
30+
31+
const mockUseInfiniteDatasets = vi.fn()
32+
vi.mock('@/service/knowledge/use-dataset', () => ({
33+
useInfiniteDatasets: (...args: any[]) => mockUseInfiniteDatasets(...args),
34+
}))
35+
36+
vi.mock('@/hooks/use-knowledge', () => ({
37+
useKnowledge: () => ({
38+
formatIndexingTechniqueAndMethod: (tech: string, method: string) => `${tech}:${method}`,
39+
}),
40+
}))
41+
42+
const baseProps = {
43+
isShow: true,
44+
onClose: vi.fn(),
45+
selectedIds: [] as string[],
46+
onSelect: vi.fn(),
47+
}
48+
49+
const makeDataset = (overrides: Partial<DataSet>): DataSet => ({
50+
id: 'dataset-id',
51+
name: 'Dataset Name',
52+
provider: 'internal',
53+
icon_info: {
54+
icon_type: 'emoji',
55+
icon: '💾',
56+
icon_background: '#fff',
57+
icon_url: '',
58+
},
59+
embedding_available: true,
60+
is_multimodal: false,
61+
description: '',
62+
permission: DatasetPermission.allTeamMembers,
63+
indexing_technique: IndexingType.ECONOMICAL,
64+
retrieval_model_dict: {
65+
search_method: RETRIEVE_METHOD.fullText,
66+
top_k: 5,
67+
reranking_enable: false,
68+
reranking_model: {
69+
reranking_model_name: '',
70+
reranking_provider_name: '',
71+
},
72+
score_threshold_enabled: false,
73+
score_threshold: 0,
74+
},
75+
...overrides,
76+
} as DataSet)
77+
78+
describe('SelectDataSet', () => {
79+
beforeEach(() => {
80+
vi.clearAllMocks()
81+
})
82+
83+
it('renders dataset entries, allows selection, and fires onSelect', async () => {
84+
const datasetOne = makeDataset({
85+
id: 'set-1',
86+
name: 'Dataset One',
87+
is_multimodal: true,
88+
indexing_technique: IndexingType.ECONOMICAL,
89+
})
90+
const datasetTwo = makeDataset({
91+
id: 'set-2',
92+
name: 'Hidden Dataset',
93+
embedding_available: false,
94+
provider: 'external',
95+
})
96+
mockUseInfiniteDatasets.mockReturnValue({
97+
data: { pages: [{ data: [datasetOne, datasetTwo] }] },
98+
isLoading: false,
99+
isFetchingNextPage: false,
100+
fetchNextPage: vi.fn(),
101+
hasNextPage: false,
102+
})
103+
104+
const onSelect = vi.fn()
105+
await act(async () => {
106+
render(<SelectDataSet {...baseProps} onSelect={onSelect} selectedIds={[]} />)
107+
})
108+
109+
expect(screen.getByText('Dataset One')).toBeInTheDocument()
110+
expect(screen.getByText('Hidden Dataset')).toBeInTheDocument()
111+
112+
await act(async () => {
113+
fireEvent.click(screen.getByText('Dataset One'))
114+
})
115+
expect(screen.getByText('1 appDebug.feature.dataSet.selected')).toBeInTheDocument()
116+
117+
const addButton = screen.getByRole('button', { name: 'common.operation.add' })
118+
await act(async () => {
119+
fireEvent.click(addButton)
120+
})
121+
expect(onSelect).toHaveBeenCalledWith([datasetOne])
122+
})
123+
124+
it('shows empty state when no datasets are available and disables add', async () => {
125+
mockUseInfiniteDatasets.mockReturnValue({
126+
data: { pages: [{ data: [] }] },
127+
isLoading: false,
128+
isFetchingNextPage: false,
129+
fetchNextPage: vi.fn(),
130+
hasNextPage: false,
131+
})
132+
133+
await act(async () => {
134+
render(<SelectDataSet {...baseProps} onSelect={vi.fn()} selectedIds={[]} />)
135+
})
136+
137+
expect(screen.getByText('appDebug.feature.dataSet.noDataSet')).toBeInTheDocument()
138+
expect(screen.getByRole('link', { name: 'appDebug.feature.dataSet.toCreate' })).toHaveAttribute('href', '/datasets/create')
139+
expect(screen.getByRole('button', { name: 'common.operation.add' })).toBeDisabled()
140+
})
141+
})
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { IPromptValuePanelProps } from './index'
2+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
3+
import * as React from 'react'
4+
import { beforeEach, describe, expect, it, vi } from 'vitest'
5+
import { useStore } from '@/app/components/app/store'
6+
import ConfigContext from '@/context/debug-configuration'
7+
import { AppModeEnum, ModelModeType, Resolution } from '@/types/app'
8+
import PromptValuePanel from './index'
9+
10+
vi.mock('@/app/components/app/store', () => ({
11+
useStore: vi.fn(),
12+
}))
13+
vi.mock('@/app/components/base/features/new-feature-panel/feature-bar', () => ({
14+
__esModule: true,
15+
default: ({ onFeatureBarClick }: { onFeatureBarClick: () => void }) => (
16+
<button type="button" onClick={onFeatureBarClick}>
17+
feature bar
18+
</button>
19+
),
20+
}))
21+
22+
const mockSetShowAppConfigureFeaturesModal = vi.fn()
23+
const mockUseStore = vi.mocked(useStore)
24+
const mockSetInputs = vi.fn()
25+
const mockOnSend = vi.fn()
26+
27+
const promptVariables = [
28+
{ key: 'textVar', name: 'Text Var', type: 'string', required: true },
29+
{ key: 'boolVar', name: 'Boolean Var', type: 'checkbox' },
30+
] as const
31+
32+
const baseContextValue: any = {
33+
modelModeType: ModelModeType.completion,
34+
modelConfig: {
35+
configs: {
36+
prompt_template: 'prompt template',
37+
prompt_variables: promptVariables,
38+
},
39+
},
40+
setInputs: mockSetInputs,
41+
mode: AppModeEnum.COMPLETION,
42+
isAdvancedMode: false,
43+
completionPromptConfig: {
44+
prompt: { text: 'completion' },
45+
conversation_histories_role: { user_prefix: 'user', assistant_prefix: 'assistant' },
46+
},
47+
chatPromptConfig: { prompt: [] },
48+
} as any
49+
50+
const defaultProps: IPromptValuePanelProps = {
51+
appType: AppModeEnum.COMPLETION,
52+
onSend: mockOnSend,
53+
inputs: { textVar: 'initial', boolVar: false },
54+
visionConfig: { enabled: false, number_limits: 0, detail: Resolution.low, transfer_methods: [] },
55+
onVisionFilesChange: vi.fn(),
56+
}
57+
58+
const renderPanel = (options: {
59+
context?: Partial<typeof baseContextValue>
60+
props?: Partial<IPromptValuePanelProps>
61+
} = {}) => {
62+
const contextValue = { ...baseContextValue, ...options.context }
63+
const props = { ...defaultProps, ...options.props }
64+
return render(
65+
<ConfigContext.Provider value={contextValue}>
66+
<PromptValuePanel {...props} />
67+
</ConfigContext.Provider>,
68+
)
69+
}
70+
71+
describe('PromptValuePanel', () => {
72+
beforeEach(() => {
73+
mockUseStore.mockImplementation(selector => selector({
74+
setShowAppConfigureFeaturesModal: mockSetShowAppConfigureFeaturesModal,
75+
appSidebarExpand: '',
76+
currentLogModalActiveTab: 'prompt',
77+
showPromptLogModal: false,
78+
showAgentLogModal: false,
79+
setShowPromptLogModal: vi.fn(),
80+
setShowAgentLogModal: vi.fn(),
81+
showMessageLogModal: false,
82+
showAppConfigureFeaturesModal: false,
83+
} as any))
84+
mockSetInputs.mockClear()
85+
mockOnSend.mockClear()
86+
mockSetShowAppConfigureFeaturesModal.mockClear()
87+
})
88+
89+
it('updates inputs, clears values, and triggers run when ready', async () => {
90+
renderPanel()
91+
92+
const textInput = screen.getByPlaceholderText('Text Var')
93+
fireEvent.change(textInput, { target: { value: 'updated' } })
94+
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({ textVar: 'updated' }))
95+
96+
const clearButton = screen.getByRole('button', { name: 'common.operation.clear' })
97+
fireEvent.click(clearButton)
98+
99+
expect(mockSetInputs).toHaveBeenLastCalledWith({
100+
textVar: '',
101+
boolVar: '',
102+
})
103+
104+
const runButton = screen.getByRole('button', { name: 'appDebug.inputs.run' })
105+
expect(runButton).not.toBeDisabled()
106+
fireEvent.click(runButton)
107+
await waitFor(() => expect(mockOnSend).toHaveBeenCalledTimes(1))
108+
})
109+
110+
it('disables run when mode is not completion', () => {
111+
renderPanel({
112+
context: {
113+
mode: AppModeEnum.CHAT,
114+
},
115+
props: {
116+
appType: AppModeEnum.CHAT,
117+
},
118+
})
119+
120+
const runButton = screen.getByRole('button', { name: 'appDebug.inputs.run' })
121+
expect(runButton).toBeDisabled()
122+
fireEvent.click(runButton)
123+
expect(mockOnSend).not.toHaveBeenCalled()
124+
})
125+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { PromptVariable } from '@/models/debug'
2+
3+
import { describe, expect, it } from 'vitest'
4+
import { replaceStringWithValues } from './utils'
5+
6+
const promptVariables: PromptVariable[] = [
7+
{ key: 'user', name: 'User', type: 'string' },
8+
{ key: 'topic', name: 'Topic', type: 'string' },
9+
]
10+
11+
describe('replaceStringWithValues', () => {
12+
it('should replace placeholders when inputs have values', () => {
13+
const template = 'Hello {{user}} talking about {{topic}}'
14+
const result = replaceStringWithValues(template, promptVariables, { user: 'Alice', topic: 'cats' })
15+
expect(result).toBe('Hello Alice talking about cats')
16+
})
17+
18+
it('should use prompt variable name when value is missing', () => {
19+
const template = 'Hi {{user}} from {{topic}}'
20+
const result = replaceStringWithValues(template, promptVariables, {})
21+
expect(result).toBe('Hi {{User}} from {{Topic}}')
22+
})
23+
24+
it('should leave placeholder untouched when no variable is defined', () => {
25+
const template = 'Unknown {{missing}} placeholder'
26+
const result = replaceStringWithValues(template, promptVariables, {})
27+
expect(result).toBe('Unknown {{missing}} placeholder')
28+
})
29+
})

0 commit comments

Comments
 (0)