ywana-core8 0.1.75 → 0.1.77
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/ACCORDION_EVALUATION.md +583 -0
- package/CHECKBOX_EVALUATION.md +273 -0
- package/CHIP_EVALUATION.md +542 -0
- package/COLOR_EVALUATION.md +524 -0
- package/COMPONENTS_EVALUATION.md +477 -0
- package/FORM_EVALUATION.md +459 -0
- package/HEADER_EVALUATION.md +436 -0
- package/ICON_EVALUATION.md +254 -0
- package/LIST_EVALUATION.md +574 -0
- package/PROGRESS_EVALUATION.md +450 -0
- package/RADIO_EVALUATION.md +439 -0
- package/RADIO_VISUAL_FIX.md +183 -0
- package/SECTION_IMPROVEMENTS.md +153 -0
- package/SWITCH_EVALUATION.md +335 -0
- package/SWITCH_VISUAL_FIX.md +232 -0
- package/TAB_EVALUATION.md +626 -0
- package/TEXTFIELD_EVALUATION.md +747 -0
- package/TOOLTIP_FIX.md +157 -0
- package/TREE_EVALUATION.md +708 -0
- package/dist/index.cjs +10893 -1969
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +7768 -1096
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +10921 -2005
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +10893 -1969
- package/dist/index.umd.js.map +1 -1
- package/jest.config.js +24 -0
- package/package.json +10 -1
- package/src/html/accordion.css +208 -4
- package/src/html/accordion.example.js +390 -0
- package/src/html/accordion.js +284 -28
- package/src/html/accordion.unit.test.js +334 -0
- package/src/html/button.css +157 -16
- package/src/html/button.example.js +374 -0
- package/src/html/button.js +240 -60
- package/src/html/button.test.js +422 -0
- package/src/html/checkbox.css +74 -2
- package/src/html/checkbox.example.js +316 -0
- package/src/html/checkbox.js +113 -26
- package/src/html/checkbox.test.js +285 -0
- package/src/html/chip.css +230 -19
- package/src/html/chip.example.js +355 -0
- package/src/html/chip.js +321 -25
- package/src/html/chip.test.js +425 -0
- package/src/html/color.css +435 -6
- package/src/html/color.example.js +527 -0
- package/src/html/color.js +458 -9
- package/src/html/color.test.js +362 -4
- package/src/html/components.example.js +492 -0
- package/src/html/components_enhanced.test.js +581 -0
- package/src/html/form.css +70 -3
- package/src/html/form.example.js +385 -0
- package/src/html/form.js +232 -34
- package/src/html/form.test.js +369 -0
- package/src/html/header2.css +264 -0
- package/src/html/header2.example.js +411 -0
- package/src/html/header2.js +203 -0
- package/src/html/header2.test.js +377 -0
- package/src/html/icon.css +20 -2
- package/src/html/icon.example.js +268 -0
- package/src/html/icon.js +86 -16
- package/src/html/icon.test.js +231 -0
- package/src/html/index.js +4 -1
- package/src/html/list.css +393 -1
- package/src/html/list.example.js +404 -0
- package/src/html/list.js +583 -40
- package/src/html/list.test.js +383 -0
- package/src/html/progress.css +707 -17
- package/src/html/progress.example.js +424 -0
- package/src/html/progress.js +906 -9
- package/src/html/progress.test.js +313 -0
- package/src/html/property.css +399 -0
- package/src/html/property.example.js +553 -0
- package/src/html/property.js +393 -15
- package/src/html/property.test.js +351 -2
- package/src/html/radio-visual-test.js +289 -0
- package/src/html/radio.css +137 -11
- package/src/html/radio.example.js +389 -0
- package/src/html/radio.js +234 -10
- package/src/html/radio.test.js +318 -0
- package/src/html/section.example.js +99 -0
- package/src/html/section.js +40 -3
- package/src/html/section.test.js +131 -0
- package/src/html/selector.css +329 -3
- package/src/html/selector.js +369 -23
- package/src/html/switch-debug.js +197 -0
- package/src/html/switch-test-visual.js +294 -0
- package/src/html/switch.css +200 -0
- package/src/html/switch.example.js +461 -0
- package/src/html/switch.js +283 -23
- package/src/html/switch.test.js +355 -0
- package/src/html/tab.css +289 -0
- package/src/html/tab.example.js +446 -0
- package/src/html/tab.js +387 -22
- package/src/html/tab_enhanced.js +378 -0
- package/src/html/tab_enhanced.test.js +504 -0
- package/src/html/table2.css +576 -0
- package/src/html/table2.example.js +703 -0
- package/src/html/table2.js +1252 -0
- package/src/html/table2.migration.md +328 -0
- package/src/html/table2.test.js +582 -0
- package/src/html/text.css +375 -0
- package/src/html/text.js +311 -20
- package/src/html/textfield2.css +841 -0
- package/src/html/textfield2.example.js +1370 -0
- package/src/html/textfield2.js +1143 -0
- package/src/html/textfield2.test.js +950 -0
- package/src/html/thumbnail.css +289 -2
- package/src/html/thumbnail.js +214 -9
- package/src/html/tokenfield.css +449 -1
- package/src/html/tokenfield.example.js +503 -0
- package/src/html/tokenfield.js +561 -56
- package/src/html/tokenfield.test.js +423 -0
- package/src/html/tooltip-positioning-demo.js +187 -0
- package/src/html/tooltip.css +25 -2
- package/src/html/tree.css +240 -10
- package/src/html/tree.example.js +475 -0
- package/src/html/tree.js +714 -28
- package/src/html/tree_enhanced.test.js +495 -0
- package/table2.test.js +454 -0
- package/src/html/button.tsx +0 -38
package/src/html/switch.js
CHANGED
@@ -1,46 +1,306 @@
|
|
1
|
-
import React from 'react'
|
1
|
+
import React, { useState, useEffect } from 'react'
|
2
|
+
import PropTypes from 'prop-types'
|
2
3
|
import RSwitch from 'react-switch'
|
3
4
|
import { Icon } from './icon'
|
5
|
+
import { Text } from './text'
|
6
|
+
import './switch.css'
|
4
7
|
|
8
|
+
/**
|
9
|
+
* Switch component using react-switch library with enhanced features
|
10
|
+
*/
|
5
11
|
export const Switch = (props) => {
|
12
|
+
const {
|
13
|
+
id,
|
14
|
+
label,
|
15
|
+
checked = false,
|
16
|
+
disabled = false,
|
17
|
+
readOnly = false,
|
18
|
+
error = false,
|
19
|
+
required = false,
|
20
|
+
size = 'normal',
|
21
|
+
className,
|
22
|
+
ariaLabel,
|
23
|
+
onChange,
|
24
|
+
onColor,
|
25
|
+
offColor,
|
26
|
+
...restProps
|
27
|
+
} = props
|
6
28
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
29
|
+
// Validate required props
|
30
|
+
if (onChange && typeof onChange !== 'function') {
|
31
|
+
console.warn('Switch component: onChange prop should be a function')
|
32
|
+
}
|
33
|
+
|
34
|
+
// Size configurations
|
35
|
+
const sizeConfig = {
|
36
|
+
small: { height: 16, width: 32, handleDiameter: 14 },
|
37
|
+
normal: { height: 20, width: 48, handleDiameter: 18 },
|
38
|
+
large: { height: 24, width: 56, handleDiameter: 22 }
|
39
|
+
}
|
40
|
+
|
41
|
+
const currentSize = sizeConfig[size] || sizeConfig.normal
|
42
|
+
const safeClassName = className || ''
|
43
|
+
|
44
|
+
// Theme-aware colors with proper fallbacks
|
45
|
+
const defaultOnColor = onColor || '#007bff' // Bootstrap primary blue
|
46
|
+
const defaultOffColor = offColor || '#6c757d' // Bootstrap secondary gray
|
47
|
+
|
48
|
+
// Handle change with validation
|
49
|
+
const handleChange = (nextChecked, event, id) => {
|
50
|
+
if (!disabled && !readOnly && onChange) {
|
51
|
+
onChange(id || 'switch', nextChecked, event)
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
// Accessibility attributes
|
56
|
+
const ariaAttributes = {
|
57
|
+
'aria-label': ariaLabel || label || 'Toggle switch',
|
58
|
+
'aria-required': required,
|
59
|
+
'aria-invalid': error,
|
60
|
+
'aria-describedby': error && typeof error === 'string' ? `${id}-error` : undefined
|
61
|
+
}
|
62
|
+
|
63
|
+
const switchElement = (
|
64
|
+
<RSwitch
|
65
|
+
id={id}
|
66
|
+
checked={checked}
|
67
|
+
onChange={handleChange}
|
68
|
+
disabled={disabled || readOnly}
|
69
|
+
onColor={defaultOnColor}
|
70
|
+
offColor={defaultOffColor}
|
71
|
+
onHandleColor="#ffffff"
|
72
|
+
offHandleColor="#f8f9fa"
|
73
|
+
handleDiameter={currentSize.handleDiameter}
|
12
74
|
uncheckedIcon={false}
|
13
75
|
checkedIcon={false}
|
14
|
-
boxShadow="0px 1px
|
15
|
-
activeBoxShadow="0px 0px
|
16
|
-
height={
|
17
|
-
width={
|
18
|
-
|
19
|
-
{...
|
20
|
-
|
21
|
-
className="react-switch"
|
76
|
+
boxShadow="0px 1px 3px rgba(0, 0, 0, 0.3)"
|
77
|
+
activeBoxShadow="0px 0px 0px 2px rgba(0, 123, 255, 0.25)"
|
78
|
+
height={currentSize.height}
|
79
|
+
width={currentSize.width}
|
80
|
+
className={`react-switch ${safeClassName} ${error ? 'error' : ''}`}
|
81
|
+
{...ariaAttributes}
|
82
|
+
{...restProps}
|
22
83
|
/>
|
23
84
|
)
|
85
|
+
|
86
|
+
// If no label, return just the switch
|
87
|
+
if (!label) {
|
88
|
+
return (
|
89
|
+
<div className={`switch-wrapper ${disabled ? 'disabled' : ''} ${error ? 'error' : ''}`}>
|
90
|
+
{switchElement}
|
91
|
+
{error && typeof error === 'string' && error.length > 0 && (
|
92
|
+
<span id={`${id}-error`} className="error-message" role="alert">
|
93
|
+
{error}
|
94
|
+
</span>
|
95
|
+
)}
|
96
|
+
</div>
|
97
|
+
)
|
98
|
+
}
|
99
|
+
|
100
|
+
// Return switch with label
|
101
|
+
return (
|
102
|
+
<div className={`switch-container ${disabled ? 'disabled' : ''} ${error ? 'error' : ''}`}>
|
103
|
+
<label htmlFor={id} className="switch-label">
|
104
|
+
<Text>{label}</Text>
|
105
|
+
{switchElement}
|
106
|
+
</label>
|
107
|
+
{error && typeof error === 'string' && error.length > 0 && (
|
108
|
+
<span id={`${id}-error`} className="error-message" role="alert">
|
109
|
+
{error}
|
110
|
+
</span>
|
111
|
+
)}
|
112
|
+
</div>
|
113
|
+
)
|
24
114
|
}
|
25
115
|
|
116
|
+
/**
|
117
|
+
* Switch2 component using Material Icons for a lightweight alternative
|
118
|
+
*/
|
26
119
|
export const Switch2 = (props) => {
|
120
|
+
const {
|
121
|
+
id,
|
122
|
+
label,
|
123
|
+
checked = false,
|
124
|
+
disabled = false,
|
125
|
+
readOnly = false,
|
126
|
+
error = false,
|
127
|
+
required = false,
|
128
|
+
size = 'normal',
|
129
|
+
className,
|
130
|
+
ariaLabel,
|
131
|
+
onChange
|
132
|
+
} = props
|
133
|
+
|
134
|
+
const [internalChecked, setInternalChecked] = useState(checked)
|
135
|
+
|
136
|
+
// Validate required props
|
137
|
+
if (onChange && typeof onChange !== 'function') {
|
138
|
+
console.warn('Switch2 component: onChange prop should be a function')
|
139
|
+
}
|
27
140
|
|
28
|
-
|
141
|
+
// Sync with external value
|
142
|
+
useEffect(() => {
|
143
|
+
setInternalChecked(checked)
|
144
|
+
}, [checked])
|
29
145
|
|
146
|
+
// Handle toggle
|
30
147
|
function toggle() {
|
31
|
-
|
148
|
+
if (!disabled && !readOnly) {
|
149
|
+
const nextValue = !internalChecked
|
150
|
+
setInternalChecked(nextValue)
|
151
|
+
if (onChange) {
|
152
|
+
onChange(id || 'switch2', nextValue)
|
153
|
+
}
|
154
|
+
}
|
32
155
|
}
|
33
156
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
157
|
+
// Handle keyboard events
|
158
|
+
const handleKeyDown = (event) => {
|
159
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
160
|
+
event.preventDefault()
|
161
|
+
toggle()
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
const icon = internalChecked ? "toggle_on" : "toggle_off"
|
166
|
+
const safeClassName = className || ''
|
167
|
+
|
168
|
+
// Generate CSS classes
|
169
|
+
const cssClasses = [
|
170
|
+
'switch2',
|
171
|
+
disabled && 'disabled',
|
172
|
+
readOnly && 'readonly',
|
173
|
+
error && 'error',
|
174
|
+
safeClassName
|
175
|
+
].filter(Boolean).join(' ')
|
176
|
+
|
177
|
+
// Accessibility attributes for the container
|
178
|
+
const containerAttributes = {
|
179
|
+
role: 'switch',
|
180
|
+
'aria-checked': internalChecked,
|
181
|
+
'aria-label': ariaLabel || label || 'Toggle switch',
|
182
|
+
'aria-required': required,
|
183
|
+
'aria-invalid': error,
|
184
|
+
'aria-describedby': error && typeof error === 'string' ? `${id}-error` : undefined,
|
185
|
+
tabIndex: disabled ? -1 : 0,
|
186
|
+
onKeyDown: handleKeyDown
|
187
|
+
}
|
188
|
+
|
189
|
+
const switchElement = (
|
190
|
+
<div className={cssClasses} {...containerAttributes}>
|
191
|
+
<Icon
|
192
|
+
icon={icon}
|
193
|
+
size={size}
|
194
|
+
clickable={!disabled && !readOnly}
|
195
|
+
disabled={disabled}
|
196
|
+
action={toggle}
|
197
|
+
ariaLabel={`${internalChecked ? 'Enabled' : 'Disabled'} toggle switch`}
|
198
|
+
/>
|
38
199
|
</div>
|
39
200
|
)
|
40
|
-
}
|
41
201
|
|
42
|
-
|
202
|
+
// If no label, return just the switch
|
203
|
+
if (!label) {
|
204
|
+
return (
|
205
|
+
<div className="switch2-wrapper">
|
206
|
+
{switchElement}
|
207
|
+
{error && typeof error === 'string' && error.length > 0 && (
|
208
|
+
<span id={`${id}-error`} className="error-message" role="alert">
|
209
|
+
{error}
|
210
|
+
</span>
|
211
|
+
)}
|
212
|
+
</div>
|
213
|
+
)
|
214
|
+
}
|
215
|
+
|
216
|
+
// Return switch with label
|
43
217
|
return (
|
44
|
-
<
|
218
|
+
<div className="switch2-container">
|
219
|
+
<label className="switch2-label">
|
220
|
+
<Text>{label}</Text>
|
221
|
+
{switchElement}
|
222
|
+
</label>
|
223
|
+
{error && typeof error === 'string' && error.length > 0 && (
|
224
|
+
<span id={`${id}-error`} className="error-message" role="alert">
|
225
|
+
{error}
|
226
|
+
</span>
|
227
|
+
)}
|
228
|
+
</div>
|
45
229
|
)
|
46
230
|
}
|
231
|
+
|
232
|
+
// PropTypes para Switch
|
233
|
+
Switch.propTypes = {
|
234
|
+
/** Unique identifier for the switch */
|
235
|
+
id: PropTypes.string,
|
236
|
+
/** Label text for the switch */
|
237
|
+
label: PropTypes.string,
|
238
|
+
/** Current checked state */
|
239
|
+
checked: PropTypes.bool,
|
240
|
+
/** Whether the switch is disabled */
|
241
|
+
disabled: PropTypes.bool,
|
242
|
+
/** Whether the switch is read-only */
|
243
|
+
readOnly: PropTypes.bool,
|
244
|
+
/** Error state - boolean or error message string */
|
245
|
+
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
246
|
+
/** Whether the switch is required */
|
247
|
+
required: PropTypes.bool,
|
248
|
+
/** Size variant of the switch */
|
249
|
+
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
250
|
+
/** Additional CSS classes */
|
251
|
+
className: PropTypes.string,
|
252
|
+
/** Accessibility label for screen readers */
|
253
|
+
ariaLabel: PropTypes.string,
|
254
|
+
/** Change handler function */
|
255
|
+
onChange: PropTypes.func,
|
256
|
+
/** Custom color when switch is on */
|
257
|
+
onColor: PropTypes.string,
|
258
|
+
/** Custom color when switch is off */
|
259
|
+
offColor: PropTypes.string
|
260
|
+
}
|
261
|
+
|
262
|
+
Switch.defaultProps = {
|
263
|
+
checked: false,
|
264
|
+
disabled: false,
|
265
|
+
readOnly: false,
|
266
|
+
error: false,
|
267
|
+
required: false,
|
268
|
+
size: 'normal',
|
269
|
+
className: ''
|
270
|
+
}
|
271
|
+
|
272
|
+
// PropTypes para Switch2
|
273
|
+
Switch2.propTypes = {
|
274
|
+
/** Unique identifier for the switch */
|
275
|
+
id: PropTypes.string,
|
276
|
+
/** Label text for the switch */
|
277
|
+
label: PropTypes.string,
|
278
|
+
/** Current checked state */
|
279
|
+
checked: PropTypes.bool,
|
280
|
+
/** Whether the switch is disabled */
|
281
|
+
disabled: PropTypes.bool,
|
282
|
+
/** Whether the switch is read-only */
|
283
|
+
readOnly: PropTypes.bool,
|
284
|
+
/** Error state - boolean or error message string */
|
285
|
+
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
286
|
+
/** Whether the switch is required */
|
287
|
+
required: PropTypes.bool,
|
288
|
+
/** Size variant of the switch */
|
289
|
+
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
290
|
+
/** Additional CSS classes */
|
291
|
+
className: PropTypes.string,
|
292
|
+
/** Accessibility label for screen readers */
|
293
|
+
ariaLabel: PropTypes.string,
|
294
|
+
/** Change handler function */
|
295
|
+
onChange: PropTypes.func
|
296
|
+
}
|
297
|
+
|
298
|
+
Switch2.defaultProps = {
|
299
|
+
checked: false,
|
300
|
+
disabled: false,
|
301
|
+
readOnly: false,
|
302
|
+
error: false,
|
303
|
+
required: false,
|
304
|
+
size: 'normal',
|
305
|
+
className: ''
|
306
|
+
}
|
@@ -0,0 +1,355 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { Switch, Switch2 } from './switch'
|
3
|
+
|
4
|
+
// Mock react-switch
|
5
|
+
jest.mock('react-switch', () => {
|
6
|
+
return function MockRSwitch(props) {
|
7
|
+
const mockReact = require('react')
|
8
|
+
return mockReact.createElement('div', {
|
9
|
+
'data-testid': 'react-switch',
|
10
|
+
'data-checked': props.checked,
|
11
|
+
'data-disabled': props.disabled,
|
12
|
+
onClick: () => props.onChange && props.onChange(!props.checked)
|
13
|
+
}, 'MockSwitch')
|
14
|
+
}
|
15
|
+
})
|
16
|
+
|
17
|
+
// Pruebas unitarias para los componentes Switch
|
18
|
+
describe('Switch Components', () => {
|
19
|
+
// Mock de los componentes dependientes
|
20
|
+
const mockText = jest.fn()
|
21
|
+
const mockIcon = jest.fn()
|
22
|
+
|
23
|
+
beforeEach(() => {
|
24
|
+
jest.clearAllMocks()
|
25
|
+
|
26
|
+
// Mock del componente Text
|
27
|
+
jest.doMock('./text', () => ({
|
28
|
+
Text: mockText
|
29
|
+
}))
|
30
|
+
|
31
|
+
// Mock del componente Icon
|
32
|
+
jest.doMock('./icon', () => ({
|
33
|
+
Icon: mockIcon
|
34
|
+
}))
|
35
|
+
|
36
|
+
// Mock de console.warn para las pruebas
|
37
|
+
jest.spyOn(console, 'warn').mockImplementation(() => {})
|
38
|
+
})
|
39
|
+
|
40
|
+
afterEach(() => {
|
41
|
+
console.warn.mockRestore()
|
42
|
+
})
|
43
|
+
|
44
|
+
describe('Switch Component', () => {
|
45
|
+
test('component exports correctly', () => {
|
46
|
+
expect(Switch).toBeDefined()
|
47
|
+
expect(typeof Switch).toBe('function')
|
48
|
+
})
|
49
|
+
|
50
|
+
test('component has correct PropTypes', () => {
|
51
|
+
expect(Switch.propTypes).toBeDefined()
|
52
|
+
expect(Switch.propTypes.id).toBeDefined()
|
53
|
+
expect(Switch.propTypes.label).toBeDefined()
|
54
|
+
expect(Switch.propTypes.checked).toBeDefined()
|
55
|
+
expect(Switch.propTypes.disabled).toBeDefined()
|
56
|
+
expect(Switch.propTypes.readOnly).toBeDefined()
|
57
|
+
expect(Switch.propTypes.error).toBeDefined()
|
58
|
+
expect(Switch.propTypes.required).toBeDefined()
|
59
|
+
expect(Switch.propTypes.size).toBeDefined()
|
60
|
+
expect(Switch.propTypes.className).toBeDefined()
|
61
|
+
expect(Switch.propTypes.ariaLabel).toBeDefined()
|
62
|
+
expect(Switch.propTypes.onChange).toBeDefined()
|
63
|
+
expect(Switch.propTypes.onColor).toBeDefined()
|
64
|
+
expect(Switch.propTypes.offColor).toBeDefined()
|
65
|
+
})
|
66
|
+
|
67
|
+
test('component has correct defaultProps', () => {
|
68
|
+
expect(Switch.defaultProps).toBeDefined()
|
69
|
+
expect(Switch.defaultProps.checked).toBe(false)
|
70
|
+
expect(Switch.defaultProps.disabled).toBe(false)
|
71
|
+
expect(Switch.defaultProps.readOnly).toBe(false)
|
72
|
+
expect(Switch.defaultProps.error).toBe(false)
|
73
|
+
expect(Switch.defaultProps.required).toBe(false)
|
74
|
+
expect(Switch.defaultProps.size).toBe('normal')
|
75
|
+
expect(Switch.defaultProps.className).toBe('')
|
76
|
+
})
|
77
|
+
|
78
|
+
test('warns when onChange is not a function', () => {
|
79
|
+
const validateOnChange = (onChange) => {
|
80
|
+
if (onChange && typeof onChange !== 'function') {
|
81
|
+
console.warn('Switch component: onChange prop should be a function')
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
validateOnChange('not a function')
|
86
|
+
expect(console.warn).toHaveBeenCalledWith('Switch component: onChange prop should be a function')
|
87
|
+
|
88
|
+
console.warn.mockClear()
|
89
|
+
validateOnChange(() => {})
|
90
|
+
expect(console.warn).not.toHaveBeenCalled()
|
91
|
+
})
|
92
|
+
|
93
|
+
test('size configuration works correctly', () => {
|
94
|
+
const sizeConfig = {
|
95
|
+
small: { height: 16, width: 32, handleDiameter: 14 },
|
96
|
+
normal: { height: 20, width: 48, handleDiameter: 18 },
|
97
|
+
large: { height: 24, width: 56, handleDiameter: 22 }
|
98
|
+
}
|
99
|
+
|
100
|
+
expect(sizeConfig.small.height).toBe(16)
|
101
|
+
expect(sizeConfig.normal.width).toBe(48)
|
102
|
+
expect(sizeConfig.large.handleDiameter).toBe(22)
|
103
|
+
})
|
104
|
+
|
105
|
+
test('change handler works correctly', () => {
|
106
|
+
const mockOnChange = jest.fn()
|
107
|
+
|
108
|
+
// Simular el comportamiento del change handler
|
109
|
+
const simulateChange = (disabled, readOnly, onChange, id) => {
|
110
|
+
const nextChecked = true
|
111
|
+
const event = {}
|
112
|
+
|
113
|
+
if (!disabled && !readOnly && onChange) {
|
114
|
+
onChange(id || 'switch', nextChecked, event)
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
// Change normal
|
119
|
+
simulateChange(false, false, mockOnChange, 'test')
|
120
|
+
expect(mockOnChange).toHaveBeenCalledWith('test', true, {})
|
121
|
+
|
122
|
+
mockOnChange.mockClear()
|
123
|
+
|
124
|
+
// Change con disabled
|
125
|
+
simulateChange(true, false, mockOnChange, 'test')
|
126
|
+
expect(mockOnChange).not.toHaveBeenCalled()
|
127
|
+
|
128
|
+
// Change con readOnly
|
129
|
+
simulateChange(false, true, mockOnChange, 'test')
|
130
|
+
expect(mockOnChange).not.toHaveBeenCalled()
|
131
|
+
})
|
132
|
+
|
133
|
+
test('accessibility attributes generation works correctly', () => {
|
134
|
+
const generateAriaAttributes = (ariaLabel, label, required, error, id) => {
|
135
|
+
return {
|
136
|
+
'aria-label': ariaLabel || label || 'Toggle switch',
|
137
|
+
'aria-required': required,
|
138
|
+
'aria-invalid': error,
|
139
|
+
'aria-describedby': error && typeof error === 'string' ? `${id}-error` : undefined
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
// Atributos básicos
|
144
|
+
const basic = generateAriaAttributes(null, null, false, false, 'test')
|
145
|
+
expect(basic['aria-label']).toBe('Toggle switch')
|
146
|
+
expect(basic['aria-required']).toBe(false)
|
147
|
+
expect(basic['aria-invalid']).toBe(false)
|
148
|
+
expect(basic['aria-describedby']).toBeUndefined()
|
149
|
+
|
150
|
+
// Con label personalizado
|
151
|
+
const withLabel = generateAriaAttributes(null, 'Custom Label', false, false, 'test')
|
152
|
+
expect(withLabel['aria-label']).toBe('Custom Label')
|
153
|
+
|
154
|
+
// Con aria-label personalizado
|
155
|
+
const withAriaLabel = generateAriaAttributes('Custom ARIA', 'Label', false, false, 'test')
|
156
|
+
expect(withAriaLabel['aria-label']).toBe('Custom ARIA')
|
157
|
+
|
158
|
+
// Con error
|
159
|
+
const withError = generateAriaAttributes(null, null, false, 'Error message', 'test')
|
160
|
+
expect(withError['aria-invalid']).toBe('Error message')
|
161
|
+
expect(withError['aria-describedby']).toBe('test-error')
|
162
|
+
})
|
163
|
+
})
|
164
|
+
|
165
|
+
describe('Switch2 Component', () => {
|
166
|
+
test('component exports correctly', () => {
|
167
|
+
expect(Switch2).toBeDefined()
|
168
|
+
expect(typeof Switch2).toBe('function')
|
169
|
+
})
|
170
|
+
|
171
|
+
test('component has correct PropTypes', () => {
|
172
|
+
expect(Switch2.propTypes).toBeDefined()
|
173
|
+
expect(Switch2.propTypes.id).toBeDefined()
|
174
|
+
expect(Switch2.propTypes.label).toBeDefined()
|
175
|
+
expect(Switch2.propTypes.checked).toBeDefined()
|
176
|
+
expect(Switch2.propTypes.disabled).toBeDefined()
|
177
|
+
expect(Switch2.propTypes.readOnly).toBeDefined()
|
178
|
+
expect(Switch2.propTypes.error).toBeDefined()
|
179
|
+
expect(Switch2.propTypes.required).toBeDefined()
|
180
|
+
expect(Switch2.propTypes.size).toBeDefined()
|
181
|
+
expect(Switch2.propTypes.className).toBeDefined()
|
182
|
+
expect(Switch2.propTypes.ariaLabel).toBeDefined()
|
183
|
+
expect(Switch2.propTypes.onChange).toBeDefined()
|
184
|
+
})
|
185
|
+
|
186
|
+
test('component has correct defaultProps', () => {
|
187
|
+
expect(Switch2.defaultProps).toBeDefined()
|
188
|
+
expect(Switch2.defaultProps.checked).toBe(false)
|
189
|
+
expect(Switch2.defaultProps.disabled).toBe(false)
|
190
|
+
expect(Switch2.defaultProps.readOnly).toBe(false)
|
191
|
+
expect(Switch2.defaultProps.error).toBe(false)
|
192
|
+
expect(Switch2.defaultProps.required).toBe(false)
|
193
|
+
expect(Switch2.defaultProps.size).toBe('normal')
|
194
|
+
expect(Switch2.defaultProps.className).toBe('')
|
195
|
+
})
|
196
|
+
|
197
|
+
test('warns when onChange is not a function', () => {
|
198
|
+
const validateOnChange = (onChange) => {
|
199
|
+
if (onChange && typeof onChange !== 'function') {
|
200
|
+
console.warn('Switch2 component: onChange prop should be a function')
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
validateOnChange('not a function')
|
205
|
+
expect(console.warn).toHaveBeenCalledWith('Switch2 component: onChange prop should be a function')
|
206
|
+
|
207
|
+
console.warn.mockClear()
|
208
|
+
validateOnChange(() => {})
|
209
|
+
expect(console.warn).not.toHaveBeenCalled()
|
210
|
+
})
|
211
|
+
|
212
|
+
test('icon selection logic works correctly', () => {
|
213
|
+
const getIcon = (checked) => {
|
214
|
+
return checked ? "toggle_on" : "toggle_off"
|
215
|
+
}
|
216
|
+
|
217
|
+
expect(getIcon(true)).toBe('toggle_on')
|
218
|
+
expect(getIcon(false)).toBe('toggle_off')
|
219
|
+
})
|
220
|
+
|
221
|
+
test('CSS classes generation works correctly', () => {
|
222
|
+
const generateClasses = (disabled, readOnly, error, className) => {
|
223
|
+
return [
|
224
|
+
'switch2',
|
225
|
+
disabled && 'disabled',
|
226
|
+
readOnly && 'readonly',
|
227
|
+
error && 'error',
|
228
|
+
className || ''
|
229
|
+
].filter(Boolean).join(' ')
|
230
|
+
}
|
231
|
+
|
232
|
+
expect(generateClasses(false, false, false, '')).toBe('switch2')
|
233
|
+
expect(generateClasses(true, false, false, '')).toBe('switch2 disabled')
|
234
|
+
expect(generateClasses(false, true, false, '')).toBe('switch2 readonly')
|
235
|
+
expect(generateClasses(false, false, true, '')).toBe('switch2 error')
|
236
|
+
expect(generateClasses(false, false, false, 'custom')).toBe('switch2 custom')
|
237
|
+
expect(generateClasses(true, true, true, 'custom')).toBe('switch2 disabled readonly error custom')
|
238
|
+
})
|
239
|
+
|
240
|
+
test('toggle function works correctly', () => {
|
241
|
+
const mockOnChange = jest.fn()
|
242
|
+
|
243
|
+
// Simular el comportamiento del toggle
|
244
|
+
const simulateToggle = (disabled, readOnly, checked, onChange, id) => {
|
245
|
+
if (!disabled && !readOnly) {
|
246
|
+
const nextValue = !checked
|
247
|
+
if (onChange) {
|
248
|
+
onChange(id || 'switch2', nextValue)
|
249
|
+
}
|
250
|
+
return nextValue
|
251
|
+
}
|
252
|
+
return checked
|
253
|
+
}
|
254
|
+
|
255
|
+
// Toggle normal
|
256
|
+
const result1 = simulateToggle(false, false, false, mockOnChange, 'test')
|
257
|
+
expect(result1).toBe(true)
|
258
|
+
expect(mockOnChange).toHaveBeenCalledWith('test', true)
|
259
|
+
|
260
|
+
mockOnChange.mockClear()
|
261
|
+
|
262
|
+
// Toggle con disabled
|
263
|
+
const result2 = simulateToggle(true, false, false, mockOnChange, 'test')
|
264
|
+
expect(result2).toBe(false)
|
265
|
+
expect(mockOnChange).not.toHaveBeenCalled()
|
266
|
+
|
267
|
+
// Toggle con readOnly
|
268
|
+
const result3 = simulateToggle(false, true, false, mockOnChange, 'test')
|
269
|
+
expect(result3).toBe(false)
|
270
|
+
expect(mockOnChange).not.toHaveBeenCalled()
|
271
|
+
})
|
272
|
+
|
273
|
+
test('keyboard event handling works correctly', () => {
|
274
|
+
const mockToggle = jest.fn()
|
275
|
+
|
276
|
+
// Simular el manejo de eventos de teclado
|
277
|
+
const simulateKeyDown = (key, toggle) => {
|
278
|
+
const event = {
|
279
|
+
key,
|
280
|
+
preventDefault: jest.fn()
|
281
|
+
}
|
282
|
+
|
283
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
284
|
+
event.preventDefault()
|
285
|
+
toggle()
|
286
|
+
}
|
287
|
+
|
288
|
+
return event
|
289
|
+
}
|
290
|
+
|
291
|
+
// Space key
|
292
|
+
const spaceEvent = simulateKeyDown(' ', mockToggle)
|
293
|
+
expect(spaceEvent.preventDefault).toHaveBeenCalled()
|
294
|
+
expect(mockToggle).toHaveBeenCalled()
|
295
|
+
|
296
|
+
mockToggle.mockClear()
|
297
|
+
|
298
|
+
// Enter key
|
299
|
+
const enterEvent = simulateKeyDown('Enter', mockToggle)
|
300
|
+
expect(enterEvent.preventDefault).toHaveBeenCalled()
|
301
|
+
expect(mockToggle).toHaveBeenCalled()
|
302
|
+
|
303
|
+
mockToggle.mockClear()
|
304
|
+
|
305
|
+
// Other key (should not trigger)
|
306
|
+
const otherEvent = simulateKeyDown('a', mockToggle)
|
307
|
+
expect(otherEvent.preventDefault).not.toHaveBeenCalled()
|
308
|
+
expect(mockToggle).not.toHaveBeenCalled()
|
309
|
+
})
|
310
|
+
|
311
|
+
test('container attributes generation works correctly', () => {
|
312
|
+
const generateContainerAttributes = (checked, ariaLabel, label, required, error, id, disabled) => {
|
313
|
+
return {
|
314
|
+
role: 'switch',
|
315
|
+
'aria-checked': checked,
|
316
|
+
'aria-label': ariaLabel || label || 'Toggle switch',
|
317
|
+
'aria-required': required,
|
318
|
+
'aria-invalid': error,
|
319
|
+
'aria-describedby': error && typeof error === 'string' ? `${id}-error` : undefined,
|
320
|
+
tabIndex: disabled ? -1 : 0
|
321
|
+
}
|
322
|
+
}
|
323
|
+
|
324
|
+
const attrs = generateContainerAttributes(true, null, 'Test Label', false, false, 'test', false)
|
325
|
+
expect(attrs.role).toBe('switch')
|
326
|
+
expect(attrs['aria-checked']).toBe(true)
|
327
|
+
expect(attrs['aria-label']).toBe('Test Label')
|
328
|
+
expect(attrs.tabIndex).toBe(0)
|
329
|
+
|
330
|
+
const disabledAttrs = generateContainerAttributes(false, null, null, false, false, 'test', true)
|
331
|
+
expect(disabledAttrs.tabIndex).toBe(-1)
|
332
|
+
})
|
333
|
+
|
334
|
+
test('state synchronization works correctly', () => {
|
335
|
+
// Simular la sincronización de estado con useEffect
|
336
|
+
let internalState = false
|
337
|
+
|
338
|
+
const syncState = (externalValue) => {
|
339
|
+
internalState = externalValue
|
340
|
+
}
|
341
|
+
|
342
|
+
// Estado inicial
|
343
|
+
syncState(false)
|
344
|
+
expect(internalState).toBe(false)
|
345
|
+
|
346
|
+
// Cambio externo
|
347
|
+
syncState(true)
|
348
|
+
expect(internalState).toBe(true)
|
349
|
+
|
350
|
+
// Otro cambio
|
351
|
+
syncState(false)
|
352
|
+
expect(internalState).toBe(false)
|
353
|
+
})
|
354
|
+
})
|
355
|
+
})
|