-
-
Notifications
You must be signed in to change notification settings - Fork 24.5k
Expand file tree
/
Copy pathArrayInput.test.tsx
More file actions
330 lines (272 loc) · 12.4 KB
/
ArrayInput.test.tsx
File metadata and controls
330 lines (272 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
import { fireEvent, render, screen } from '@testing-library/react'
import type { InputParam, NodeData } from '@/core/types'
import { ArrayInput } from './ArrayInput'
// --- Mocks ---
const mockOnDataChange = jest.fn()
jest.mock('./NodeInputHandler', () => ({
NodeInputHandler: ({
inputParam,
onDataChange
}: {
inputParam: InputParam
data: NodeData
onDataChange: (args: { inputParam: InputParam; newValue: unknown }) => void
}) => (
<div data-testid={`input-handler-${inputParam.name}`}>
<label>{inputParam.label}</label>
<input data-testid={`input-${inputParam.name}`} onChange={(e) => onDataChange({ inputParam, newValue: e.target.value })} />
</div>
)
}))
jest.mock('@tabler/icons-react', () => ({
IconPlus: () => <span data-testid='icon-plus' />,
IconTrash: () => <span data-testid='icon-trash' />
}))
describe('ArrayInput', () => {
const mockInputParam: InputParam = {
id: 'test-array',
name: 'testArray',
label: 'Test Item',
type: 'array',
array: [
{ id: 'field1', name: 'field1', label: 'Field 1', type: 'string', default: '' } as InputParam,
{ id: 'field2', name: 'field2', label: 'Field 2', type: 'number', default: 0 } as InputParam
]
}
const mockNodeData: NodeData = {
id: 'node-1',
name: 'testNode',
label: 'Test Node',
inputValues: {}
} as NodeData
beforeEach(() => {
jest.clearAllMocks()
})
// Test 1: Render existing items
it('should render existing items correctly', () => {
const dataWithItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [
{ field1: 'value1', field2: 10 },
{ field1: 'value2', field2: 20 }
]
}
} as NodeData
render(<ArrayInput inputParam={mockInputParam} data={dataWithItems} onDataChange={mockOnDataChange} />)
// Verify both items are rendered
expect(screen.getByText('0')).toBeInTheDocument()
expect(screen.getByText('1')).toBeInTheDocument()
// Verify field handlers are rendered for both items
expect(screen.getAllByTestId('input-handler-field1')).toHaveLength(2)
expect(screen.getAllByTestId('input-handler-field2')).toHaveLength(2)
})
// Test 2: Render Add button
it('should render Add button with correct label', () => {
render(<ArrayInput inputParam={mockInputParam} data={mockNodeData} onDataChange={mockOnDataChange} />)
const addButton = screen.getByRole('button', { name: /Add Test Item/i })
expect(addButton).toBeInTheDocument()
expect(screen.getByTestId('icon-plus')).toBeInTheDocument()
})
// Test 3: Add new item
it('should add new item and call onDataChange with new array', () => {
render(<ArrayInput inputParam={mockInputParam} data={mockNodeData} onDataChange={mockOnDataChange} />)
const addButton = screen.getByRole('button', { name: /Add Test Item/i })
fireEvent.click(addButton)
// Verify onDataChange was called with new array containing default values
expect(mockOnDataChange).toHaveBeenCalledWith({
inputParam: mockInputParam,
newValue: [{ field1: '', field2: 0 }]
})
})
// Test 4: Delete item
it('should delete item and verify item removed from array', () => {
const dataWithItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [
{ field1: 'value1', field2: 10 },
{ field1: 'value2', field2: 20 }
]
}
} as NodeData
render(<ArrayInput inputParam={mockInputParam} data={dataWithItems} onDataChange={mockOnDataChange} />)
// Get all delete buttons (IconTrash buttons)
const deleteButtons = screen.getAllByTitle('Delete')
// Click the first delete button
fireEvent.click(deleteButtons[0])
// Verify onDataChange was called with updated array (first item removed)
expect(mockOnDataChange).toHaveBeenCalledWith({
inputParam: mockInputParam,
newValue: [{ field1: 'value2', field2: 20 }]
})
})
// Test 5: Handle field changes
it('should handle nested field changes and update parent array', () => {
const dataWithItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [{ field1: 'initial', field2: 5 }]
}
} as NodeData
render(<ArrayInput inputParam={mockInputParam} data={dataWithItems} onDataChange={mockOnDataChange} />)
// Change field1 value
const field1Input = screen.getByTestId('input-field1')
fireEvent.change(field1Input, { target: { value: 'updated' } })
// Verify parent array was updated
expect(mockOnDataChange).toHaveBeenCalledWith({
inputParam: mockInputParam,
newValue: [{ field1: 'updated', field2: 5 }]
})
})
// Test 6: Empty array initialization
it('should render with empty array and only show Add button', () => {
render(<ArrayInput inputParam={mockInputParam} data={mockNodeData} onDataChange={mockOnDataChange} />)
// Verify no items are rendered
expect(screen.queryByText('0')).not.toBeInTheDocument()
// Verify Add button is present
expect(screen.getByRole('button', { name: /Add Test Item/i })).toBeInTheDocument()
})
// Test 7: Respect disabled prop
it('should disable buttons when disabled prop is true', () => {
const dataWithItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [{ field1: 'value1', field2: 10 }]
}
} as NodeData
render(<ArrayInput inputParam={mockInputParam} data={dataWithItems} disabled={true} onDataChange={mockOnDataChange} />)
// Verify Add button is disabled
const addButton = screen.getByRole('button', { name: /Add Test Item/i })
expect(addButton).toBeDisabled()
// Verify Delete button is disabled
const deleteButton = screen.getByTitle('Delete')
expect(deleteButton).toBeDisabled()
})
// Test 8: Filter hidden fields
it('should not render fields with display set to false', () => {
const inputParamWithHiddenField: InputParam = {
...mockInputParam,
array: [
{ id: 'visible', name: 'visible', label: 'Visible Field', type: 'string', display: true } as InputParam,
{ id: 'hidden', name: 'hidden', label: 'Hidden Field', type: 'string', display: false } as InputParam
]
}
const dataWithItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [{ visible: 'test', hidden: 'should-not-show' }]
}
} as NodeData
render(<ArrayInput inputParam={inputParamWithHiddenField} data={dataWithItems} onDataChange={mockOnDataChange} />)
// Verify visible field is rendered
expect(screen.getByTestId('input-handler-visible')).toBeInTheDocument()
// Verify hidden field is NOT rendered
expect(screen.queryByTestId('input-handler-hidden')).not.toBeInTheDocument()
})
// Test 9: Multiple items
it('should render multiple items with correct indices', () => {
const dataWithMultipleItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [
{ field1: 'item1', field2: 1 },
{ field1: 'item2', field2: 2 },
{ field1: 'item3', field2: 3 },
{ field1: 'item4', field2: 4 }
]
}
} as NodeData
render(<ArrayInput inputParam={mockInputParam} data={dataWithMultipleItems} onDataChange={mockOnDataChange} />)
// Verify all indices are shown
expect(screen.getByText('0')).toBeInTheDocument()
expect(screen.getByText('1')).toBeInTheDocument()
expect(screen.getByText('2')).toBeInTheDocument()
expect(screen.getByText('3')).toBeInTheDocument()
// Verify all field handlers are rendered (4 items * 2 fields each = 8 handlers)
expect(screen.getAllByTestId('input-handler-field1')).toHaveLength(4)
expect(screen.getAllByTestId('input-handler-field2')).toHaveLength(4)
})
// Test 10: Default values
it('should initialize new items with field default values', () => {
const inputParamWithDefaults: InputParam = {
id: 'test-array',
name: 'testArray',
label: 'Test Item',
type: 'array',
array: [
{ id: 'name', name: 'name', label: 'Name', type: 'string', default: 'John Doe' } as InputParam,
{ id: 'age', name: 'age', label: 'Age', type: 'number', default: 25 } as InputParam,
{ id: 'active', name: 'active', label: 'Active', type: 'boolean', default: true } as InputParam
]
}
render(<ArrayInput inputParam={inputParamWithDefaults} data={mockNodeData} onDataChange={mockOnDataChange} />)
const addButton = screen.getByRole('button', { name: /Add Test Item/i })
fireEvent.click(addButton)
// Verify new item initialized with correct default values
expect(mockOnDataChange).toHaveBeenCalledWith({
inputParam: inputParamWithDefaults,
newValue: [{ name: 'John Doe', age: 25, active: true }]
})
})
// minItems constraint
it('should respect minItems constraint and disable delete when minimum reached', () => {
const inputParamWithMinItems: InputParam = {
...mockInputParam,
minItems: 2
}
const dataWithItems: NodeData = {
...mockNodeData,
inputValues: {
testArray: [
{ field1: 'value1', field2: 10 },
{ field1: 'value2', field2: 20 }
]
}
} as NodeData
render(<ArrayInput inputParam={inputParamWithMinItems} data={dataWithItems} onDataChange={mockOnDataChange} />)
// Both delete buttons should be disabled when at minItems limit
const deleteButtons = screen.getAllByTitle('Delete')
expect(deleteButtons[0]).toBeDisabled()
expect(deleteButtons[1]).toBeDisabled()
})
// Test 11: itemParameters prop overrides inputParam.array display flags
it('should use itemParameters prop for field visibility when provided, ignoring inputParam.array display flags', () => {
// inputParam.array has both fields with no display flag (both would show)
const dataWithItem: NodeData = {
...mockNodeData,
inputValues: { testArray: [{ field1: 'value', field2: 10 }] }
} as NodeData
// Parent (EditNodeDialog) has evaluated field2 as hidden
const itemParameters: InputParam[][] = [
[
{ id: 'field1', name: 'field1', label: 'Field 1', type: 'string', display: true } as InputParam,
{ id: 'field2', name: 'field2', label: 'Field 2', type: 'number', display: false } as InputParam
]
]
render(
<ArrayInput inputParam={mockInputParam} data={dataWithItem} onDataChange={mockOnDataChange} itemParameters={itemParameters} />
)
// field1 visible per itemParameters
expect(screen.getByTestId('input-handler-field1')).toBeInTheDocument()
// field2 hidden per itemParameters even though inputParam.array has no display flag
expect(screen.queryByTestId('input-handler-field2')).not.toBeInTheDocument()
})
// Test reading minItems from inputParam
it('should read minItems from inputParam', () => {
const inputParamWithMinItems: InputParam = {
...mockInputParam,
minItems: 1
}
const dataWithOneItem: NodeData = {
...mockNodeData,
inputValues: {
testArray: [{ field1: 'value1', field2: 10 }]
}
} as NodeData
render(<ArrayInput inputParam={inputParamWithMinItems} data={dataWithOneItem} onDataChange={mockOnDataChange} />)
// Delete button should be disabled when at minItems limit
const deleteButton = screen.getByTitle('Delete')
expect(deleteButton).toBeDisabled()
})
})