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,41 +1,116 @@
1
- import React, {useState, useEffect} from 'react'
1
+ import React, { useState, useEffect, useCallback } from 'react'
2
+ import PropTypes from 'prop-types'
2
3
  import { Icon } from './icon'
3
4
  import './accordion.css'
4
5
 
5
6
  /**
6
- * Accordion
7
+ * Enhanced Accordion component with improved accessibility and robustness
7
8
  */
8
9
  export const Accordion = (props) => {
10
+ const {
11
+ className,
12
+ sections = [],
13
+ disabled = false,
14
+ allowMultiple = true,
15
+ animated = true,
16
+ onCheck,
17
+ onToggle,
18
+ onSectionChange,
19
+ ariaLabel,
20
+ ...restProps
21
+ } = props
9
22
 
10
- const { className, sections = [], onCheck } = props
11
23
  const [openSections, setOpenSections] = useState([])
12
24
  const [checkedSections, setCheckedSections] = useState([])
13
25
 
26
+ // Validate props
27
+ if (!Array.isArray(sections)) {
28
+ console.warn('Accordion component: sections prop must be an array')
29
+ }
30
+
31
+ // Initialize sections state
14
32
  useEffect(() => {
15
- const openSections = sections.map(section => section.open)
16
- const checkedSections = sections.map(section => section.checked)
33
+ const openSections = sections.map(section => section.open || false)
34
+ const checkedSections = sections.map(section => section.checked || false)
17
35
  setOpenSections(openSections)
18
36
  setCheckedSections(checkedSections)
19
37
  }, [sections])
20
38
 
21
- function toggle(index) {
22
- const next = openSections.map((open, i) => i === index ? !open : open)
23
- setOpenSections(next)
24
- }
39
+ // Toggle section open/closed
40
+ const toggle = useCallback((index) => {
41
+ if (disabled) return
42
+
43
+ setOpenSections(prevOpen => {
44
+ const next = allowMultiple
45
+ ? prevOpen.map((open, i) => i === index ? !open : open)
46
+ : prevOpen.map((open, i) => i === index ? !open : false)
47
+
48
+ if (onToggle) {
49
+ onToggle(index, next[index], sections[index])
50
+ }
51
+
52
+ if (onSectionChange) {
53
+ onSectionChange('toggle', index, next[index], sections[index])
54
+ }
55
+
56
+ return next
57
+ })
58
+ }, [disabled, allowMultiple, onToggle, onSectionChange, sections])
59
+
60
+ // Toggle section checked state
61
+ const check = useCallback((index) => {
62
+ if (disabled) return
63
+
64
+ setCheckedSections(prevChecked => {
65
+ const next = prevChecked.map((checked, i) => i === index ? !checked : checked)
66
+
67
+ if (onCheck) {
68
+ onCheck(index, next[index], sections[index]?.id, sections[index])
69
+ }
25
70
 
26
- function check(index) {
27
- const next = checkedSections.map((checked, i) => i === index ? !checked : checked)
28
- setCheckedSections(next)
29
- if (onCheck) onCheck(index, next[index], sections[index].id)
71
+ if (onSectionChange) {
72
+ onSectionChange('check', index, next[index], sections[index])
73
+ }
74
+
75
+ return next
76
+ })
77
+ }, [disabled, onCheck, onSectionChange, sections])
78
+
79
+ // Generate CSS classes
80
+ const safeClassName = className || ''
81
+ const cssClasses = [
82
+ 'accordion',
83
+ disabled && 'disabled',
84
+ animated && 'animated',
85
+ safeClassName
86
+ ].filter(Boolean).join(' ')
87
+
88
+ // Accessibility attributes
89
+ const ariaAttributes = {
90
+ 'aria-label': ariaLabel || 'Accordion',
91
+ 'aria-disabled': disabled,
92
+ role: 'region'
30
93
  }
31
94
 
32
95
  return (
33
- <div className={`accordion ${className}`}>
96
+ <div className={cssClasses} {...ariaAttributes} {...restProps}>
34
97
  {sections.map((section, index) => {
35
98
  const isOpen = openSections[index]
36
99
  const isChecked = checkedSections[index]
100
+ const sectionId = section.id || `accordion-section-${index}`
101
+
37
102
  return (
38
- <AccordionSection key={index} {...section} open={isOpen} checked={isChecked} onToggle={() => toggle(index)} onCheck={() => check(index)}>
103
+ <AccordionSection
104
+ key={sectionId}
105
+ {...section}
106
+ open={isOpen}
107
+ checked={isChecked}
108
+ disabled={disabled || section.disabled}
109
+ animated={animated}
110
+ sectionIndex={index}
111
+ onToggle={() => toggle(index)}
112
+ onCheck={() => check(index)}
113
+ >
39
114
  {section.children}
40
115
  </AccordionSection>
41
116
  )
@@ -48,23 +123,204 @@ export const Accordion = (props) => {
48
123
  * AccordionSection
49
124
  */
50
125
  const AccordionSection = (props) => {
126
+ const {
127
+ checked,
128
+ icon,
129
+ title,
130
+ subtitle,
131
+ open = false,
132
+ disabled = false,
133
+ animated = true,
134
+ sectionIndex,
135
+ onToggle,
136
+ onCheck,
137
+ toolbar,
138
+ info,
139
+ children,
140
+ ariaLabel,
141
+ ...restProps
142
+ } = props
51
143
 
52
- const { checked, icon, title, subtitle, open = false, onToggle, onCheck, toolbar, info, children } = props
53
144
  const togglerIcon = open ? "expand_less" : "expand_more"
54
- const checkedIcon = checked === undefined || checked === null ? null : checked === false ? "check_box_outline_blank" : "check_box"
145
+ const checkedIcon = checked === undefined || checked === null ?
146
+ null :
147
+ checked === false ? "check_box_outline_blank" : "check_box"
148
+
149
+ // Handle keyboard navigation
150
+ const handleKeyDown = useCallback((event) => {
151
+ if (disabled) return
152
+
153
+ switch (event.key) {
154
+ case 'Enter':
155
+ case ' ':
156
+ event.preventDefault()
157
+ if (onToggle) onToggle()
158
+ break
159
+ case 'ArrowDown':
160
+ case 'ArrowUp':
161
+ // Allow parent to handle navigation between sections
162
+ break
163
+ default:
164
+ break
165
+ }
166
+ }, [disabled, onToggle])
167
+
168
+ // Handle header click
169
+ const handleHeaderClick = useCallback((event) => {
170
+ if (disabled) return
171
+
172
+ // Don't toggle if clicking on interactive elements
173
+ if (event.target.closest('.accordion-section-checker') ||
174
+ event.target.closest('.accordion-section--toolbar')) {
175
+ return
176
+ }
177
+
178
+ if (onToggle) onToggle()
179
+ }, [disabled, onToggle])
180
+
181
+ // Generate CSS classes
182
+ const cssClasses = [
183
+ 'accordion-section',
184
+ open && 'open',
185
+ disabled && 'disabled',
186
+ animated && 'animated',
187
+ checked && 'checked'
188
+ ].filter(Boolean).join(' ')
189
+
190
+ // Generate unique IDs for accessibility
191
+ const headerId = `accordion-header-${sectionIndex}`
192
+ const contentId = `accordion-content-${sectionIndex}`
193
+
194
+ // Accessibility attributes for header
195
+ const headerAriaAttributes = {
196
+ 'aria-expanded': open,
197
+ 'aria-controls': contentId,
198
+ 'aria-disabled': disabled,
199
+ 'aria-label': ariaLabel || (typeof title === 'string' ? title : 'Accordion section'),
200
+ role: 'button',
201
+ tabIndex: disabled ? -1 : 0,
202
+ id: headerId
203
+ }
204
+
205
+ // Accessibility attributes for content
206
+ const contentAriaAttributes = {
207
+ 'aria-labelledby': headerId,
208
+ 'aria-hidden': !open,
209
+ role: 'region',
210
+ id: contentId
211
+ }
55
212
 
56
213
  return (
57
- <section key={title} className={`accordion-section`}>
58
- <header>
59
- { checkedIcon ? <Icon className="accordion-section-checker" icon={checkedIcon} clickable action={onCheck}/> : '' }
60
- { icon ? <Icon className="accordion-section-icon" icon={icon} /> : '' }
61
- { title ? <div className="accordion-section--title">{title}</div> : '' }
62
- { subtitle ? <div className="accordion-section--subtitle">{subtitle}</div> : '' }
63
- { info ? <div className="accordion-section--info">{info}</div> : '' }
64
- { toolbar ? <div className="accordion-section--toolbar">{toolbar}</div> : '' }
65
- <Icon className="accordion-section-toggler" icon={togglerIcon} clickable action={onToggle} />
66
- </header>
67
- {open ? <main>{children}</main> : ''}
214
+ <section className={cssClasses} {...restProps}>
215
+ <header
216
+ className="accordion-section-header"
217
+ onClick={handleHeaderClick}
218
+ onKeyDown={handleKeyDown}
219
+ {...headerAriaAttributes}
220
+ >
221
+ {checkedIcon && (
222
+ <Icon
223
+ className="accordion-section-checker"
224
+ icon={checkedIcon}
225
+ clickable={!disabled}
226
+ action={disabled ? undefined : onCheck}
227
+ disabled={disabled}
228
+ ariaLabel={checked ? 'Uncheck section' : 'Check section'}
229
+ />
230
+ )}
231
+
232
+ {icon && (
233
+ <Icon
234
+ className="accordion-section-icon"
235
+ icon={icon}
236
+ ariaLabel={`${icon} icon`}
237
+ />
238
+ )}
239
+
240
+ {title && (
241
+ <div className="accordion-section--title">
242
+ {title}
243
+ </div>
244
+ )}
245
+
246
+ {subtitle && (
247
+ <div className="accordion-section--subtitle">
248
+ {subtitle}
249
+ </div>
250
+ )}
251
+
252
+ {info && (
253
+ <div className="accordion-section--info">
254
+ {info}
255
+ </div>
256
+ )}
257
+
258
+ {toolbar && (
259
+ <div className="accordion-section--toolbar" role="toolbar">
260
+ {toolbar}
261
+ </div>
262
+ )}
263
+
264
+ <Icon
265
+ className="accordion-section-toggler"
266
+ icon={togglerIcon}
267
+ clickable={!disabled}
268
+ action={disabled ? undefined : onToggle}
269
+ disabled={disabled}
270
+ ariaLabel={open ? 'Collapse section' : 'Expand section'}
271
+ />
272
+ </header>
273
+
274
+ {open && (
275
+ <main
276
+ className="accordion-section-content"
277
+ {...contentAriaAttributes}
278
+ >
279
+ {children}
280
+ </main>
281
+ )}
68
282
  </section>
69
283
  )
70
284
  }
285
+
286
+ // PropTypes for Accordion
287
+ Accordion.propTypes = {
288
+ /** Array of section objects */
289
+ sections: PropTypes.arrayOf(PropTypes.shape({
290
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
291
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
292
+ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
293
+ icon: PropTypes.string,
294
+ info: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
295
+ toolbar: PropTypes.node,
296
+ children: PropTypes.node,
297
+ open: PropTypes.bool,
298
+ checked: PropTypes.bool,
299
+ disabled: PropTypes.bool
300
+ })).isRequired,
301
+ /** Additional CSS classes */
302
+ className: PropTypes.string,
303
+ /** Whether accordion is disabled */
304
+ disabled: PropTypes.bool,
305
+ /** Whether multiple sections can be open */
306
+ allowMultiple: PropTypes.bool,
307
+ /** Whether to animate transitions */
308
+ animated: PropTypes.bool,
309
+ /** ARIA label for the accordion */
310
+ ariaLabel: PropTypes.string,
311
+ /** Callback when section is checked/unchecked */
312
+ onCheck: PropTypes.func,
313
+ /** Callback when section is toggled */
314
+ onToggle: PropTypes.func,
315
+ /** Callback when any section changes */
316
+ onSectionChange: PropTypes.func
317
+ }
318
+
319
+ Accordion.defaultProps = {
320
+ className: '',
321
+ disabled: false,
322
+ allowMultiple: true,
323
+ animated: true
324
+ }
325
+
326
+ export default Accordion
@@ -0,0 +1,334 @@
1
+ import React from 'react'
2
+ import { Accordion } from './accordion'
3
+
4
+ // Pruebas unitarias para el componente Accordion mejorado
5
+ describe('Accordion Component', () => {
6
+ // Mock de los componentes dependientes
7
+ const mockIcon = jest.fn()
8
+
9
+ beforeEach(() => {
10
+ jest.clearAllMocks()
11
+
12
+ // Mock del componente Icon
13
+ jest.doMock('./icon', () => ({
14
+ Icon: mockIcon
15
+ }))
16
+
17
+ // Mock de console.warn para las pruebas
18
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
19
+ })
20
+
21
+ afterEach(() => {
22
+ console.warn.mockRestore()
23
+ })
24
+
25
+ test('component exports correctly', () => {
26
+ expect(Accordion).toBeDefined()
27
+ expect(typeof Accordion).toBe('function')
28
+ })
29
+
30
+ test('component has correct PropTypes', () => {
31
+ expect(Accordion.propTypes).toBeDefined()
32
+ expect(Accordion.propTypes.sections).toBeDefined()
33
+ expect(Accordion.propTypes.className).toBeDefined()
34
+ expect(Accordion.propTypes.disabled).toBeDefined()
35
+ expect(Accordion.propTypes.allowMultiple).toBeDefined()
36
+ expect(Accordion.propTypes.animated).toBeDefined()
37
+ expect(Accordion.propTypes.ariaLabel).toBeDefined()
38
+ expect(Accordion.propTypes.onCheck).toBeDefined()
39
+ expect(Accordion.propTypes.onToggle).toBeDefined()
40
+ expect(Accordion.propTypes.onSectionChange).toBeDefined()
41
+ })
42
+
43
+ test('component has correct defaultProps', () => {
44
+ expect(Accordion.defaultProps).toBeDefined()
45
+ expect(Accordion.defaultProps.className).toBe('')
46
+ expect(Accordion.defaultProps.disabled).toBe(false)
47
+ expect(Accordion.defaultProps.allowMultiple).toBe(true)
48
+ expect(Accordion.defaultProps.animated).toBe(true)
49
+ })
50
+
51
+ test('warns when sections is not an array', () => {
52
+ const validateSections = (sections) => {
53
+ if (!Array.isArray(sections)) {
54
+ console.warn('Accordion component: sections prop must be an array')
55
+ }
56
+ }
57
+
58
+ validateSections('not an array')
59
+ expect(console.warn).toHaveBeenCalledWith('Accordion component: sections prop must be an array')
60
+
61
+ console.warn.mockClear()
62
+ validateSections([])
63
+ expect(console.warn).not.toHaveBeenCalled()
64
+ })
65
+
66
+ test('sections state initialization works correctly', () => {
67
+ const sections = [
68
+ { id: 1, open: true, checked: false },
69
+ { id: 2, open: false, checked: true },
70
+ { id: 3, open: true, checked: true }
71
+ ]
72
+
73
+ const initializeState = (sections) => {
74
+ const openSections = sections.map(section => section.open || false)
75
+ const checkedSections = sections.map(section => section.checked || false)
76
+ return { openSections, checkedSections }
77
+ }
78
+
79
+ const result = initializeState(sections)
80
+ expect(result.openSections).toEqual([true, false, true])
81
+ expect(result.checkedSections).toEqual([false, true, true])
82
+ })
83
+
84
+ test('toggle logic works correctly with allowMultiple=true', () => {
85
+ const openSections = [true, false, true]
86
+ const allowMultiple = true
87
+ const index = 1
88
+
89
+ const toggle = (openSections, index, allowMultiple) => {
90
+ return allowMultiple
91
+ ? openSections.map((open, i) => i === index ? !open : open)
92
+ : openSections.map((open, i) => i === index ? !open : false)
93
+ }
94
+
95
+ const result = toggle(openSections, index, allowMultiple)
96
+ expect(result).toEqual([true, true, true])
97
+ })
98
+
99
+ test('toggle logic works correctly with allowMultiple=false', () => {
100
+ const openSections = [true, false, true]
101
+ const allowMultiple = false
102
+ const index = 1
103
+
104
+ const toggle = (openSections, index, allowMultiple) => {
105
+ return allowMultiple
106
+ ? openSections.map((open, i) => i === index ? !open : open)
107
+ : openSections.map((open, i) => i === index ? !open : false)
108
+ }
109
+
110
+ const result = toggle(openSections, index, allowMultiple)
111
+ expect(result).toEqual([false, true, false])
112
+ })
113
+
114
+ test('check logic works correctly', () => {
115
+ const checkedSections = [false, true, false]
116
+ const index = 0
117
+
118
+ const check = (checkedSections, index) => {
119
+ return checkedSections.map((checked, i) => i === index ? !checked : checked)
120
+ }
121
+
122
+ const result = check(checkedSections, index)
123
+ expect(result).toEqual([true, true, false])
124
+ })
125
+
126
+ test('CSS classes generation works correctly', () => {
127
+ const generateClasses = (disabled, animated, className) => {
128
+ return [
129
+ 'accordion',
130
+ disabled && 'disabled',
131
+ animated && 'animated',
132
+ className || ''
133
+ ].filter(Boolean).join(' ')
134
+ }
135
+
136
+ expect(generateClasses(false, false, '')).toBe('accordion')
137
+ expect(generateClasses(true, false, '')).toBe('accordion disabled')
138
+ expect(generateClasses(false, true, '')).toBe('accordion animated')
139
+ expect(generateClasses(true, true, 'custom')).toBe('accordion disabled animated custom')
140
+ })
141
+
142
+ test('accessibility attributes generation works correctly', () => {
143
+ const generateAriaAttributes = (ariaLabel, disabled) => {
144
+ return {
145
+ 'aria-label': ariaLabel || 'Accordion',
146
+ 'aria-disabled': disabled,
147
+ role: 'region'
148
+ }
149
+ }
150
+
151
+ const result1 = generateAriaAttributes(null, false)
152
+ expect(result1['aria-label']).toBe('Accordion')
153
+ expect(result1['aria-disabled']).toBe(false)
154
+ expect(result1.role).toBe('region')
155
+
156
+ const result2 = generateAriaAttributes('Custom Accordion', true)
157
+ expect(result2['aria-label']).toBe('Custom Accordion')
158
+ expect(result2['aria-disabled']).toBe(true)
159
+ })
160
+
161
+ test('section ID generation works correctly', () => {
162
+ const generateSectionId = (section, index) => {
163
+ return section.id || `accordion-section-${index}`
164
+ }
165
+
166
+ expect(generateSectionId({ id: 'custom-id' }, 0)).toBe('custom-id')
167
+ expect(generateSectionId({}, 0)).toBe('accordion-section-0')
168
+ expect(generateSectionId({ id: null }, 1)).toBe('accordion-section-1')
169
+ })
170
+
171
+ test('callback handling works correctly', () => {
172
+ const mockOnCheck = jest.fn()
173
+ const mockOnToggle = jest.fn()
174
+ const mockOnSectionChange = jest.fn()
175
+
176
+ const simulateCheck = (index, newValue, section, onCheck, onSectionChange) => {
177
+ if (onCheck) {
178
+ onCheck(index, newValue, section?.id, section)
179
+ }
180
+
181
+ if (onSectionChange) {
182
+ onSectionChange('check', index, newValue, section)
183
+ }
184
+ }
185
+
186
+ const simulateToggle = (index, newValue, section, onToggle, onSectionChange) => {
187
+ if (onToggle) {
188
+ onToggle(index, newValue, section)
189
+ }
190
+
191
+ if (onSectionChange) {
192
+ onSectionChange('toggle', index, newValue, section)
193
+ }
194
+ }
195
+
196
+ const section = { id: 'test-section', title: 'Test' }
197
+
198
+ simulateCheck(0, true, section, mockOnCheck, mockOnSectionChange)
199
+ expect(mockOnCheck).toHaveBeenCalledWith(0, true, 'test-section', section)
200
+ expect(mockOnSectionChange).toHaveBeenCalledWith('check', 0, true, section)
201
+
202
+ simulateToggle(0, true, section, mockOnToggle, mockOnSectionChange)
203
+ expect(mockOnToggle).toHaveBeenCalledWith(0, true, section)
204
+ expect(mockOnSectionChange).toHaveBeenCalledWith('toggle', 0, true, section)
205
+ })
206
+
207
+ test('disabled state prevents interactions', () => {
208
+ const simulateInteraction = (disabled) => {
209
+ if (disabled) return false
210
+ return true
211
+ }
212
+
213
+ expect(simulateInteraction(true)).toBe(false)
214
+ expect(simulateInteraction(false)).toBe(true)
215
+ })
216
+ })
217
+
218
+ // AccordionSection tests
219
+ describe('AccordionSection Component', () => {
220
+ test('keyboard navigation logic works correctly', () => {
221
+ const mockOnToggle = jest.fn()
222
+
223
+ const handleKeyDown = (event, disabled, onToggle) => {
224
+ if (disabled) return false
225
+
226
+ switch (event.key) {
227
+ case 'Enter':
228
+ case ' ':
229
+ if (onToggle) onToggle()
230
+ return true
231
+ default:
232
+ return false
233
+ }
234
+ }
235
+
236
+ // Test Enter key
237
+ expect(handleKeyDown({ key: 'Enter' }, false, mockOnToggle)).toBe(true)
238
+ expect(mockOnToggle).toHaveBeenCalled()
239
+
240
+ mockOnToggle.mockClear()
241
+
242
+ // Test Space key
243
+ expect(handleKeyDown({ key: ' ' }, false, mockOnToggle)).toBe(true)
244
+ expect(mockOnToggle).toHaveBeenCalled()
245
+
246
+ mockOnToggle.mockClear()
247
+
248
+ // Test disabled state
249
+ expect(handleKeyDown({ key: 'Enter' }, true, mockOnToggle)).toBe(false)
250
+ expect(mockOnToggle).not.toHaveBeenCalled()
251
+
252
+ // Test other keys
253
+ expect(handleKeyDown({ key: 'Tab' }, false, mockOnToggle)).toBe(false)
254
+ })
255
+
256
+ test('header click logic works correctly', () => {
257
+ const mockOnToggle = jest.fn()
258
+
259
+ const handleHeaderClick = (event, disabled, onToggle) => {
260
+ if (disabled) return false
261
+
262
+ // Don't toggle if clicking on interactive elements
263
+ if (event.target.closest('.accordion-section-checker') ||
264
+ event.target.closest('.accordion-section--toolbar')) {
265
+ return false
266
+ }
267
+
268
+ if (onToggle) onToggle()
269
+ return true
270
+ }
271
+
272
+ // Normal click
273
+ const normalEvent = { target: { closest: () => null } }
274
+ expect(handleHeaderClick(normalEvent, false, mockOnToggle)).toBe(true)
275
+ expect(mockOnToggle).toHaveBeenCalled()
276
+
277
+ mockOnToggle.mockClear()
278
+
279
+ // Click on checker
280
+ const checkerEvent = { target: { closest: (selector) => selector === '.accordion-section-checker' ? true : null } }
281
+ expect(handleHeaderClick(checkerEvent, false, mockOnToggle)).toBe(false)
282
+ expect(mockOnToggle).not.toHaveBeenCalled()
283
+
284
+ // Click on toolbar
285
+ const toolbarEvent = { target: { closest: (selector) => selector === '.accordion-section--toolbar' ? true : null } }
286
+ expect(handleHeaderClick(toolbarEvent, false, mockOnToggle)).toBe(false)
287
+ expect(mockOnToggle).not.toHaveBeenCalled()
288
+
289
+ // Disabled state
290
+ expect(handleHeaderClick(normalEvent, true, mockOnToggle)).toBe(false)
291
+ expect(mockOnToggle).not.toHaveBeenCalled()
292
+ })
293
+
294
+ test('section CSS classes generation works correctly', () => {
295
+ const generateSectionClasses = (open, disabled, animated, checked) => {
296
+ return [
297
+ 'accordion-section',
298
+ open && 'open',
299
+ disabled && 'disabled',
300
+ animated && 'animated',
301
+ checked && 'checked'
302
+ ].filter(Boolean).join(' ')
303
+ }
304
+
305
+ expect(generateSectionClasses(false, false, false, false)).toBe('accordion-section')
306
+ expect(generateSectionClasses(true, false, false, false)).toBe('accordion-section open')
307
+ expect(generateSectionClasses(false, true, false, false)).toBe('accordion-section disabled')
308
+ expect(generateSectionClasses(false, false, true, false)).toBe('accordion-section animated')
309
+ expect(generateSectionClasses(false, false, false, true)).toBe('accordion-section checked')
310
+ expect(generateSectionClasses(true, true, true, true)).toBe('accordion-section open disabled animated checked')
311
+ })
312
+
313
+ test('checked icon logic works correctly', () => {
314
+ const getCheckedIcon = (checked) => {
315
+ return checked === undefined || checked === null ?
316
+ null :
317
+ checked === false ? "check_box_outline_blank" : "check_box"
318
+ }
319
+
320
+ expect(getCheckedIcon(undefined)).toBeNull()
321
+ expect(getCheckedIcon(null)).toBeNull()
322
+ expect(getCheckedIcon(false)).toBe("check_box_outline_blank")
323
+ expect(getCheckedIcon(true)).toBe("check_box")
324
+ })
325
+
326
+ test('toggler icon logic works correctly', () => {
327
+ const getTogglerIcon = (open) => {
328
+ return open ? "expand_less" : "expand_more"
329
+ }
330
+
331
+ expect(getTogglerIcon(true)).toBe("expand_less")
332
+ expect(getTogglerIcon(false)).toBe("expand_more")
333
+ })
334
+ })