ywana-core8 0.1.103 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +4941 -324
- package/dist/index.js +42339 -0
- package/dist/index.js.map +1 -0
- package/dist/index.modern.js +37459 -31678
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +39635 -34010
- package/dist/index.umd.js.map +1 -1
- package/package.json +26 -29
- package/src/Test.stories.jsx +28 -0
- package/src/desktop/Desktop.stories.jsx +110 -0
- package/src/desktop/WindowContext.js +135 -0
- package/src/desktop/WindowManager.js +355 -0
- package/src/desktop/desktop.css +55 -4
- package/src/desktop/desktop.js +312 -6
- package/src/desktop/index.js +7 -0
- package/src/desktop/window.css +229 -36
- package/src/desktop/window.js +255 -20
- package/src/desktop.backup/desktop.css +6 -0
- package/src/desktop.backup/desktop.js +13 -0
- package/src/desktop.backup/window.css +58 -0
- package/src/desktop.backup/window.js +27 -0
- package/src/html/Accordion.stories.jsx +178 -0
- package/src/html/Button.stories.jsx +175 -0
- package/src/html/Checkbox.stories.jsx +131 -0
- package/src/html/Chip.stories.jsx +189 -0
- package/src/html/Color.stories.jsx +234 -0
- package/src/html/Form.stories.jsx +271 -0
- package/src/html/Icon.stories.jsx +233 -0
- package/src/html/Progress.stories.jsx +247 -0
- package/src/html/Radio.stories.jsx +289 -0
- package/src/html/StyleTest.stories.jsx +81 -0
- package/src/html/Switch.stories.jsx +329 -0
- package/src/html/Tab.stories.jsx +239 -0
- package/src/html/Table.stories.jsx +188 -0
- package/src/html/Table2.stories.jsx +238 -0
- package/src/html/TextField2.stories.jsx +337 -0
- package/src/html/Tree.stories.jsx +285 -0
- package/src/html/accordion.example.js +0 -74
- package/src/html/accordion.js +1 -6
- package/src/html/button.js +2 -13
- package/src/html/checkbox.js +1 -9
- package/src/html/chip.js +2 -19
- package/src/html/color.js +1 -14
- package/src/html/form.js +4 -15
- package/src/html/header2.js +1 -12
- package/src/html/icon.js +1 -7
- package/src/html/index.js +1 -1
- package/src/html/list.js +1 -19
- package/src/html/menu.js +9 -5
- package/src/html/progress.js +5 -53
- package/src/html/property.js +9 -25
- package/src/html/radio.js +2 -16
- package/src/html/section.js +1 -6
- package/src/html/selector.js +2 -19
- package/src/html/switch.css +134 -100
- package/src/html/switch.example.js +46 -36
- package/src/html/switch.js +43 -192
- package/src/html/tab.js +3 -24
- package/src/html/text.js +1 -12
- package/src/html/textfield2.js +5 -42
- package/src/html/thumbnail.js +1 -12
- package/src/html/tokenfield.js +2 -21
- package/src/html/tree.js +3 -35
- package/src/index.js +1 -0
- package/__previewjs__/Wrapper.tsx +0 -14
- package/build-doc.sh +0 -10
- package/db/db.json +0 -89
- package/db/routes.json +0 -0
- package/dist/index.cjs +0 -36722
- package/dist/index.cjs.map +0 -1
- package/dist/index.css.map +0 -1
- package/doc/README.md +0 -196
- package/doc/evalulations/ACCORDION_EVALUATION.md +0 -583
- package/doc/evalulations/CHECKBOX_EVALUATION.md +0 -273
- package/doc/evalulations/CHIP_EVALUATION.md +0 -542
- package/doc/evalulations/COLOR_EVALUATION.md +0 -524
- package/doc/evalulations/COMPONENTS_EVALUATION.md +0 -477
- package/doc/evalulations/FORM_EVALUATION.md +0 -459
- package/doc/evalulations/HEADER_EVALUATION.md +0 -436
- package/doc/evalulations/ICON_EVALUATION.md +0 -254
- package/doc/evalulations/LIST_EVALUATION.md +0 -574
- package/doc/evalulations/PROGRESS_EVALUATION.md +0 -450
- package/doc/evalulations/RADIO_EVALUATION.md +0 -439
- package/doc/evalulations/RADIO_VISUAL_FIX.md +0 -183
- package/doc/evalulations/SECTION_IMPROVEMENTS.md +0 -153
- package/doc/evalulations/SWITCH_EVALUATION.md +0 -335
- package/doc/evalulations/SWITCH_VISUAL_FIX.md +0 -232
- package/doc/evalulations/TAB_EVALUATION.md +0 -626
- package/doc/evalulations/TEXTFIELD_EVALUATION.md +0 -747
- package/doc/evalulations/TOOLTIP_FIX.md +0 -157
- package/doc/evalulations/TREE_EVALUATION.md +0 -708
- package/doc/index.html +0 -0
- package/doc/package-lock.json +0 -17298
- package/doc/package.json +0 -34
- package/doc/public/index.html +0 -24
- package/doc/scripts/generate-examples.js +0 -129
- package/doc/src/App.css +0 -171
- package/doc/src/App.js +0 -114
- package/doc/src/components/ExamplePage.js +0 -129
- package/doc/src/components/WelcomePage.js +0 -84
- package/doc/src/index.css +0 -246
- package/doc/src/index.js +0 -17
- package/doc/src/theme.css +0 -256
- package/jest.config.js +0 -24
- package/preview.config.js +0 -38
- package/publish.sh +0 -6
- package/src/desktop/dektop.test.js +0 -11
- package/src/domain/CollectionAPI.test.js +0 -19
- package/src/domain/ContentEditor.test.js +0 -52
- package/src/domain2/CollectionAPI.test.js +0 -19
- package/src/domain2/CollectionContext.test.js +0 -71
- package/src/domain2/CollectionPage.test.js +0 -112
- package/src/domain2/DynamicForm.test.js +0 -47
- package/src/html/accordion.test.js +0 -37
- package/src/html/accordion.unit.test.js +0 -334
- package/src/html/button.example.new.js +0 -416
- package/src/html/button.test.js +0 -422
- package/src/html/checkbox.test.js +0 -285
- package/src/html/chip.test.js +0 -425
- package/src/html/color.example.js.backup +0 -527
- package/src/html/color.test.js +0 -377
- package/src/html/components.example.js.backup +0 -492
- package/src/html/components_enhanced.test.js +0 -581
- package/src/html/form.example.js.backup +0 -385
- package/src/html/form.test.js +0 -369
- package/src/html/header2.example.js.backup +0 -411
- package/src/html/header2.test.js +0 -377
- package/src/html/icon.example.js.backup +0 -268
- package/src/html/icon.test.js +0 -231
- package/src/html/label.test.js +0 -0
- package/src/html/list.example.js.backup +0 -404
- package/src/html/list.test.js +0 -383
- package/src/html/progress.example.js.backup +0 -424
- package/src/html/progress.test.js +0 -313
- package/src/html/property.example.js.backup +0 -553
- package/src/html/property.test.js +0 -371
- package/src/html/radio.example.js.backup +0 -389
- package/src/html/radio.test.js +0 -318
- package/src/html/section.example.js.backup +0 -99
- package/src/html/section.test.js +0 -131
- package/src/html/selector.test.js +0 -20
- package/src/html/switch.example.js.backup +0 -461
- package/src/html/switch.test.js +0 -355
- package/src/html/tab.example.js.backup +0 -446
- package/src/html/tab.test.js +0 -25
- package/src/html/tab_enhanced.test.js +0 -504
- package/src/html/table.test.js +0 -70
- package/src/html/table2.test.js +0 -582
- package/src/html/text.test.js +0 -15
- package/src/html/textfield.test.js +0 -51
- package/src/html/textfield2.example.js.backup +0 -1370
- package/src/html/textfield2.test.js +0 -950
- package/src/html/tokenfield.example.js.backup +0 -503
- package/src/html/tokenfield.test.js +0 -423
- package/src/html/tree.example.js.backup +0 -475
- package/src/html/tree.test.js +0 -43
- package/src/html/tree_enhanced.test.js +0 -495
- package/src/http/token.test.js +0 -50
- package/src/incubator/pdfViewer.js +0 -33
- package/src/incubator/wizard.test.js +0 -127
- package/src/site/site.test.js +0 -230
- package/src/site/view.test.js +0 -41
- package/src/widgets/calendar/Calendar.test.js +0 -28
- package/src/widgets/explorer/Explorer.test.js +0 -121
- package/src/widgets/ide/editor.test.js +0 -33
- package/src/widgets/kanban/Kanban.test.js +0 -78
- package/src/widgets/login/LoginBox.test.js +0 -12
- package/src/widgets/login/ResetPasswordBox.test.js +0 -34
- package/src/widgets/login/validations.test.js +0 -51
- package/src/widgets/planner/Planner.test.js +0 -60
- package/src/widgets/upload/Upload.test.js +0 -32
- package/table2.test.js +0 -454
@@ -1,950 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
import { TextField2, TextArea2, PasswordField2, DropDown2, DateRange2 } from './textfield2'
|
3
|
-
|
4
|
-
// Pruebas unitarias para los componentes TextField2
|
5
|
-
describe('TextField2 Components', () => {
|
6
|
-
// Mock de los componentes dependientes
|
7
|
-
const mockText = jest.fn()
|
8
|
-
const mockIcon = jest.fn()
|
9
|
-
const mockSiteContext = {
|
10
|
-
translate: jest.fn((text) => text),
|
11
|
-
changeFocus: jest.fn()
|
12
|
-
}
|
13
|
-
|
14
|
-
beforeEach(() => {
|
15
|
-
jest.clearAllMocks()
|
16
|
-
|
17
|
-
// Mock del componente Text
|
18
|
-
jest.doMock('./text', () => ({
|
19
|
-
Text: mockText
|
20
|
-
}))
|
21
|
-
|
22
|
-
// Mock del componente Icon
|
23
|
-
jest.doMock('./icon', () => ({
|
24
|
-
Icon: mockIcon
|
25
|
-
}))
|
26
|
-
|
27
|
-
// Mock del SiteContext
|
28
|
-
jest.doMock('../site/siteContext', () => ({
|
29
|
-
SiteContext: {
|
30
|
-
Provider: ({ children }) => children,
|
31
|
-
Consumer: ({ children }) => children(mockSiteContext)
|
32
|
-
}
|
33
|
-
}))
|
34
|
-
|
35
|
-
// Mock de console.warn para las pruebas
|
36
|
-
jest.spyOn(console, 'warn').mockImplementation(() => {})
|
37
|
-
})
|
38
|
-
|
39
|
-
afterEach(() => {
|
40
|
-
console.warn.mockRestore()
|
41
|
-
})
|
42
|
-
|
43
|
-
describe('TextField2 Component', () => {
|
44
|
-
test('component exports correctly', () => {
|
45
|
-
expect(TextField2).toBeDefined()
|
46
|
-
expect(typeof TextField2).toBe('function')
|
47
|
-
})
|
48
|
-
|
49
|
-
test('component has correct PropTypes', () => {
|
50
|
-
expect(TextField2.propTypes).toBeDefined()
|
51
|
-
expect(TextField2.propTypes.id).toBeDefined()
|
52
|
-
expect(TextField2.propTypes.type).toBeDefined()
|
53
|
-
expect(TextField2.propTypes.className).toBeDefined()
|
54
|
-
expect(TextField2.propTypes.label).toBeDefined()
|
55
|
-
expect(TextField2.propTypes.labelPosition).toBeDefined()
|
56
|
-
expect(TextField2.propTypes.placeholder).toBeDefined()
|
57
|
-
expect(TextField2.propTypes.value).toBeDefined()
|
58
|
-
expect(TextField2.propTypes.outlined).toBeDefined()
|
59
|
-
expect(TextField2.propTypes.readOnly).toBeDefined()
|
60
|
-
expect(TextField2.propTypes.disabled).toBeDefined()
|
61
|
-
expect(TextField2.propTypes.required).toBeDefined()
|
62
|
-
expect(TextField2.propTypes.canClear).toBeDefined()
|
63
|
-
expect(TextField2.propTypes.showPasswordToggle).toBeDefined()
|
64
|
-
expect(TextField2.propTypes.autoComplete).toBeDefined()
|
65
|
-
expect(TextField2.propTypes.error).toBeDefined()
|
66
|
-
expect(TextField2.propTypes.helperText).toBeDefined()
|
67
|
-
expect(TextField2.propTypes.validation).toBeDefined()
|
68
|
-
expect(TextField2.propTypes.onChange).toBeDefined()
|
69
|
-
})
|
70
|
-
|
71
|
-
test('component has correct defaultProps', () => {
|
72
|
-
expect(TextField2.defaultProps).toBeDefined()
|
73
|
-
expect(TextField2.defaultProps.type).toBe('text')
|
74
|
-
expect(TextField2.defaultProps.labelPosition).toBe('top')
|
75
|
-
expect(TextField2.defaultProps.outlined).toBe(false)
|
76
|
-
expect(TextField2.defaultProps.readOnly).toBe(false)
|
77
|
-
expect(TextField2.defaultProps.disabled).toBe(false)
|
78
|
-
expect(TextField2.defaultProps.required).toBe(false)
|
79
|
-
expect(TextField2.defaultProps.canClear).toBe(true)
|
80
|
-
expect(TextField2.defaultProps.showPasswordToggle).toBe(true)
|
81
|
-
expect(TextField2.defaultProps.autoComplete).toBe('off')
|
82
|
-
expect(TextField2.defaultProps.debounceMs).toBe(0)
|
83
|
-
expect(TextField2.defaultProps.className).toBe('')
|
84
|
-
})
|
85
|
-
|
86
|
-
test('warns when id prop is missing', () => {
|
87
|
-
const validateId = (id) => {
|
88
|
-
if (!id) {
|
89
|
-
console.warn('TextField2 component: id prop is required')
|
90
|
-
}
|
91
|
-
}
|
92
|
-
|
93
|
-
validateId(null)
|
94
|
-
expect(console.warn).toHaveBeenCalledWith('TextField2 component: id prop is required')
|
95
|
-
|
96
|
-
console.warn.mockClear()
|
97
|
-
validateId('test-id')
|
98
|
-
expect(console.warn).not.toHaveBeenCalled()
|
99
|
-
})
|
100
|
-
|
101
|
-
test('validation logic works correctly', () => {
|
102
|
-
const validateField = (value, validation, required) => {
|
103
|
-
if (validation && value !== undefined) {
|
104
|
-
const validationResult = validation(value)
|
105
|
-
const valid = typeof validationResult === 'boolean' ? validationResult : validationResult.valid
|
106
|
-
const errorMessage = typeof validationResult === 'object' ? validationResult.message : ''
|
107
|
-
|
108
|
-
return {
|
109
|
-
isValid: valid,
|
110
|
-
internalError: valid ? '' : errorMessage || (required && !value ? 'This field is required' : 'Invalid value')
|
111
|
-
}
|
112
|
-
} else if (required && !value) {
|
113
|
-
return {
|
114
|
-
isValid: false,
|
115
|
-
internalError: 'This field is required'
|
116
|
-
}
|
117
|
-
} else {
|
118
|
-
return {
|
119
|
-
isValid: true,
|
120
|
-
internalError: ''
|
121
|
-
}
|
122
|
-
}
|
123
|
-
}
|
124
|
-
|
125
|
-
// Valid value with validation
|
126
|
-
const result1 = validateField('test@email.com', (v) => v.includes('@'), false)
|
127
|
-
expect(result1.isValid).toBe(true)
|
128
|
-
expect(result1.internalError).toBe('')
|
129
|
-
|
130
|
-
// Invalid value with validation
|
131
|
-
const result2 = validateField('invalid-email', (v) => v.includes('@'), false)
|
132
|
-
expect(result2.isValid).toBe(false)
|
133
|
-
expect(result2.internalError).toBe('Invalid value')
|
134
|
-
|
135
|
-
// Required field empty
|
136
|
-
const result3 = validateField('', null, true)
|
137
|
-
expect(result3.isValid).toBe(false)
|
138
|
-
expect(result3.internalError).toBe('This field is required')
|
139
|
-
|
140
|
-
// Valid required field
|
141
|
-
const result4 = validateField('value', null, true)
|
142
|
-
expect(result4.isValid).toBe(true)
|
143
|
-
expect(result4.internalError).toBe('')
|
144
|
-
|
145
|
-
// Validation with custom error message
|
146
|
-
const result5 = validateField('short', (v) => ({ valid: v.length >= 6, message: 'Must be at least 6 characters' }), false)
|
147
|
-
expect(result5.isValid).toBe(false)
|
148
|
-
expect(result5.internalError).toBe('Must be at least 6 characters')
|
149
|
-
})
|
150
|
-
|
151
|
-
test('CSS classes generation works correctly', () => {
|
152
|
-
const generateClasses = (outlined, labelPosition, label, type, isFocused, disabled, readOnly, error, internalError, isValid, className, id) => {
|
153
|
-
const borderStyle = outlined ? 'textfield2-outlined' : 'textfield2'
|
154
|
-
const labelStyle = label ? '' : 'no-label'
|
155
|
-
const labelPositionStyle = labelPosition === 'left' ? 'label-left' : 'label-top'
|
156
|
-
|
157
|
-
return [
|
158
|
-
'textfield2',
|
159
|
-
borderStyle,
|
160
|
-
labelStyle,
|
161
|
-
labelPositionStyle,
|
162
|
-
`textfield2-${type}`,
|
163
|
-
isFocused && 'focused',
|
164
|
-
disabled && 'disabled',
|
165
|
-
readOnly && 'readonly',
|
166
|
-
error || internalError ? 'error' : '',
|
167
|
-
!isValid && 'invalid',
|
168
|
-
className || '',
|
169
|
-
id
|
170
|
-
].filter(Boolean).join(' ')
|
171
|
-
}
|
172
|
-
|
173
|
-
expect(generateClasses(false, 'top', 'Label', 'text', false, false, false, '', '', true, '', 'test-id'))
|
174
|
-
.toBe('textfield2 textfield2 label-top textfield2-text test-id')
|
175
|
-
|
176
|
-
expect(generateClasses(true, 'left', '', 'email', true, false, false, '', '', true, 'custom', 'test-id'))
|
177
|
-
.toBe('textfield2 textfield2-outlined no-label label-left textfield2-email focused custom test-id')
|
178
|
-
|
179
|
-
expect(generateClasses(false, 'top', 'Label', 'password', false, true, false, '', '', true, '', 'test-id'))
|
180
|
-
.toBe('textfield2 textfield2 label-top textfield2-password disabled test-id')
|
181
|
-
|
182
|
-
expect(generateClasses(false, 'top', 'Label', 'text', false, false, false, 'Error message', '', false, '', 'test-id'))
|
183
|
-
.toBe('textfield2 textfield2 label-top textfield2-text error invalid test-id')
|
184
|
-
})
|
185
|
-
|
186
|
-
test('accessibility attributes generation works correctly', () => {
|
187
|
-
const generateAriaAttributes = (ariaLabel, label, ariaDescribedBy, error, internalError, helperText, id, isValid, required, disabled, readOnly) => {
|
188
|
-
return {
|
189
|
-
'aria-label': ariaLabel || label,
|
190
|
-
'aria-describedby': ariaDescribedBy || (error || internalError || helperText ? `${id}-helper` : undefined),
|
191
|
-
'aria-invalid': !isValid || !!(error || internalError),
|
192
|
-
'aria-required': required,
|
193
|
-
'aria-disabled': disabled,
|
194
|
-
'aria-readonly': readOnly
|
195
|
-
}
|
196
|
-
}
|
197
|
-
|
198
|
-
const basic = generateAriaAttributes(null, 'Test Label', null, '', '', '', 'test-id', true, false, false, false)
|
199
|
-
expect(basic['aria-label']).toBe('Test Label')
|
200
|
-
expect(basic['aria-describedby']).toBeUndefined()
|
201
|
-
expect(basic['aria-invalid']).toBe(false)
|
202
|
-
expect(basic['aria-required']).toBe(false)
|
203
|
-
expect(basic['aria-disabled']).toBe(false)
|
204
|
-
expect(basic['aria-readonly']).toBe(false)
|
205
|
-
|
206
|
-
const withError = generateAriaAttributes(null, 'Test Label', null, 'Error message', '', '', 'test-id', false, true, false, false)
|
207
|
-
expect(withError['aria-describedby']).toBe('test-id-helper')
|
208
|
-
expect(withError['aria-invalid']).toBe(true)
|
209
|
-
expect(withError['aria-required']).toBe(true)
|
210
|
-
|
211
|
-
const disabled = generateAriaAttributes(null, 'Test Label', null, '', '', '', 'test-id', true, false, true, true)
|
212
|
-
expect(disabled['aria-disabled']).toBe(true)
|
213
|
-
expect(disabled['aria-readonly']).toBe(true)
|
214
|
-
})
|
215
|
-
|
216
|
-
test('input attributes generation works correctly', () => {
|
217
|
-
const generateInputAttributes = (id, type, isPasswordVisible, placeholder, value, required, disabled, readOnly, maxLength, minLength, pattern, step, min, max, autoComplete, ariaAttributes, restProps) => {
|
218
|
-
return {
|
219
|
-
id,
|
220
|
-
type: type === 'password' && isPasswordVisible ? 'text' : type,
|
221
|
-
placeholder,
|
222
|
-
value: value || '',
|
223
|
-
required,
|
224
|
-
disabled,
|
225
|
-
readOnly,
|
226
|
-
maxLength,
|
227
|
-
minLength,
|
228
|
-
pattern,
|
229
|
-
step,
|
230
|
-
min,
|
231
|
-
max,
|
232
|
-
autoComplete,
|
233
|
-
...ariaAttributes,
|
234
|
-
...restProps
|
235
|
-
}
|
236
|
-
}
|
237
|
-
|
238
|
-
const basic = generateInputAttributes('test-id', 'text', false, 'Enter text', 'value', false, false, false, null, null, null, null, null, null, 'off', {}, {})
|
239
|
-
expect(basic.id).toBe('test-id')
|
240
|
-
expect(basic.type).toBe('text')
|
241
|
-
expect(basic.placeholder).toBe('Enter text')
|
242
|
-
expect(basic.value).toBe('value')
|
243
|
-
|
244
|
-
const password = generateInputAttributes('pwd-id', 'password', true, 'Enter password', 'secret', true, false, false, null, null, null, null, null, null, 'off', {}, {})
|
245
|
-
expect(password.type).toBe('text') // Password visible
|
246
|
-
expect(password.required).toBe(true)
|
247
|
-
|
248
|
-
const passwordHidden = generateInputAttributes('pwd-id', 'password', false, 'Enter password', 'secret', true, false, false, null, null, null, null, null, null, 'off', {}, {})
|
249
|
-
expect(passwordHidden.type).toBe('password') // Password hidden
|
250
|
-
})
|
251
|
-
|
252
|
-
test('debounce logic works correctly', () => {
|
253
|
-
jest.useFakeTimers()
|
254
|
-
|
255
|
-
const mockOnChange = jest.fn()
|
256
|
-
let debounceRef = { current: null }
|
257
|
-
|
258
|
-
const simulateDebounce = (debounceMs, id, value, onChange) => {
|
259
|
-
// Clear previous debounce
|
260
|
-
if (debounceRef.current) {
|
261
|
-
clearTimeout(debounceRef.current)
|
262
|
-
}
|
263
|
-
|
264
|
-
if (debounceMs > 0) {
|
265
|
-
debounceRef.current = setTimeout(() => {
|
266
|
-
if (onChange) onChange(id, value, {})
|
267
|
-
}, debounceMs)
|
268
|
-
} else {
|
269
|
-
if (onChange) onChange(id, value, {})
|
270
|
-
}
|
271
|
-
}
|
272
|
-
|
273
|
-
// No debounce
|
274
|
-
simulateDebounce(0, 'test-id', 'value', mockOnChange)
|
275
|
-
expect(mockOnChange).toHaveBeenCalledWith('test-id', 'value', {})
|
276
|
-
|
277
|
-
mockOnChange.mockClear()
|
278
|
-
|
279
|
-
// With debounce
|
280
|
-
simulateDebounce(300, 'test-id', 'value1', mockOnChange)
|
281
|
-
expect(mockOnChange).not.toHaveBeenCalled()
|
282
|
-
|
283
|
-
jest.advanceTimersByTime(300)
|
284
|
-
expect(mockOnChange).toHaveBeenCalledWith('test-id', 'value1', {})
|
285
|
-
|
286
|
-
mockOnChange.mockClear()
|
287
|
-
|
288
|
-
// Debounce cancellation
|
289
|
-
simulateDebounce(300, 'test-id', 'value2', mockOnChange)
|
290
|
-
simulateDebounce(300, 'test-id', 'value3', mockOnChange)
|
291
|
-
|
292
|
-
jest.advanceTimersByTime(300)
|
293
|
-
expect(mockOnChange).toHaveBeenCalledTimes(1)
|
294
|
-
expect(mockOnChange).toHaveBeenCalledWith('test-id', 'value3', {})
|
295
|
-
|
296
|
-
jest.useRealTimers()
|
297
|
-
})
|
298
|
-
|
299
|
-
test('password visibility toggle works correctly', () => {
|
300
|
-
const togglePassword = (isPasswordVisible) => !isPasswordVisible
|
301
|
-
|
302
|
-
expect(togglePassword(false)).toBe(true)
|
303
|
-
expect(togglePassword(true)).toBe(false)
|
304
|
-
})
|
305
|
-
|
306
|
-
test('clear functionality works correctly', () => {
|
307
|
-
const mockOnChange = jest.fn()
|
308
|
-
|
309
|
-
const simulateClear = (disabled, readOnly, id, onChange) => {
|
310
|
-
if (disabled || readOnly) return
|
311
|
-
|
312
|
-
if (onChange) onChange(id, '', { target: { value: '' } })
|
313
|
-
}
|
314
|
-
|
315
|
-
// Normal clear
|
316
|
-
simulateClear(false, false, 'test-id', mockOnChange)
|
317
|
-
expect(mockOnChange).toHaveBeenCalledWith('test-id', '', { target: { value: '' } })
|
318
|
-
|
319
|
-
mockOnChange.mockClear()
|
320
|
-
|
321
|
-
// Disabled clear
|
322
|
-
simulateClear(true, false, 'test-id', mockOnChange)
|
323
|
-
expect(mockOnChange).not.toHaveBeenCalled()
|
324
|
-
|
325
|
-
// ReadOnly clear
|
326
|
-
simulateClear(false, true, 'test-id', mockOnChange)
|
327
|
-
expect(mockOnChange).not.toHaveBeenCalled()
|
328
|
-
})
|
329
|
-
|
330
|
-
test('focus management works correctly', () => {
|
331
|
-
const mockSite = {
|
332
|
-
changeFocus: jest.fn()
|
333
|
-
}
|
334
|
-
|
335
|
-
const simulateFocus = (disabled, onFocus, site) => {
|
336
|
-
if (disabled) return
|
337
|
-
|
338
|
-
if (onFocus) onFocus({})
|
339
|
-
|
340
|
-
if (site && site.changeFocus) {
|
341
|
-
site.changeFocus({
|
342
|
-
lose: () => {} // setIsFocused(false)
|
343
|
-
})
|
344
|
-
}
|
345
|
-
}
|
346
|
-
|
347
|
-
const mockOnFocus = jest.fn()
|
348
|
-
|
349
|
-
simulateFocus(false, mockOnFocus, mockSite)
|
350
|
-
expect(mockOnFocus).toHaveBeenCalled()
|
351
|
-
expect(mockSite.changeFocus).toHaveBeenCalled()
|
352
|
-
|
353
|
-
mockOnFocus.mockClear()
|
354
|
-
mockSite.changeFocus.mockClear()
|
355
|
-
|
356
|
-
// Disabled focus
|
357
|
-
simulateFocus(true, mockOnFocus, mockSite)
|
358
|
-
expect(mockOnFocus).not.toHaveBeenCalled()
|
359
|
-
expect(mockSite.changeFocus).not.toHaveBeenCalled()
|
360
|
-
})
|
361
|
-
|
362
|
-
test('error and helper text display logic works correctly', () => {
|
363
|
-
const getDisplayText = (error, internalError, helperText) => {
|
364
|
-
const displayError = error || internalError
|
365
|
-
const displayHelperText = helperText && !displayError
|
366
|
-
|
367
|
-
return {
|
368
|
-
displayError,
|
369
|
-
displayHelperText,
|
370
|
-
showHelper: !!(displayError || displayHelperText)
|
371
|
-
}
|
372
|
-
}
|
373
|
-
|
374
|
-
const result1 = getDisplayText('External error', '', 'Helper text')
|
375
|
-
expect(result1.displayError).toBe('External error')
|
376
|
-
expect(result1.displayHelperText).toBe(false)
|
377
|
-
expect(result1.showHelper).toBe(true)
|
378
|
-
|
379
|
-
const result2 = getDisplayText('', 'Internal error', 'Helper text')
|
380
|
-
expect(result2.displayError).toBe('Internal error')
|
381
|
-
expect(result2.displayHelperText).toBe(false)
|
382
|
-
expect(result2.showHelper).toBe(true)
|
383
|
-
|
384
|
-
const result3 = getDisplayText('', '', 'Helper text')
|
385
|
-
expect(result3.displayError).toBe('')
|
386
|
-
expect(result3.displayHelperText).toBe(true)
|
387
|
-
expect(result3.showHelper).toBe(true)
|
388
|
-
|
389
|
-
const result4 = getDisplayText('', '', '')
|
390
|
-
expect(result4.displayError).toBe('')
|
391
|
-
expect(result4.displayHelperText).toBe('')
|
392
|
-
expect(result4.showHelper).toBe(false)
|
393
|
-
})
|
394
|
-
})
|
395
|
-
|
396
|
-
describe('TextArea2 Component', () => {
|
397
|
-
test('component exports correctly', () => {
|
398
|
-
expect(TextArea2).toBeDefined()
|
399
|
-
expect(typeof TextArea2).toBe('function')
|
400
|
-
})
|
401
|
-
|
402
|
-
test('component has correct PropTypes', () => {
|
403
|
-
expect(TextArea2.propTypes).toBeDefined()
|
404
|
-
// Should inherit from TextField2
|
405
|
-
expect(TextArea2.propTypes.id).toBeDefined()
|
406
|
-
expect(TextArea2.propTypes.rows).toBeDefined()
|
407
|
-
})
|
408
|
-
|
409
|
-
test('component has correct defaultProps', () => {
|
410
|
-
expect(TextArea2.defaultProps).toBeDefined()
|
411
|
-
expect(TextArea2.defaultProps.type).toBe('textarea')
|
412
|
-
})
|
413
|
-
})
|
414
|
-
|
415
|
-
describe('PasswordField2 Component', () => {
|
416
|
-
test('component exports correctly', () => {
|
417
|
-
expect(PasswordField2).toBeDefined()
|
418
|
-
expect(typeof PasswordField2).toBe('function')
|
419
|
-
})
|
420
|
-
|
421
|
-
test('component has correct PropTypes', () => {
|
422
|
-
expect(PasswordField2.propTypes).toBeDefined()
|
423
|
-
// Should inherit from TextField2
|
424
|
-
expect(PasswordField2.propTypes.id).toBeDefined()
|
425
|
-
expect(PasswordField2.propTypes.showPasswordToggle).toBeDefined()
|
426
|
-
})
|
427
|
-
|
428
|
-
test('component has correct defaultProps', () => {
|
429
|
-
expect(PasswordField2.defaultProps).toBeDefined()
|
430
|
-
expect(PasswordField2.defaultProps.type).toBe('password')
|
431
|
-
})
|
432
|
-
})
|
433
|
-
|
434
|
-
describe('DropDown2 Component', () => {
|
435
|
-
test('component exports correctly', () => {
|
436
|
-
expect(DropDown2).toBeDefined()
|
437
|
-
expect(typeof DropDown2).toBe('function')
|
438
|
-
})
|
439
|
-
|
440
|
-
test('component has correct PropTypes', () => {
|
441
|
-
expect(DropDown2.propTypes).toBeDefined()
|
442
|
-
expect(DropDown2.propTypes.id).toBeDefined()
|
443
|
-
expect(DropDown2.propTypes.options).toBeDefined()
|
444
|
-
expect(DropDown2.propTypes.value).toBeDefined()
|
445
|
-
expect(DropDown2.propTypes.placeholder).toBeDefined()
|
446
|
-
expect(DropDown2.propTypes.label).toBeDefined()
|
447
|
-
expect(DropDown2.propTypes.outlined).toBeDefined()
|
448
|
-
expect(DropDown2.propTypes.disabled).toBeDefined()
|
449
|
-
expect(DropDown2.propTypes.readOnly).toBeDefined()
|
450
|
-
expect(DropDown2.propTypes.required).toBeDefined()
|
451
|
-
expect(DropDown2.propTypes.searchable).toBeDefined()
|
452
|
-
expect(DropDown2.propTypes.clearable).toBeDefined()
|
453
|
-
expect(DropDown2.propTypes.multiple).toBeDefined()
|
454
|
-
expect(DropDown2.propTypes.onChange).toBeDefined()
|
455
|
-
})
|
456
|
-
|
457
|
-
test('component has correct defaultProps', () => {
|
458
|
-
expect(DropDown2.defaultProps).toBeDefined()
|
459
|
-
expect(DropDown2.defaultProps.options).toEqual([])
|
460
|
-
expect(DropDown2.defaultProps.outlined).toBe(false)
|
461
|
-
expect(DropDown2.defaultProps.disabled).toBe(false)
|
462
|
-
expect(DropDown2.defaultProps.readOnly).toBe(false)
|
463
|
-
expect(DropDown2.defaultProps.required).toBe(false)
|
464
|
-
expect(DropDown2.defaultProps.searchable).toBe(false)
|
465
|
-
expect(DropDown2.defaultProps.clearable).toBe(false)
|
466
|
-
expect(DropDown2.defaultProps.multiple).toBe(false)
|
467
|
-
expect(DropDown2.defaultProps.position).toBe('bottom')
|
468
|
-
expect(DropDown2.defaultProps.maxHeight).toBe('200px')
|
469
|
-
expect(DropDown2.defaultProps.className).toBe('')
|
470
|
-
})
|
471
|
-
|
472
|
-
test('warns when id prop is missing', () => {
|
473
|
-
const validateId = (id) => {
|
474
|
-
if (!id) {
|
475
|
-
console.warn('DropDown2 component: id prop is required')
|
476
|
-
}
|
477
|
-
}
|
478
|
-
|
479
|
-
validateId(null)
|
480
|
-
expect(console.warn).toHaveBeenCalledWith('DropDown2 component: id prop is required')
|
481
|
-
|
482
|
-
console.warn.mockClear()
|
483
|
-
validateId('test-id')
|
484
|
-
expect(console.warn).not.toHaveBeenCalled()
|
485
|
-
})
|
486
|
-
|
487
|
-
test('warns when options is not an array', () => {
|
488
|
-
const validateOptions = (options) => {
|
489
|
-
if (!Array.isArray(options)) {
|
490
|
-
console.warn('DropDown2 component: options must be an array')
|
491
|
-
}
|
492
|
-
}
|
493
|
-
|
494
|
-
validateOptions('not an array')
|
495
|
-
expect(console.warn).toHaveBeenCalledWith('DropDown2 component: options must be an array')
|
496
|
-
|
497
|
-
console.warn.mockClear()
|
498
|
-
validateOptions([])
|
499
|
-
expect(console.warn).not.toHaveBeenCalled()
|
500
|
-
})
|
501
|
-
|
502
|
-
test('display value generation works correctly', () => {
|
503
|
-
const options = [
|
504
|
-
{ value: 'option1', label: 'Option 1' },
|
505
|
-
{ value: 'option2', label: 'Option 2' },
|
506
|
-
{ value: 'option3', label: 'Option 3' }
|
507
|
-
]
|
508
|
-
|
509
|
-
const getDisplayValue = (value, multiple, options, renderValue) => {
|
510
|
-
if (!value) return ''
|
511
|
-
|
512
|
-
if (multiple && Array.isArray(value)) {
|
513
|
-
if (value.length === 0) return ''
|
514
|
-
if (value.length === 1) {
|
515
|
-
const option = options.find(opt => opt.value === value[0])
|
516
|
-
return option ? option.label : value[0]
|
517
|
-
}
|
518
|
-
return `${value.length} items selected`
|
519
|
-
}
|
520
|
-
|
521
|
-
const option = options.find(opt => opt.value === value)
|
522
|
-
if (renderValue && option) {
|
523
|
-
return renderValue(option)
|
524
|
-
}
|
525
|
-
return option ? option.label : value
|
526
|
-
}
|
527
|
-
|
528
|
-
// Single selection
|
529
|
-
expect(getDisplayValue('option1', false, options)).toBe('Option 1')
|
530
|
-
expect(getDisplayValue('unknown', false, options)).toBe('unknown')
|
531
|
-
expect(getDisplayValue('', false, options)).toBe('')
|
532
|
-
|
533
|
-
// Multiple selection
|
534
|
-
expect(getDisplayValue([], true, options)).toBe('')
|
535
|
-
expect(getDisplayValue(['option1'], true, options)).toBe('Option 1')
|
536
|
-
expect(getDisplayValue(['option1', 'option2'], true, options)).toBe('2 items selected')
|
537
|
-
|
538
|
-
// Custom render
|
539
|
-
const customRender = (option) => `Custom: ${option.label}`
|
540
|
-
expect(getDisplayValue('option1', false, options, customRender)).toBe('Custom: Option 1')
|
541
|
-
})
|
542
|
-
|
543
|
-
test('option filtering works correctly', () => {
|
544
|
-
const options = [
|
545
|
-
{ value: 'apple', label: 'Apple' },
|
546
|
-
{ value: 'banana', label: 'Banana' },
|
547
|
-
{ value: 'cherry', label: 'Cherry' }
|
548
|
-
]
|
549
|
-
|
550
|
-
const filterOptions = (options, searchTerm, searchable, filterFunction) => {
|
551
|
-
if (!searchTerm || !searchable) return options
|
552
|
-
|
553
|
-
if (filterFunction) {
|
554
|
-
return options.filter(option => filterFunction(option, searchTerm))
|
555
|
-
}
|
556
|
-
|
557
|
-
return options.filter(option =>
|
558
|
-
option.label.toLowerCase().includes(searchTerm.toLowerCase())
|
559
|
-
)
|
560
|
-
}
|
561
|
-
|
562
|
-
// No search
|
563
|
-
expect(filterOptions(options, 'app', false)).toEqual(options)
|
564
|
-
expect(filterOptions(options, '', true)).toEqual(options)
|
565
|
-
|
566
|
-
// Default filtering
|
567
|
-
expect(filterOptions(options, 'app', true)).toEqual([{ value: 'apple', label: 'Apple' }])
|
568
|
-
expect(filterOptions(options, 'an', true)).toEqual([{ value: 'banana', label: 'Banana' }])
|
569
|
-
|
570
|
-
// Custom filtering
|
571
|
-
const customFilter = (option, term) => option.value.startsWith(term)
|
572
|
-
expect(filterOptions(options, 'a', true, customFilter)).toEqual([{ value: 'apple', label: 'Apple' }])
|
573
|
-
})
|
574
|
-
|
575
|
-
test('option grouping works correctly', () => {
|
576
|
-
const options = [
|
577
|
-
{ value: 'apple', label: 'Apple', category: 'Fruits' },
|
578
|
-
{ value: 'banana', label: 'Banana', category: 'Fruits' },
|
579
|
-
{ value: 'carrot', label: 'Carrot', category: 'Vegetables' }
|
580
|
-
]
|
581
|
-
|
582
|
-
const groupOptions = (options, groupBy) => {
|
583
|
-
if (!groupBy) return [{ options }]
|
584
|
-
|
585
|
-
const groups = options.reduce((acc, option) => {
|
586
|
-
const groupKey = typeof groupBy === 'function' ? groupBy(option) : option[groupBy]
|
587
|
-
if (!acc[groupKey]) {
|
588
|
-
acc[groupKey] = []
|
589
|
-
}
|
590
|
-
acc[groupKey].push(option)
|
591
|
-
return acc
|
592
|
-
}, {})
|
593
|
-
|
594
|
-
return Object.entries(groups).map(([label, options]) => ({ label, options }))
|
595
|
-
}
|
596
|
-
|
597
|
-
// No grouping
|
598
|
-
expect(groupOptions(options)).toEqual([{ options }])
|
599
|
-
|
600
|
-
// Group by property
|
601
|
-
const grouped = groupOptions(options, 'category')
|
602
|
-
expect(grouped).toHaveLength(2)
|
603
|
-
expect(grouped[0].label).toBe('Fruits')
|
604
|
-
expect(grouped[0].options).toHaveLength(2)
|
605
|
-
expect(grouped[1].label).toBe('Vegetables')
|
606
|
-
expect(grouped[1].options).toHaveLength(1)
|
607
|
-
|
608
|
-
// Group by function
|
609
|
-
const groupByFirstLetter = (option) => option.label[0]
|
610
|
-
const groupedByLetter = groupOptions(options, groupByFirstLetter)
|
611
|
-
expect(groupedByLetter).toHaveLength(3) // A, B, C
|
612
|
-
})
|
613
|
-
|
614
|
-
test('selection logic works correctly', () => {
|
615
|
-
const handleSelection = (selectedValue, multiple, currentValue) => {
|
616
|
-
if (multiple) {
|
617
|
-
const currentValues = Array.isArray(currentValue) ? currentValue : []
|
618
|
-
if (currentValues.includes(selectedValue)) {
|
619
|
-
return currentValues.filter(v => v !== selectedValue)
|
620
|
-
} else {
|
621
|
-
return [...currentValues, selectedValue]
|
622
|
-
}
|
623
|
-
} else {
|
624
|
-
return selectedValue
|
625
|
-
}
|
626
|
-
}
|
627
|
-
|
628
|
-
// Single selection
|
629
|
-
expect(handleSelection('option1', false, '')).toBe('option1')
|
630
|
-
expect(handleSelection('option2', false, 'option1')).toBe('option2')
|
631
|
-
|
632
|
-
// Multiple selection - add
|
633
|
-
expect(handleSelection('option1', true, [])).toEqual(['option1'])
|
634
|
-
expect(handleSelection('option2', true, ['option1'])).toEqual(['option1', 'option2'])
|
635
|
-
|
636
|
-
// Multiple selection - remove
|
637
|
-
expect(handleSelection('option1', true, ['option1', 'option2'])).toEqual(['option2'])
|
638
|
-
})
|
639
|
-
|
640
|
-
test('keyboard navigation logic works correctly', () => {
|
641
|
-
const handleKeyNavigation = (key, isOpen, focusedIndex, optionsLength) => {
|
642
|
-
switch (key) {
|
643
|
-
case 'ArrowDown':
|
644
|
-
if (!isOpen) {
|
645
|
-
return { isOpen: true, focusedIndex: -1 }
|
646
|
-
} else {
|
647
|
-
return {
|
648
|
-
isOpen: true,
|
649
|
-
focusedIndex: focusedIndex < optionsLength - 1 ? focusedIndex + 1 : 0
|
650
|
-
}
|
651
|
-
}
|
652
|
-
case 'ArrowUp':
|
653
|
-
if (isOpen) {
|
654
|
-
return {
|
655
|
-
isOpen: true,
|
656
|
-
focusedIndex: focusedIndex > 0 ? focusedIndex - 1 : optionsLength - 1
|
657
|
-
}
|
658
|
-
}
|
659
|
-
return { isOpen, focusedIndex }
|
660
|
-
case 'Escape':
|
661
|
-
return { isOpen: false, focusedIndex: -1 }
|
662
|
-
default:
|
663
|
-
return { isOpen, focusedIndex }
|
664
|
-
}
|
665
|
-
}
|
666
|
-
|
667
|
-
// Arrow down when closed
|
668
|
-
expect(handleKeyNavigation('ArrowDown', false, -1, 3)).toEqual({ isOpen: true, focusedIndex: -1 })
|
669
|
-
|
670
|
-
// Arrow down when open
|
671
|
-
expect(handleKeyNavigation('ArrowDown', true, -1, 3)).toEqual({ isOpen: true, focusedIndex: 0 })
|
672
|
-
expect(handleKeyNavigation('ArrowDown', true, 0, 3)).toEqual({ isOpen: true, focusedIndex: 1 })
|
673
|
-
expect(handleKeyNavigation('ArrowDown', true, 2, 3)).toEqual({ isOpen: true, focusedIndex: 0 }) // Wrap around
|
674
|
-
|
675
|
-
// Arrow up
|
676
|
-
expect(handleKeyNavigation('ArrowUp', true, 1, 3)).toEqual({ isOpen: true, focusedIndex: 0 })
|
677
|
-
expect(handleKeyNavigation('ArrowUp', true, 0, 3)).toEqual({ isOpen: true, focusedIndex: 2 }) // Wrap around
|
678
|
-
|
679
|
-
// Escape
|
680
|
-
expect(handleKeyNavigation('Escape', true, 1, 3)).toEqual({ isOpen: false, focusedIndex: -1 })
|
681
|
-
})
|
682
|
-
|
683
|
-
test('CSS classes generation works correctly', () => {
|
684
|
-
const generateClasses = (outlined, disabled, readOnly, isOpen, error, internalError, multiple, className) => {
|
685
|
-
return [
|
686
|
-
'dropdown2',
|
687
|
-
outlined && 'outlined',
|
688
|
-
disabled && 'disabled',
|
689
|
-
readOnly && 'readonly',
|
690
|
-
isOpen && 'open',
|
691
|
-
error || internalError ? 'error' : '',
|
692
|
-
multiple && 'multiple',
|
693
|
-
className || ''
|
694
|
-
].filter(Boolean).join(' ')
|
695
|
-
}
|
696
|
-
|
697
|
-
expect(generateClasses(false, false, false, false, '', '', false, '')).toBe('dropdown2')
|
698
|
-
expect(generateClasses(true, false, false, false, '', '', false, '')).toBe('dropdown2 outlined')
|
699
|
-
expect(generateClasses(false, true, false, false, '', '', false, '')).toBe('dropdown2 disabled')
|
700
|
-
expect(generateClasses(false, false, true, false, '', '', false, '')).toBe('dropdown2 readonly')
|
701
|
-
expect(generateClasses(false, false, false, true, '', '', false, '')).toBe('dropdown2 open')
|
702
|
-
expect(generateClasses(false, false, false, false, 'Error', '', false, '')).toBe('dropdown2 error')
|
703
|
-
expect(generateClasses(false, false, false, false, '', '', true, '')).toBe('dropdown2 multiple')
|
704
|
-
expect(generateClasses(false, false, false, false, '', '', false, 'custom')).toBe('dropdown2 custom')
|
705
|
-
})
|
706
|
-
|
707
|
-
test('accessibility attributes generation works correctly', () => {
|
708
|
-
const generateAriaAttributes = (ariaLabel, label, isOpen, disabled, readOnly, required, error, internalError, helperText, id) => {
|
709
|
-
return {
|
710
|
-
'aria-label': ariaLabel || label,
|
711
|
-
'aria-expanded': isOpen,
|
712
|
-
'aria-haspopup': 'listbox',
|
713
|
-
'aria-disabled': disabled,
|
714
|
-
'aria-readonly': readOnly,
|
715
|
-
'aria-required': required,
|
716
|
-
'aria-invalid': !!(error || internalError),
|
717
|
-
'aria-describedby': error || internalError || helperText ? `${id}-helper` : undefined
|
718
|
-
}
|
719
|
-
}
|
720
|
-
|
721
|
-
const basic = generateAriaAttributes(null, 'Test Label', false, false, false, false, '', '', '', 'test-id')
|
722
|
-
expect(basic['aria-label']).toBe('Test Label')
|
723
|
-
expect(basic['aria-expanded']).toBe(false)
|
724
|
-
expect(basic['aria-haspopup']).toBe('listbox')
|
725
|
-
expect(basic['aria-disabled']).toBe(false)
|
726
|
-
expect(basic['aria-readonly']).toBe(false)
|
727
|
-
expect(basic['aria-required']).toBe(false)
|
728
|
-
expect(basic['aria-invalid']).toBe(false)
|
729
|
-
expect(basic['aria-describedby']).toBeUndefined()
|
730
|
-
|
731
|
-
const withError = generateAriaAttributes(null, 'Test Label', true, false, false, true, 'Error message', '', '', 'test-id')
|
732
|
-
expect(withError['aria-expanded']).toBe(true)
|
733
|
-
expect(withError['aria-required']).toBe(true)
|
734
|
-
expect(withError['aria-invalid']).toBe(true)
|
735
|
-
expect(withError['aria-describedby']).toBe('test-id-helper')
|
736
|
-
})
|
737
|
-
})
|
738
|
-
|
739
|
-
describe('DateRange2 Component', () => {
|
740
|
-
test('component exports correctly', () => {
|
741
|
-
expect(DateRange2).toBeDefined()
|
742
|
-
expect(typeof DateRange2).toBe('function')
|
743
|
-
})
|
744
|
-
|
745
|
-
test('component has correct PropTypes', () => {
|
746
|
-
expect(DateRange2.propTypes).toBeDefined()
|
747
|
-
expect(DateRange2.propTypes.id).toBeDefined()
|
748
|
-
expect(DateRange2.propTypes.label).toBeDefined()
|
749
|
-
expect(DateRange2.propTypes.value).toBeDefined()
|
750
|
-
expect(DateRange2.propTypes.outlined).toBeDefined()
|
751
|
-
expect(DateRange2.propTypes.disabled).toBeDefined()
|
752
|
-
expect(DateRange2.propTypes.readOnly).toBeDefined()
|
753
|
-
expect(DateRange2.propTypes.required).toBeDefined()
|
754
|
-
expect(DateRange2.propTypes.minDate).toBeDefined()
|
755
|
-
expect(DateRange2.propTypes.maxDate).toBeDefined()
|
756
|
-
expect(DateRange2.propTypes.error).toBeDefined()
|
757
|
-
expect(DateRange2.propTypes.helperText).toBeDefined()
|
758
|
-
expect(DateRange2.propTypes.onChange).toBeDefined()
|
759
|
-
expect(DateRange2.propTypes.onValidation).toBeDefined()
|
760
|
-
})
|
761
|
-
|
762
|
-
test('component has correct defaultProps', () => {
|
763
|
-
expect(DateRange2.defaultProps).toBeDefined()
|
764
|
-
expect(DateRange2.defaultProps.outlined).toBe(false)
|
765
|
-
expect(DateRange2.defaultProps.disabled).toBe(false)
|
766
|
-
expect(DateRange2.defaultProps.readOnly).toBe(false)
|
767
|
-
expect(DateRange2.defaultProps.required).toBe(false)
|
768
|
-
expect(DateRange2.defaultProps.className).toBe('')
|
769
|
-
})
|
770
|
-
|
771
|
-
test('warns when id prop is missing', () => {
|
772
|
-
const validateId = (id) => {
|
773
|
-
if (!id) {
|
774
|
-
console.warn('DateRange2 component: id prop is required')
|
775
|
-
}
|
776
|
-
}
|
777
|
-
|
778
|
-
validateId(null)
|
779
|
-
expect(console.warn).toHaveBeenCalledWith('DateRange2 component: id prop is required')
|
780
|
-
|
781
|
-
console.warn.mockClear()
|
782
|
-
validateId('test-id')
|
783
|
-
expect(console.warn).not.toHaveBeenCalled()
|
784
|
-
})
|
785
|
-
|
786
|
-
test('form initialization from value works correctly', () => {
|
787
|
-
const initializeForm = (value) => {
|
788
|
-
if (value && typeof value === 'object') {
|
789
|
-
return {
|
790
|
-
from: value.from || '',
|
791
|
-
to: value.to || ''
|
792
|
-
}
|
793
|
-
}
|
794
|
-
return { from: '', to: '' }
|
795
|
-
}
|
796
|
-
|
797
|
-
expect(initializeForm(null)).toEqual({ from: '', to: '' })
|
798
|
-
expect(initializeForm({})).toEqual({ from: '', to: '' })
|
799
|
-
expect(initializeForm({ from: '2023-01-01' })).toEqual({ from: '2023-01-01', to: '' })
|
800
|
-
expect(initializeForm({ from: '2023-01-01', to: '2023-12-31' })).toEqual({ from: '2023-01-01', to: '2023-12-31' })
|
801
|
-
})
|
802
|
-
|
803
|
-
test('date range validation works correctly', () => {
|
804
|
-
const validateDateRange = (form, required, minDate, maxDate) => {
|
805
|
-
let valid = true
|
806
|
-
let errorMessage = ''
|
807
|
-
|
808
|
-
if (required && (!form.from || !form.to)) {
|
809
|
-
valid = false
|
810
|
-
errorMessage = 'Both dates are required'
|
811
|
-
} else if (form.from && form.to) {
|
812
|
-
const fromDate = new Date(form.from)
|
813
|
-
const toDate = new Date(form.to)
|
814
|
-
|
815
|
-
if (fromDate > toDate) {
|
816
|
-
valid = false
|
817
|
-
errorMessage = 'From date must be before To date'
|
818
|
-
} else if (minDate && fromDate < new Date(minDate)) {
|
819
|
-
valid = false
|
820
|
-
errorMessage = `From date must be after ${minDate}`
|
821
|
-
} else if (maxDate && toDate > new Date(maxDate)) {
|
822
|
-
valid = false
|
823
|
-
errorMessage = `To date must be before ${maxDate}`
|
824
|
-
}
|
825
|
-
}
|
826
|
-
|
827
|
-
return { valid, errorMessage }
|
828
|
-
}
|
829
|
-
|
830
|
-
// Valid range
|
831
|
-
const result1 = validateDateRange({ from: '2023-01-01', to: '2023-12-31' }, false, null, null)
|
832
|
-
expect(result1.valid).toBe(true)
|
833
|
-
expect(result1.errorMessage).toBe('')
|
834
|
-
|
835
|
-
// Required but empty
|
836
|
-
const result2 = validateDateRange({ from: '', to: '' }, true, null, null)
|
837
|
-
expect(result2.valid).toBe(false)
|
838
|
-
expect(result2.errorMessage).toBe('Both dates are required')
|
839
|
-
|
840
|
-
// From after To
|
841
|
-
const result3 = validateDateRange({ from: '2023-12-31', to: '2023-01-01' }, false, null, null)
|
842
|
-
expect(result3.valid).toBe(false)
|
843
|
-
expect(result3.errorMessage).toBe('From date must be before To date')
|
844
|
-
|
845
|
-
// Before min date
|
846
|
-
const result4 = validateDateRange({ from: '2022-01-01', to: '2023-12-31' }, false, '2023-01-01', null)
|
847
|
-
expect(result4.valid).toBe(false)
|
848
|
-
expect(result4.errorMessage).toBe('From date must be after 2023-01-01')
|
849
|
-
|
850
|
-
// After max date
|
851
|
-
const result5 = validateDateRange({ from: '2023-01-01', to: '2024-12-31' }, false, null, '2024-01-01')
|
852
|
-
expect(result5.valid).toBe(false)
|
853
|
-
expect(result5.errorMessage).toBe('To date must be before 2024-01-01')
|
854
|
-
})
|
855
|
-
|
856
|
-
test('form change handling works correctly', () => {
|
857
|
-
const handleFormChange = (prevForm, fieldId, fieldValue) => {
|
858
|
-
return {
|
859
|
-
...prevForm,
|
860
|
-
[fieldId]: fieldValue
|
861
|
-
}
|
862
|
-
}
|
863
|
-
|
864
|
-
const initialForm = { from: '', to: '' }
|
865
|
-
|
866
|
-
const result1 = handleFormChange(initialForm, 'from', '2023-01-01')
|
867
|
-
expect(result1).toEqual({ from: '2023-01-01', to: '' })
|
868
|
-
|
869
|
-
const result2 = handleFormChange(result1, 'to', '2023-12-31')
|
870
|
-
expect(result2).toEqual({ from: '2023-01-01', to: '2023-12-31' })
|
871
|
-
})
|
872
|
-
|
873
|
-
test('CSS classes generation works correctly', () => {
|
874
|
-
const generateClasses = (outlined, disabled, readOnly, error, internalError, isValid, className) => {
|
875
|
-
return [
|
876
|
-
'date-range2',
|
877
|
-
outlined && 'outlined',
|
878
|
-
disabled && 'disabled',
|
879
|
-
readOnly && 'readonly',
|
880
|
-
error || internalError ? 'error' : '',
|
881
|
-
!isValid && 'invalid',
|
882
|
-
className || ''
|
883
|
-
].filter(Boolean).join(' ')
|
884
|
-
}
|
885
|
-
|
886
|
-
expect(generateClasses(false, false, false, '', '', true, '')).toBe('date-range2')
|
887
|
-
expect(generateClasses(true, false, false, '', '', true, '')).toBe('date-range2 outlined')
|
888
|
-
expect(generateClasses(false, true, false, '', '', true, '')).toBe('date-range2 disabled')
|
889
|
-
expect(generateClasses(false, false, true, '', '', true, '')).toBe('date-range2 readonly')
|
890
|
-
expect(generateClasses(false, false, false, 'Error', '', true, '')).toBe('date-range2 error')
|
891
|
-
expect(generateClasses(false, false, false, '', '', false, '')).toBe('date-range2 invalid')
|
892
|
-
expect(generateClasses(false, false, false, '', '', true, 'custom')).toBe('date-range2 custom')
|
893
|
-
})
|
894
|
-
|
895
|
-
test('accessibility attributes generation works correctly', () => {
|
896
|
-
const generateAriaAttributes = (ariaLabel, label, isValid, error, internalError, required, disabled, readOnly, helperText, id) => {
|
897
|
-
return {
|
898
|
-
'aria-label': ariaLabel || label,
|
899
|
-
'aria-invalid': !isValid || !!(error || internalError),
|
900
|
-
'aria-required': required,
|
901
|
-
'aria-disabled': disabled,
|
902
|
-
'aria-readonly': readOnly,
|
903
|
-
'aria-describedby': error || internalError || helperText ? `${id}-helper` : undefined
|
904
|
-
}
|
905
|
-
}
|
906
|
-
|
907
|
-
const basic = generateAriaAttributes(null, 'Date Range', true, '', '', false, false, false, '', 'test-id')
|
908
|
-
expect(basic['aria-label']).toBe('Date Range')
|
909
|
-
expect(basic['aria-invalid']).toBe(false)
|
910
|
-
expect(basic['aria-required']).toBe(false)
|
911
|
-
expect(basic['aria-disabled']).toBe(false)
|
912
|
-
expect(basic['aria-readonly']).toBe(false)
|
913
|
-
expect(basic['aria-describedby']).toBeUndefined()
|
914
|
-
|
915
|
-
const withError = generateAriaAttributes(null, 'Date Range', false, 'Error message', '', true, false, false, '', 'test-id')
|
916
|
-
expect(withError['aria-invalid']).toBe(true)
|
917
|
-
expect(withError['aria-required']).toBe(true)
|
918
|
-
expect(withError['aria-describedby']).toBe('test-id-helper')
|
919
|
-
})
|
920
|
-
|
921
|
-
test('min/max date constraints work correctly', () => {
|
922
|
-
const getDateConstraints = (form, minDate, maxDate) => {
|
923
|
-
return {
|
924
|
-
fromMin: minDate,
|
925
|
-
fromMax: form.to || maxDate,
|
926
|
-
toMin: form.from || minDate,
|
927
|
-
toMax: maxDate
|
928
|
-
}
|
929
|
-
}
|
930
|
-
|
931
|
-
const form1 = { from: '', to: '' }
|
932
|
-
const constraints1 = getDateConstraints(form1, '2023-01-01', '2023-12-31')
|
933
|
-
expect(constraints1).toEqual({
|
934
|
-
fromMin: '2023-01-01',
|
935
|
-
fromMax: '2023-12-31',
|
936
|
-
toMin: '2023-01-01',
|
937
|
-
toMax: '2023-12-31'
|
938
|
-
})
|
939
|
-
|
940
|
-
const form2 = { from: '2023-06-01', to: '2023-09-01' }
|
941
|
-
const constraints2 = getDateConstraints(form2, '2023-01-01', '2023-12-31')
|
942
|
-
expect(constraints2).toEqual({
|
943
|
-
fromMin: '2023-01-01',
|
944
|
-
fromMax: '2023-09-01',
|
945
|
-
toMin: '2023-06-01',
|
946
|
-
toMax: '2023-12-31'
|
947
|
-
})
|
948
|
-
})
|
949
|
-
})
|
950
|
-
})
|