@@ -4,7 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
44
55import type { InputParam , NodeData } from '@/core/types'
66
7- import { type AsyncInputProps , NodeInputHandler } from './NodeInputHandler'
7+ import { type AsyncInputProps , type ConfigInputComponentProps , NodeInputHandler } from './NodeInputHandler'
88
99// ─── Mocks ────────────────────────────────────────────────────────────────────
1010
@@ -177,3 +177,146 @@ describe('NodeInputHandler – async onChange wiring', () => {
177177 } )
178178 } )
179179} )
180+
181+ describe ( 'NodeInputHandler – loadConfig rendering' , ( ) => {
182+ const StubAsyncInput : ComponentType < AsyncInputProps > = ( { onChange } ) => (
183+ < button data-testid = 'async-select' onClick = { ( ) => onChange ( 'selected-value' ) } >
184+ Select
185+ </ button >
186+ )
187+
188+ const StubConfigInput : ComponentType < ConfigInputComponentProps > = ( { inputParam } ) => (
189+ < div data-testid = { `config-input-${ inputParam . name } ` } > Config Accordion</ div >
190+ )
191+
192+ it ( 'renders ConfigInputComponent when loadConfig is true and value exists' , ( ) => {
193+ render (
194+ < NodeInputHandler
195+ inputParam = { makeParam ( { type : 'asyncOptions' , loadConfig : true } ) }
196+ data = { { ...baseNodeData , inputValues : { myField : 'chatOpenAI' } } }
197+ isAdditionalParams
198+ onDataChange = { mockOnDataChange }
199+ AsyncInputComponent = { StubAsyncInput }
200+ ConfigInputComponent = { StubConfigInput }
201+ onConfigChange = { jest . fn ( ) }
202+ />
203+ )
204+
205+ expect ( screen . getByTestId ( 'config-input-myField' ) ) . toBeTruthy ( )
206+ } )
207+
208+ it ( 'does not render ConfigInputComponent when loadConfig is false' , ( ) => {
209+ render (
210+ < NodeInputHandler
211+ inputParam = { makeParam ( { type : 'asyncOptions' , loadConfig : false } ) }
212+ data = { { ...baseNodeData , inputValues : { myField : 'chatOpenAI' } } }
213+ isAdditionalParams
214+ onDataChange = { mockOnDataChange }
215+ AsyncInputComponent = { StubAsyncInput }
216+ ConfigInputComponent = { StubConfigInput }
217+ onConfigChange = { jest . fn ( ) }
218+ />
219+ )
220+
221+ expect ( screen . queryByTestId ( 'config-input-myField' ) ) . toBeNull ( )
222+ } )
223+
224+ it ( 'does not render ConfigInputComponent when value is empty' , ( ) => {
225+ render (
226+ < NodeInputHandler
227+ inputParam = { makeParam ( { type : 'asyncOptions' , loadConfig : true } ) }
228+ data = { { ...baseNodeData , inputValues : { myField : '' } } }
229+ isAdditionalParams
230+ onDataChange = { mockOnDataChange }
231+ AsyncInputComponent = { StubAsyncInput }
232+ ConfigInputComponent = { StubConfigInput }
233+ onConfigChange = { jest . fn ( ) }
234+ />
235+ )
236+
237+ expect ( screen . queryByTestId ( 'config-input-myField' ) ) . toBeNull ( )
238+ } )
239+
240+ it ( 'does not render ConfigInputComponent when component is not injected' , ( ) => {
241+ render (
242+ < NodeInputHandler
243+ inputParam = { makeParam ( { type : 'asyncOptions' , loadConfig : true } ) }
244+ data = { { ...baseNodeData , inputValues : { myField : 'chatOpenAI' } } }
245+ isAdditionalParams
246+ onDataChange = { mockOnDataChange }
247+ AsyncInputComponent = { StubAsyncInput }
248+ />
249+ )
250+
251+ expect ( screen . queryByTestId ( 'config-input-myField' ) ) . toBeNull ( )
252+ } )
253+
254+ it ( 'does not render ConfigInputComponent when onConfigChange is not provided' , ( ) => {
255+ render (
256+ < NodeInputHandler
257+ inputParam = { makeParam ( { type : 'asyncOptions' , loadConfig : true } ) }
258+ data = { { ...baseNodeData , inputValues : { myField : 'chatOpenAI' } } }
259+ isAdditionalParams
260+ onDataChange = { mockOnDataChange }
261+ AsyncInputComponent = { StubAsyncInput }
262+ ConfigInputComponent = { StubConfigInput }
263+ />
264+ )
265+
266+ expect ( screen . queryByTestId ( 'config-input-myField' ) ) . toBeNull ( )
267+ } )
268+ } )
269+
270+ describe ( 'NodeInputHandler – credential type rendering' , ( ) => {
271+ const StubAsyncInput : ComponentType < AsyncInputProps > = ( { onChange } ) => (
272+ < button data-testid = 'credential-select' onClick = { ( ) => onChange ( 'cred-id-123' ) } >
273+ Select Credential
274+ </ button >
275+ )
276+
277+ it ( 'renders AsyncInputComponent for credential type' , ( ) => {
278+ render (
279+ < NodeInputHandler
280+ inputParam = { makeParam ( { type : 'credential' , name : 'FLOWISE_CREDENTIAL_ID' , credentialNames : [ 'awsApi' ] } ) }
281+ data = { baseNodeData }
282+ isAdditionalParams
283+ onDataChange = { mockOnDataChange }
284+ AsyncInputComponent = { StubAsyncInput }
285+ />
286+ )
287+
288+ expect ( screen . getByTestId ( 'credential-select' ) ) . toBeTruthy ( )
289+ } )
290+
291+ it ( 'renders nothing for credential type when no AsyncInputComponent' , ( ) => {
292+ const { container } = render (
293+ < NodeInputHandler
294+ inputParam = { makeParam ( { type : 'credential' , name : 'FLOWISE_CREDENTIAL_ID' , credentialNames : [ 'awsApi' ] } ) }
295+ data = { baseNodeData }
296+ isAdditionalParams
297+ onDataChange = { mockOnDataChange }
298+ />
299+ )
300+
301+ expect ( container . querySelector ( 'button' ) ) . toBeNull ( )
302+ } )
303+
304+ it ( 'calls onDataChange when credential onChange fires' , ( ) => {
305+ render (
306+ < NodeInputHandler
307+ inputParam = { makeParam ( { type : 'credential' , name : 'FLOWISE_CREDENTIAL_ID' , credentialNames : [ 'awsApi' ] } ) }
308+ data = { baseNodeData }
309+ isAdditionalParams
310+ onDataChange = { mockOnDataChange }
311+ AsyncInputComponent = { StubAsyncInput }
312+ />
313+ )
314+
315+ fireEvent . click ( screen . getByTestId ( 'credential-select' ) )
316+
317+ expect ( mockOnDataChange ) . toHaveBeenCalledWith ( {
318+ inputParam : expect . objectContaining ( { name : 'FLOWISE_CREDENTIAL_ID' , type : 'credential' } ) ,
319+ newValue : 'cred-id-123'
320+ } )
321+ } )
322+ } )
0 commit comments