ywana-core8 0.1.74 → 0.1.76

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.
Files changed (123) hide show
  1. package/ACCORDION_EVALUATION.md +583 -0
  2. package/CHECKBOX_EVALUATION.md +273 -0
  3. package/CHIP_EVALUATION.md +542 -0
  4. package/COLOR_EVALUATION.md +524 -0
  5. package/COMPONENTS_EVALUATION.md +477 -0
  6. package/FORM_EVALUATION.md +459 -0
  7. package/HEADER_EVALUATION.md +436 -0
  8. package/ICON_EVALUATION.md +254 -0
  9. package/LIST_EVALUATION.md +574 -0
  10. package/PROGRESS_EVALUATION.md +450 -0
  11. package/RADIO_EVALUATION.md +439 -0
  12. package/RADIO_VISUAL_FIX.md +183 -0
  13. package/SECTION_IMPROVEMENTS.md +153 -0
  14. package/SWITCH_EVALUATION.md +335 -0
  15. package/SWITCH_VISUAL_FIX.md +232 -0
  16. package/TAB_EVALUATION.md +626 -0
  17. package/TEXTFIELD_EVALUATION.md +747 -0
  18. package/TOOLTIP_FIX.md +157 -0
  19. package/TREE_EVALUATION.md +708 -0
  20. package/dist/index.cjs +7900 -1615
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.css +6094 -1122
  23. package/dist/index.css.map +1 -1
  24. package/dist/index.modern.js +7929 -1645
  25. package/dist/index.modern.js.map +1 -1
  26. package/dist/index.umd.js +7900 -1615
  27. package/dist/index.umd.js.map +1 -1
  28. package/jest.config.js +24 -0
  29. package/package.json +10 -1
  30. package/src/html/accordion.css +208 -4
  31. package/src/html/accordion.example.js +390 -0
  32. package/src/html/accordion.js +284 -28
  33. package/src/html/accordion.unit.test.js +334 -0
  34. package/src/html/button.css +157 -16
  35. package/src/html/button.example.js +374 -0
  36. package/src/html/button.js +240 -60
  37. package/src/html/button.test.js +422 -0
  38. package/src/html/checkbox.css +74 -2
  39. package/src/html/checkbox.example.js +316 -0
  40. package/src/html/checkbox.js +113 -26
  41. package/src/html/checkbox.test.js +285 -0
  42. package/src/html/chip.css +230 -19
  43. package/src/html/chip.example.js +355 -0
  44. package/src/html/chip.js +321 -25
  45. package/src/html/chip.test.js +425 -0
  46. package/src/html/color.css +435 -6
  47. package/src/html/color.example.js +527 -0
  48. package/src/html/color.js +458 -9
  49. package/src/html/color.test.js +362 -4
  50. package/src/html/components.example.js +492 -0
  51. package/src/html/components_enhanced.test.js +581 -0
  52. package/src/html/form.css +70 -3
  53. package/src/html/form.example.js +385 -0
  54. package/src/html/form.js +232 -34
  55. package/src/html/form.test.js +369 -0
  56. package/src/html/header2.css +264 -0
  57. package/src/html/header2.example.js +411 -0
  58. package/src/html/header2.js +203 -0
  59. package/src/html/header2.test.js +377 -0
  60. package/src/html/icon.css +20 -2
  61. package/src/html/icon.example.js +268 -0
  62. package/src/html/icon.js +86 -16
  63. package/src/html/icon.test.js +231 -0
  64. package/src/html/index.js +1 -1
  65. package/src/html/list.css +393 -1
  66. package/src/html/list.example.js +404 -0
  67. package/src/html/list.js +583 -40
  68. package/src/html/list.test.js +383 -0
  69. package/src/html/progress.css +707 -17
  70. package/src/html/progress.example.js +424 -0
  71. package/src/html/progress.js +906 -9
  72. package/src/html/progress.test.js +313 -0
  73. package/src/html/property.css +399 -0
  74. package/src/html/property.example.js +553 -0
  75. package/src/html/property.js +393 -15
  76. package/src/html/property.test.js +351 -2
  77. package/src/html/radio-visual-test.js +289 -0
  78. package/src/html/radio.css +137 -11
  79. package/src/html/radio.example.js +389 -0
  80. package/src/html/radio.js +234 -10
  81. package/src/html/radio.test.js +318 -0
  82. package/src/html/section.example.js +99 -0
  83. package/src/html/section.js +40 -3
  84. package/src/html/section.test.js +131 -0
  85. package/src/html/selector.css +329 -3
  86. package/src/html/selector.js +369 -23
  87. package/src/html/switch-debug.js +197 -0
  88. package/src/html/switch-test-visual.js +294 -0
  89. package/src/html/switch.css +200 -0
  90. package/src/html/switch.example.js +461 -0
  91. package/src/html/switch.js +283 -23
  92. package/src/html/switch.test.js +355 -0
  93. package/src/html/tab.css +288 -0
  94. package/src/html/tab.example.js +446 -0
  95. package/src/html/tab.js +387 -22
  96. package/src/html/tab_enhanced.js +378 -0
  97. package/src/html/tab_enhanced.test.js +504 -0
  98. package/src/html/table2.css +576 -0
  99. package/src/html/table2.example.js +703 -0
  100. package/src/html/table2.js +1252 -0
  101. package/src/html/table2.migration.md +328 -0
  102. package/src/html/table2.test.js +582 -0
  103. package/src/html/text.css +375 -0
  104. package/src/html/text.js +311 -20
  105. package/src/html/textfield.js +1 -1
  106. package/src/html/textfield2.css +842 -0
  107. package/src/html/textfield2.example.js +499 -0
  108. package/src/html/textfield2.js +1130 -0
  109. package/src/html/textfield2.test.js +950 -0
  110. package/src/html/thumbnail.css +289 -2
  111. package/src/html/thumbnail.js +214 -9
  112. package/src/html/tokenfield.css +449 -1
  113. package/src/html/tokenfield.example.js +503 -0
  114. package/src/html/tokenfield.js +561 -56
  115. package/src/html/tokenfield.test.js +423 -0
  116. package/src/html/tooltip-positioning-demo.js +187 -0
  117. package/src/html/tooltip.css +25 -2
  118. package/src/html/tree.css +228 -0
  119. package/src/html/tree.example.js +475 -0
  120. package/src/html/tree.js +712 -28
  121. package/src/html/tree_enhanced.test.js +495 -0
  122. package/table2.test.js +454 -0
  123. package/src/html/button.tsx +0 -38
@@ -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
- return (
8
- <RSwitch
9
- onColor="#86d3ff"
10
- onHandleColor="#2693e6"
11
- handleDiameter={30}
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 5px rgba(0, 0, 0, 0.6)"
15
- activeBoxShadow="0px 0px 1px 10px rgba(0, 0, 0, 0.2)"
16
- height={20}
17
- width={48}
18
-
19
- {...props}
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
- const { checked, onChange } = props
141
+ // Sync with external value
142
+ useEffect(() => {
143
+ setInternalChecked(checked)
144
+ }, [checked])
29
145
 
146
+ // Handle toggle
30
147
  function toggle() {
31
- onChange(!checked)
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
- const icon = checked ? "toggle_on" : "toggle_off"
35
- return (
36
- <div className="switch">
37
- <Icon icon={icon} clickable action={toggle} />
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
- const Switch2Test = () => {
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
- <Switch2 checked={true} onChange={console.log} />
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
+ })